///|
/// Ordered typed JSON; numeric payloads use decimal strings, including raw
/// Float32 bits. No precision or NaN information is routed through JSON numbers.
pub fn binary_to_json(entries : Array[BinaryEntry]) -> Json {
Json::array(
entries.map(entry => {
let (kind, value) = match entry.value {
ObjectValue(children) => ("object", binary_to_json(children))
StringValue(s) => ("string", Json::string(s))
WideString(s) => ("wstring", Json::string(s))
Int32Value(n) => ("int32", Json::string(n.to_string()))
Float32Bits(n) => ("float32bits", Json::string(n.to_string()))
Pointer32(n) => ("pointer32", Json::string(n.to_string()))
Color32(n) => ("color32", Json::string(n.to_string()))
UInt64Value(n) => ("uint64", Json::string(n.to_string()))
Int64Value(n) => ("int64", Json::string(n.to_string()))
}
Json::object({
"key": Json::string(entry.key),
"type": Json::string(kind),
"value": value,
})
}),
)
}
///|
pub fn binary_from_json(
json : Json,
options? : BinaryOptions = BinaryOptions::default(),
) -> Array[BinaryEntry] raise BinaryError {
check_binary_options(options)
let count : Ref[Int] = { val: 0, }
let result = binary_json_entries(json, options, 0, count)
encode_binary(result, options~) |> ignore
result
}
///|
fn binary_json_entries(
json : Json,
options : BinaryOptions,
depth : Int,
count : Ref[Int],
) -> Array[BinaryEntry] raise BinaryError {
guard json is Array(rows) else {
raise InvalidBinary(0, "Expected a typed entry array")
}
if depth > options.max_depth {
raise InvalidBinary(0, "JSON exceeds binary depth limit")
}
let result : Array[BinaryEntry] = []
for row in rows {
if count.val >= options.max_entries {
raise InvalidBinary(0, "JSON exceeds binary entry limit")
}
count.val += 1
guard row is Object(fields) &&
fields.length() == 3 &&
fields.get("key") is Some(String(key)) &&
fields.get("type") is Some(String(kind)) &&
fields.get("value") is Some(raw) else {
raise InvalidBinary(0, "Expected key, type and value")
}
let value = if kind == "object" {
ObjectValue(binary_json_entries(raw, options, depth + 1, count))
} else {
guard raw is String(text) else {
raise InvalidBinary(0, "Scalar payload must be a string")
}
try {
match kind {
"string" => BinaryValue::StringValue(text)
"wstring" => WideString(text)
"int32" => Int32Value(@strconv.parse_int(text[:]))
"int64" => Int64Value(@strconv.parse_int64(text[:]))
"uint64" => UInt64Value(@strconv.parse_uint64(text[:]))
"float32bits" => Float32Bits(@strconv.parse_uint(text[:]))
"pointer32" => Pointer32(@strconv.parse_uint(text[:]))
"color32" => Color32(@strconv.parse_uint(text[:]))
_ => raise InvalidBinary(0, "Unknown typed JSON kind: " + kind)
}
} catch {
_ => raise InvalidBinary(0, "Invalid typed JSON scalar: " + kind)
}
}
result.push({ key, value, })
}
result
}