///|
// Encode every string token with an 's' prefix and every number as an 'n'
// string before using the core parser. Tagging *all* strings prevents collisions
// with user data. This keeps core's grammar/Unicode checks without its lossy
// Double conversion. Object keys must still be original string tokens.
fn pg_json(text : String, canonical_numbers? : Bool = true) -> Json raise {
let encoded = StringBuilder()
let mut i = 0
let mut depth = 0
while i < text.length() {
let start = i
match text[i] {
'"' => {
encoded.write_string("\"s")
i += 1
let content = i
while i < text.length() {
if text[i] == '\\' {
i += 2
} else if text[i] == '"' {
break
} else {
i += 1
}
}
if i >= text.length() {
raise Conversion("unterminated JSON string")
}
i += 1
encoded.write_view(text[content:i])
}
'-' | '0'..='9' => {
i += 1
while i < text.length() {
match text[i] {
'0'..='9' | '.' | 'e' | 'E' | '+' | '-' => i += 1
_ => break
}
}
let token = text[start:i]
lexmatch token {
re"^-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE](\+|-)?[0-9]+)?$" => ()
_ => raise Conversion("invalid JSON number")
}
encoded.write_string("\"n")
encoded.write_view(token)
encoded.write_char('"')
}
_ => {
// Non-ASCII outside a quoted string is invalid JSON.
let unit = text[i]
if unit > 127 {
raise Conversion("invalid JSON token")
}
match unit {
'{' | '[' => {
depth += 1
// serde_json permits at most 127 nested containers.
if depth >= 128 {
raise Conversion("JSON depth limit exceeded")
}
}
'}' | ']' => depth -= 1
_ => ()
}
encoded.write_char(unit.to_int().unsafe_to_char())
i += 1
}
}
}
restore_pg_json(@json.parse(encoded.to_string()), canonical_numbers~)
}
///|
fn restore_pg_json(
value : Json,
canonical_numbers? : Bool = true,
) -> Json raise {
match value {
String(text) => {
if text.has_prefix("s") {
return Json::string(text[1:].to_owned())
}
let token = text[1:].to_owned()
let mut repr = token.replace_all(old="E", new="e")
if canonical_numbers && repr == "-0" {
repr = "0"
} else if canonical_numbers && repr.find("e") is Some(pos) {
if repr[pos + 1] != '+' && repr[pos + 1] != '-' {
repr = repr[:pos + 1].to_owned() + "+" + repr[pos + 1:].to_owned()
}
}
Json::number(pg_float(token, single=false), repr~)
}
Array(items) =>
Json::array(items.map(item => restore_pg_json(item, canonical_numbers~)))
Object(fields) => {
let restored : Map[String, Json] = Map([])
for key, value in fields {
if !key.has_prefix("s") {
raise Conversion("JSON object key must be a string")
}
restored[key[1:].to_owned()] = restore_pg_json(
value,
canonical_numbers~,
)
}
Json::object(restored)
}
_ => value
}
}
///|
fn pg_json_equal(left : Json, right : Json) -> Bool {
match (left, right) {
(Number(a, repr=ar), Number(b, repr=br)) =>
ar.unwrap_or(a.to_string()) == br.unwrap_or(b.to_string())
(Array(a), Array(b)) =>
a.length() == b.length() &&
a.iter().zip(b.iter()).all(pair => pg_json_equal(pair.0, pair.1))
(Object(a), Object(b)) =>
a.length() == b.length() &&
a
.iter()
.all(pair => b.get(pair.0) is Some(value) && pg_json_equal(pair.1, value))
_ => left == right
}
}