Current section
Files
Jump to
Current section
Files
src/jasper.gleam
import gleam/dict.{type Dict}
import gleam/dynamic/decode.{type Decoder}
import gleam/float
import gleam/function
import gleam/int
import gleam/json.{type DecodeError, type Json}
import gleam/list
import gleam/result
import gleam/string
import gleam/string_tree.{type StringTree}
pub type JsonValue {
Object(Dict(String, JsonValue))
Array(List(JsonValue))
String(String)
Int(Int)
Float(Float)
Bool(Bool)
Null
}
pub fn decoder() -> Decoder(JsonValue) {
decode.one_of(decode.string |> decode.map(String), [
decode.int |> decode.map(Int),
decode.float |> decode.map(Float),
decode.bool |> decode.map(Bool),
decode.list(decode.recursive(decoder)) |> decode.map(Array),
decode.dict(decode.string, decode.recursive(decoder))
|> decode.map(Object),
decode.success(Null),
])
}
pub fn parse(value: String) -> Result(JsonValue, DecodeError) {
json.parse(value, decoder())
}
pub fn parse_bits(value: BitArray) -> Result(JsonValue, DecodeError) {
json.parse_bits(value, decoder())
}
pub fn to_json(value: JsonValue) -> Json {
case value {
String(s) -> json.string(s)
Int(i) -> json.int(i)
Float(f) -> json.float(f)
Bool(b) -> json.bool(b)
Array(a) -> json.array(a, to_json)
Object(o) -> json.dict(o, function.identity, to_json)
Null -> json.null()
}
}
pub fn to_string(value: JsonValue) -> String {
to_json(value) |> json.to_string
}
pub fn to_string_tree(value: JsonValue) -> StringTree {
to_json(value) |> json.to_string_tree
}
pub type Indentation {
Spaces(Int)
Tab
Tabs(Int)
}
pub fn to_pretty_string(value: JsonValue, indentation: Indentation) -> String {
do_to_pretty_string(
value,
case indentation {
Spaces(n) -> string.repeat(" ", n)
Tab -> "\t"
Tabs(n) -> string.repeat("\t", n)
},
0,
)
}
fn do_to_pretty_string(value: JsonValue, space: String, depth: Int) -> String {
case value {
Object(obj) ->
case dict.to_list(obj) {
[] -> "{}"
ls ->
"{\n"
<> string.repeat(space, depth + 1)
<> {
list.map(ls, fn(kv) {
"\""
<> kv.0
<> "\": "
<> do_to_pretty_string(kv.1, space, depth + 1)
})
|> string.join(",\n" <> string.repeat(space, depth + 1))
}
<> "\n"
<> string.repeat(space, depth)
<> "}"
}
Array([]) -> "[]"
Array(arr) ->
"[\n"
<> string.repeat(space, depth + 1)
<> {
arr
|> list.map(do_to_pretty_string(_, space, depth + 1))
|> string.join(",\n" <> string.repeat(space, depth + 1))
}
<> "\n"
<> string.repeat(space, depth)
<> "]"
String(s) -> "\"" <> s <> "\""
Int(i) -> int.to_string(i)
Float(f) -> float.to_string(f)
Bool(True) -> "true"
Bool(False) -> "false"
Null -> "null"
}
}
pub fn to_pretty_string_tree(
value: JsonValue,
indentation: Indentation,
) -> StringTree {
to_pretty_string(value, indentation) |> string_tree.from_string
}
pub type JsonQuery {
Root
Key(query: JsonQuery, key: String)
KeyOr(query: JsonQuery, key: String, or: JsonValue)
Index(query: JsonQuery, index: Int)
IndexOr(query: JsonQuery, index: Int, or: JsonValue)
Filter(query: JsonQuery, predicate: fn(JsonValue) -> Bool)
Map(query: JsonQuery, mapping: fn(JsonValue) -> JsonValue)
MapKeys(query: JsonQuery, mapping: fn(String) -> String)
MapValues(query: JsonQuery, mapping: fn(String, JsonValue) -> JsonValue)
FilterMap(query: JsonQuery, mapping: fn(JsonValue) -> Result(JsonValue, Nil))
ForEach(query: JsonQuery)
ForEachOk(query: JsonQuery)
}
type InvJsonQuery {
InvEnd
InvKey(key: String, query: InvJsonQuery)
InvKeyOr(key: String, or: JsonValue, query: InvJsonQuery)
InvIndex(index: Int, query: InvJsonQuery)
InvIndexOr(index: Int, or: JsonValue, query: InvJsonQuery)
InvFilter(predicate: fn(JsonValue) -> Bool, query: InvJsonQuery)
InvMap(mapping: fn(JsonValue) -> JsonValue, query: InvJsonQuery)
InvMapKeys(mapping: fn(String) -> String, query: InvJsonQuery)
InvMapValues(mapping: fn(String, JsonValue) -> JsonValue, query: InvJsonQuery)
InvFilterMap(
mapping: fn(JsonValue) -> Result(JsonValue, Nil),
query: InvJsonQuery,
)
InvForEach(query: InvJsonQuery)
InvForEachOk(query: InvJsonQuery)
}
fn invert_query(query: JsonQuery) -> InvJsonQuery {
do_invert_query(query, InvEnd)
}
fn do_invert_query(query: JsonQuery, state: InvJsonQuery) -> InvJsonQuery {
case query {
Root -> state
Key(query, key) -> do_invert_query(query, InvKey(key, state))
KeyOr(query, key, o) -> do_invert_query(query, InvKeyOr(key, o, state))
Index(query, index) -> do_invert_query(query, InvIndex(index, state))
IndexOr(query, index, or) ->
do_invert_query(query, InvIndexOr(index, or, state))
Filter(query, predicate) ->
do_invert_query(query, InvFilter(predicate, state))
Map(query, mapping) -> do_invert_query(query, InvMap(mapping, state))
MapKeys(query, mapping) ->
do_invert_query(query, InvMapKeys(mapping, state))
MapValues(query, mapping) ->
do_invert_query(query, InvMapValues(mapping, state))
FilterMap(query, mapping) ->
do_invert_query(query, InvFilterMap(mapping, state))
ForEach(query) -> do_invert_query(query, InvForEach(state))
ForEachOk(query) -> do_invert_query(query, InvForEachOk(state))
}
}
pub type JsonQueryError {
UnexpectedType(JsonValue)
MissingObjectKey(JsonValue, key: String)
IndexOutOfBounds(JsonValue, index: Int)
}
pub fn query(json: JsonValue, query: JsonQuery) {
do_query(json, invert_query(query))
}
fn do_query(
json: JsonValue,
query: InvJsonQuery,
) -> Result(JsonValue, JsonQueryError) {
case query {
InvEnd -> Ok(json)
InvKey(key, q) ->
case json {
Object(obj) as j ->
obj
|> dict.get(key)
|> result.replace_error(MissingObjectKey(j, key))
j -> Error(UnexpectedType(j))
}
|> result.map(do_query(_, q))
|> result.flatten
InvKeyOr(key, or, q) ->
case json {
Object(obj) ->
obj
|> dict.get(key)
|> result.unwrap(or)
|> Ok
j -> Error(UnexpectedType(j))
}
|> result.map(do_query(_, q))
|> result.flatten
InvIndex(index, q) ->
case json {
Array(arr) as j ->
arr
|> list.drop(index)
|> list.first()
|> result.replace_error(IndexOutOfBounds(j, index))
j -> Error(UnexpectedType(j))
}
|> result.map(do_query(_, q))
|> result.flatten
InvIndexOr(index, or, q) ->
case json {
Array(arr) ->
arr
|> list.drop(index)
|> list.first()
|> result.unwrap(or)
|> Ok
j -> Error(UnexpectedType(j))
}
|> result.map(do_query(_, q))
|> result.flatten
InvFilter(predicate, q) ->
case json {
Array(arr) ->
arr
|> list.filter(predicate)
|> Array
|> do_query(q)
j -> Error(UnexpectedType(j))
}
InvMap(mapping, q) ->
case json {
Array(arr) ->
arr
|> list.map(mapping)
|> Array
|> do_query(q)
j -> Error(UnexpectedType(j))
}
InvMapKeys(mapping, q) ->
case json {
Object(obj) ->
obj
|> dict.to_list
|> list.map(fn(kv) { #(mapping(kv.0), kv.1) })
|> dict.from_list
|> Object
|> do_query(q)
j -> Error(UnexpectedType(j))
}
InvMapValues(mapping, q) ->
case json {
Object(obj) ->
obj
|> dict.map_values(mapping)
|> Object
|> do_query(q)
j -> Error(UnexpectedType(j))
}
InvFilterMap(mapping, q) ->
case json {
Array(arr) ->
arr
|> list.filter_map(mapping)
|> Array
|> do_query(q)
j -> Error(UnexpectedType(j))
}
InvForEach(q) ->
case json {
Array(arr) ->
arr
|> list.map(do_query(_, q))
|> result.all
|> result.map(Array)
j -> Error(UnexpectedType(j))
}
InvForEachOk(q) ->
case json {
Array(arr) ->
arr
|> list.map(do_query(_, q))
|> result.values
|> Array
|> Ok
j -> Error(UnexpectedType(j))
}
}
}