Current section
Files
Jump to
Current section
Files
priv/internal.gleam
import gleam/bit_array
import gleam/dict
import gleam/dynamic.{type Dynamic}
import gleam/dynamic/decode
import gleam/json
import gleam/option.{type Option, None, Some}
/// One JSON field, or nothing when the value is absent. Keeps optional
/// properties out of the encoded object entirely rather than emitting null.
pub fn opt(
name: String,
value: Option(a),
to_json: fn(a) -> json.Json,
) -> List(#(String, json.Json)) {
case value {
Some(v) -> [#(name, to_json(v))]
None -> []
}
}
/// A `cid-link` is rendered in JSON as `{ "$link": <cid> }`.
pub fn encode_cid_link(cid: String) -> json.Json {
json.object([#("$link", json.string(cid))])
}
pub fn cid_link_decoder() -> decode.Decoder(String) {
decode.at(["$link"], decode.string)
}
/// `bytes` are rendered in JSON as `{ "$bytes": <base64, no padding> }`.
pub fn encode_bytes(bytes: BitArray) -> json.Json {
json.object([#("$bytes", json.string(bit_array.base64_encode(bytes, False)))])
}
pub fn bytes_decoder() -> decode.Decoder(BitArray) {
use encoded <- decode.then(decode.at(["$bytes"], decode.string))
case bit_array.base64_decode(encoded) {
Ok(bytes) -> decode.success(bytes)
Error(_) -> decode.failure(<<>>, "bytes")
}
}
/// Re-encode an already-decoded `unknown` value back to JSON so records that
/// carry one round-trip. Reconstructs the JSON value from the decoded `Dynamic`.
pub fn dynamic_to_json(value: Dynamic) -> json.Json {
case decode.run(value, json_value_decoder()) {
Ok(rendered) -> rendered
Error(_) -> json.null()
}
}
fn json_value_decoder() -> decode.Decoder(json.Json) {
use <- decode.recursive
decode.one_of(decode.bool |> decode.map(json.bool), [
decode.int |> decode.map(json.int),
decode.float |> decode.map(json.float),
decode.string |> decode.map(json.string),
decode.list(json_value_decoder()) |> decode.map(json.preprocessed_array),
decode.dict(decode.string, json_value_decoder())
|> decode.map(fn(entries) { json.object(dict.to_list(entries)) }),
decode.success(json.null()),
])
}