///|
fn parse_bounded_json(text : String, limits : Limits) -> Json raise SdmxError {
check_text(text, limits, "JSON text")
let value = @json.parse(
text[:],
max_nesting_depth=24,
reject_duplicate_keys=true,
preserve_number_repr=true,
) catch {
error => raise Invalid(path="JSON", reason=error.to_string())
}
let pending = [value]
let mut nodes = 0
while !pending.is_empty() {
let node = pending.pop().unwrap()
nodes += 1
if nodes > 1000000 {
raise Limit(resource="JSON nodes", maximum=1000000)
}
match node {
Object(object) =>
for key, child in object {
check_field(key, limits, "JSON property")
pending.push(child)
}
Array(array) =>
for child in array {
pending.push(child)
}
String(text) => check_field(text, limits, "JSON string")
_ => ()
}
if pending.length() > 1000000 {
raise Limit(resource="JSON nodes", maximum=1000000)
}
}
value
}
///|
fn j_object(value : Json, path : String) -> Map[String, Json] raise SdmxError {
match value {
Object(object) => object
_ => raise Invalid(path~, reason="expected object")
}
}
///|
fn j_array(value : Json, path : String) -> Array[Json] raise SdmxError {
match value {
Array(array) => array
_ => raise Invalid(path~, reason="expected array")
}
}
///|
fn j_string(value : Json, path : String) -> String raise SdmxError {
match value {
String(text) => text
_ => raise Invalid(path~, reason="expected string")
}
}
///|
fn j_bool(value : Json, path : String) -> Bool raise SdmxError {
match value {
True => true
False => false
_ => raise Invalid(path~, reason="expected boolean")
}
}
///|
fn j_int(value : Json, path : String, maximum : Int32) -> Int32 raise SdmxError {
match value {
Number(n, ..) if n >= 0.0 && n <= maximum.to_double() => {
let result = n.to_int()
if result.to_double() != n {
raise Invalid(path~, reason="expected nonnegative integer")
}
result
}
_ => raise Invalid(path~, reason="index outside supported range")
}
}
///|
fn j_required(
object : Map[String, Json],
name : String,
) -> Json raise SdmxError {
match object.get(name) {
Some(value) => value
None => raise Invalid(path=name, reason="missing property")
}
}
///|
fn j_known(
object : Map[String, Json],
names : Array[String],
path : String,
) -> Unit raise SdmxError {
for name, _ in object {
if !names.contains(name) {
raise Unsupported(feature="\{path}.\{name}")
}
}
}
///|
fn j_strings(value : Json, path : String) -> Array[String] raise SdmxError {
j_array(value, path).map(v => j_string(v, path))
}
///|
fn j_optional_string(
object : Map[String, Json],
name : String,
fallback : String,
) -> String raise SdmxError {
match object.get(name) {
None | Some(Null) => fallback
Some(value) => j_string(value, name)
}
}
///|
fn j_optional_bool(
object : Map[String, Json],
name : String,
fallback : Bool,
) -> Bool raise SdmxError {
match object.get(name) {
None | Some(Null) => fallback
Some(value) => j_bool(value, name)
}
}
///|
fn j_scalar(value : Json, path : String) -> String? raise SdmxError {
match value {
Null => None
String(text) =>
if text.is_empty() {
raise Invalid(path~, reason="empty scalar outside profile")
} else {
Some(text)
}
Number(_, repr~) =>
Some(
match repr {
Some(text) => text
None => value.stringify()
},
)
_ => raise Unsupported(feature="non-scalar component value at \{path}")
}
}
///|
fn canonical_json(value : Json, limits : Limits) -> String raise SdmxError {
let out = StringBuilder()
let mut units = 0
fn write(text : String) -> Unit raise SdmxError {
if units > limits.max_output - text.length() {
raise Limit(resource="JSON output", maximum=limits.max_output)
}
units += text.length()
out.write_string(text)
}
fn visit(value : Json) -> Unit raise SdmxError {
match value {
Object(object) => {
write("{")
let names : Array[String] = object.keys().collect()
names.sort_by((a, b) => a.lexical_compare(b))
for i, name in names {
if i > 0 {
write(",")
}
write(Json::string(name).stringify())
write(":")
visit(object[name])
}
write("}")
}
Array(array) => {
write("[")
for i, child in array {
if i > 0 {
write(",")
}
visit(child)
}
write("]")
}
_ => write(value.stringify())
}
}
visit(value)
out.to_string()
}