/
nv-lang
/
nova
Обзор
Документация
Войти
/
nv-lang
/
nova
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
std/src/encoding/serde/json.nv
435 строк
17 KB
Evgeniy Golovin
fix(221.1): №254 — bound-check + specificity для Next[T]/Iter[I], Iter-делегаты, T-binding, разворот 16 обходов
03 авг 2026, 14:37
03 авг 2026, 14:37
ef341bd
Код
Авторство
О чём код?
// std/encoding/serde/json.nv — JSON-backend для serde. // // JsonSerializer / JsonDeserializer реализуют Serializer / Deserializer // (serde.nv) ПОВЕРХ существующего std/encoding/json (JsonValue/Json.parse/ // @to_str) — reuse RFC 8259-логики, escape, format_num, strict-DuplicateKey. // НЕ дублируем парсер. PURE codec (без I/O-эффекта). // // Публичный API — то, что зовёт typed `.json[T]` для record-DTO: // Serde.encode[T]/decode[T]/encode_pretty/to_value/from_value/ // decode_bytes/decode_with. // // Soundness: depth-guard (default 128), numeric точное-целое-check // (LossyInteger/OutOfRange, no silent-lossy). Детерминизм полей-порядка: // struct/record-поля (begin_struct/struct_field) идут в ПОРЯДКЕ ОБЪЯВЛЕНИЯ — // `SerFrame.obj` — `JsonObject` (encoding.json), упорядоченная map, а не голый // `HashMap` (который порядок вставки не хранит, а его hash-итерация вдобавок // рандомизирована МЕЖДУ процессами — HashDoS-защита seed'а, трогать нельзя). // Отдельно: явный `HashMap[str, V]`-VALUE (не struct-поля, а map-тип в теле // записи) по-прежнему сортирует ключи (`keys.sort()`) — это не относится // к порядку struct-полей, а к произвольной map-СОДЕРЖИМОЙ. module encoding.serde import std.encoding.json.{JsonValue, JsonObject, Json, ParseJsonError, pretty} import std.encoding.base64.{Base64} // ────────────────────────────────────────────────────────────────────────── // JsonSerializer — единый mutable stack-machine. Собирает // JsonValue-дерево через begin/key/end; scalars — терминалы. `put` присоединяет // готовое значение к текущему frame (obj с pending-key / arr / root). // ────────────────────────────────────────────────────────────────────────── /// Serialization stack frame: obj (with a pending key) or arr. /// /// `obj` — a `JsonObject` (ordered map), NOT a bare `HashMap`: struct fields /// (`begin_struct`/`struct_field`) follow the order of `serialize_field` calls, /// i.e. the DECLARATION order of the record's fields — this is the deterministic /// field-order fix (registry 221.1 №148). type SerFrame { is_obj bool, obj JsonObject, arr []JsonValue, pending str } fn SerFrame.obj_frame() -> SerFrame => { is_obj: true, obj: JsonObject.new(), arr: [], pending: "" } fn SerFrame.arr_frame() -> SerFrame => { is_obj: false, obj: JsonObject.new(), arr: [], pending: "" } /// Serializer: drive Serialize → build a JsonValue → @to_str() compact JSON. /// Carries a depth-counter (Q14). #unstable export type JsonSerializer { root JsonValue has_root bool stack []SerFrame depth int max_depth int } #unstable export fn JsonSerializer.new() -> JsonSerializer => { root: JsonValue.null(), has_root: false, stack: [], depth: 0, max_depth: 128 } /// Attach a finished value to the current context (top frame / root). fn JsonSerializer mut @put(v JsonValue) -> Result[(), SerError] { match @stack.pop() { None => { @root = v @has_root = true Ok(()) } Some(mut fr) => { if fr.is_obj { fr.obj.insert(fr.pending, v) fr.pending = "" } else { fr.arr.push(v) } @stack.push(fr) Ok(()) } } } fn JsonSerializer mut @set_pending(key str) -> Result[(), SerError] { match @stack.pop() { None => Err(SerError.new(SerOther("field key outside container"))) Some(mut fr) => { fr.pending = key @stack.push(fr) Ok(()) } } } fn JsonSerializer mut @finish_obj() -> Result[(), SerError] { @depth -= 1 match @stack.pop() { None => Err(SerError.new(SerOther("end without begin"))) // Прямая конструкция варианта из УЖЕ упорядоченного `fr.obj` (JsonObject) // — НЕ `JsonValue.object(HashMap)` (та перегонка сортирует ключи, что // потеряло бы порядок объявления полей записи). Some(fr) => @put(JsonValue.Object(fr.obj)) } } // ── Serializer-протокол ── fn JsonSerializer mut @serialize_bool(v bool) -> Result[(), SerError] => @put(JsonValue.bool(v)) fn JsonSerializer mut @serialize_int(v int) -> Result[(), SerError] { // Reject integers that would silently lose precision in the f64 // JSON number — SYMMETRIC with `is_exact_int` on the deser side (`|n| < // 2^53`, strict), so a value this encodes is one deser_int accepts back. // `json_encode(9007199254740993)` is `Err(SerLossyInteger)`, never ...992. if v >= 9007199254740992 || v <= -9007199254740992 { return Err(SerError.new(SerLossyInteger(v.to_str()))) } @put(JsonValue.num(v as f64)) } fn JsonSerializer mut @serialize_uint(v u64) -> Result[(), SerError] { if v >= 9007199254740992 { return Err(SerError.new(SerLossyInteger(v.to_str()))) } @put(JsonValue.num(v as f64)) } fn JsonSerializer mut @serialize_float(v f64) -> Result[(), SerError] { if v.is_nan() || v.is_infinite() { return Err(SerError.new(NonFiniteFloat)) } @put(JsonValue.num(v)) } fn JsonSerializer mut @serialize_str(v str) -> Result[(), SerError] => @put(JsonValue.str(v)) fn JsonSerializer mut @serialize_bytes(v []u8) -> Result[(), SerError] => @put(JsonValue.str(Base64.encode(v))) fn JsonSerializer mut @serialize_unit() -> Result[(), SerError] => @put(JsonValue.null()) fn JsonSerializer mut @serialize_none() -> Result[(), SerError] => @put(JsonValue.null()) fn JsonSerializer mut @begin_struct(name str, len int) -> Result[(), SerError] { @depth += 1 if @depth > @max_depth { return Err(SerError.new(SerDepthLimit)) } @stack.push(SerFrame.obj_frame()) Ok(()) } fn JsonSerializer mut @struct_field(key str) -> Result[(), SerError] => @set_pending(key) fn JsonSerializer mut @end_struct() -> Result[(), SerError] => @finish_obj() fn JsonSerializer mut @begin_map(len int) -> Result[(), SerError] { @depth += 1 if @depth > @max_depth { return Err(SerError.new(SerDepthLimit)) } @stack.push(SerFrame.obj_frame()) Ok(()) } fn JsonSerializer mut @map_key(key str) -> Result[(), SerError] => @set_pending(key) fn JsonSerializer mut @end_map() -> Result[(), SerError] => @finish_obj() fn JsonSerializer mut @begin_seq(len int) -> Result[(), SerError] { @depth += 1 if @depth > @max_depth { return Err(SerError.new(SerDepthLimit)) } @stack.push(SerFrame.arr_frame()) Ok(()) } fn JsonSerializer mut @end_seq() -> Result[(), SerError] { @depth -= 1 match @stack.pop() { None => Err(SerError.new(SerOther("end_seq without begin"))) Some(fr) => @put(JsonValue.array(fr.arr)) } } // ────────────────────────────────────────────────────────────────────────── // JsonDeserializer — cursor по JsonValue-дереву. Скаляры — прямой pull; составные // — под-cursor через enter_* (sub-deserializer только читает, нет write-back). // depth counter. numeric fidelity. // ────────────────────────────────────────────────────────────────────────── /// Deserializer: Json.parse → JsonValue → drive Deserialize. #unstable export type JsonDeserializer { cur JsonValue, depth int, max_depth int } #unstable export fn JsonDeserializer.new(v JsonValue, depth int, max_depth int) -> JsonDeserializer => { cur: v, depth, max_depth } /// Name of the current value's JSON type (for error messages). fn json_kind(v JsonValue) -> str { if v.is_null() { "null" } else if v.is_bool() { "bool" } else if v.is_num() { "number" } else if v.is_str() { "string" } else if v.is_array() { "array" } else { "object" } } /// Is `f64` an exact integer in the safe range [-2^53, 2^53]? (Q15) fn is_exact_int(n f64) -> bool => n.is_finite() && n == n.floor() && n.abs() < 9007199254740992.0 // ── Deserializer-протокол ── fn JsonDeserializer mut @deser_bool() -> Result[bool, DeError] { match @cur.bool() { Some(b) => Ok(b) None => Err(DeError.unexpected("bool", json_kind(@cur))) } } fn JsonDeserializer mut @deser_str() -> Result[str, DeError] { match @cur.str() { Some(s) => Ok(s) None => Err(DeError.unexpected("string", json_kind(@cur))) } } fn JsonDeserializer mut @deser_float() -> Result[f64, DeError] { match @cur.num() { Some(n) => Ok(n) None => Err(DeError.unexpected("number", json_kind(@cur))) } } fn JsonDeserializer mut @deser_int() -> Result[int, DeError] { match @cur.num() { None => Err(DeError.unexpected("int", json_kind(@cur))) Some(n) => { if !is_exact_int(n) { Err(DeError.new(LossyInteger(n.to_str()))) } else { Ok(n as int) } } } } fn JsonDeserializer mut @deser_uint() -> Result[u64, DeError] { match @cur.num() { None => Err(DeError.unexpected("uint", json_kind(@cur))) Some(n) => { if n < 0.0 { Err(DeError.new(OutOfRange(n.to_str()))) } else if !is_exact_int(n) { Err(DeError.new(LossyInteger(n.to_str()))) } else { Ok(n as u64) } } } } fn JsonDeserializer mut @deser_bytes() -> Result[[]u8, DeError] { match @cur.str() { None => Err(DeError.unexpected("base64 string", json_kind(@cur))) Some(s) => match Base64.decode(s) { Ok(b) => Ok(b) Err(_e) => Err(DeError.new(Syntax("invalid base64 data"))) } } } fn JsonDeserializer mut @is_null() -> Result[bool, DeError] => Ok(@cur.is_null()) fn JsonDeserializer mut @is_str() -> Result[bool, DeError] => Ok(@cur.is_str()) fn JsonDeserializer mut @enter_field(key str) -> Result[JsonDeserializer, DeError] { if @depth + 1 > @max_depth { return Err(DeError.new(DepthLimitExceeded)) } match @cur.object() { None => Err(DeError.unexpected("object", json_kind(@cur))) Some(m) => match m.get(key) { None => Err(DeError.new(MissingField(key), path: "$.${key}")) Some(v) => Ok(JsonDeserializer.new(v, @depth + 1, @max_depth)) } } } fn JsonDeserializer mut @enter_field_or_null(key str) -> Result[JsonDeserializer, DeError] { if @depth + 1 > @max_depth { return Err(DeError.new(DepthLimitExceeded)) } match @cur.object() { None => Err(DeError.unexpected("object", json_kind(@cur))) Some(m) => match m.get(key) { None => Ok(JsonDeserializer.new(JsonValue.null(), @depth + 1, @max_depth)) Some(v) => Ok(JsonDeserializer.new(v, @depth + 1, @max_depth)) } } } // Exact presence check — distinct from // `enter_field_or_null` (which conflates "absent key" with "present JSON // null"). Used by the record synthesizer's `default`/`alias`-fallback chain. fn JsonDeserializer mut @has_field(key str) -> Result[bool, DeError] { match @cur.object() { None => Err(DeError.unexpected("object", json_kind(@cur))) Some(m) => Ok(m.get(key).is_some()) } } fn JsonDeserializer mut @seq_len() -> Result[int, DeError] { match @cur.array() { None => Err(DeError.unexpected("array", json_kind(@cur))) Some(a) => Ok(a.len()) } } fn JsonDeserializer mut @enter_index(i int) -> Result[JsonDeserializer, DeError] { if @depth + 1 > @max_depth { return Err(DeError.new(DepthLimitExceeded)) } match @cur.array() { None => Err(DeError.unexpected("array", json_kind(@cur))) Some(a) => match a.get(i) { Some(v) => Ok(JsonDeserializer.new(v, @depth + 1, @max_depth)) None => Err(DeError.new(Other("sequence index out of range"))) } } } fn JsonDeserializer mut @map_keys() -> Result[[]str, DeError] { match @cur.object() { None => Err(DeError.unexpected("object", json_kind(@cur))) Some(m) => { mut ks = m.keys().collect() Ok(ks) } } } fn JsonDeserializer mut @enter_key(key str) -> Result[JsonDeserializer, DeError] => @enter_field(key) // ── error-rendering (line/col из ParseJsonError сохраняются в тексте) ── /// ParseJsonError → a message with line/col (source-chain in the text, §8.0.3). fn parse_err_msg(e ParseJsonError) -> str { match e { UnexpectedChar { line, col, found } => "unexpected character '${found}' at line ${line}, col ${col}" UnexpectedEof { line, col } => "unexpected end of input at line ${line}, col ${col}" InvalidEscape { line, col, seq } => "invalid escape '${seq}' at line ${line}, col ${col}" InvalidUnicode { line, col, hex } => "invalid unicode '${hex}' at line ${line}, col ${col}" InvalidNumber { line, col, text } => "invalid number '${text}' at line ${line}, col ${col}" DuplicateKey { line, col, key } => "duplicate key '${key}' at line ${line}, col ${col}" TrailingContent { line, col } => "trailing content at line ${line}, col ${col}" } } // ────────────────────────────────────────────────────────────────────────── // Публичный API — record-DTO, разблокировка typed `.json[T]`. // ────────────────────────────────────────────────────────────────────────── // NB: публичный API — FREE-функции, НЕ namespace-static методы. Причина: // turbofish на namespace/type-static generic-методе (`Ns.decode[T]`) НЕ // мономорфизируется (erased-symbol); free-fn turbofish — мономорфизируется. // Каждая делает работу ИНЛАЙН через `v.serialize`/`T.deserialize` // (proven-working generic-dispatch), без вложенных generic-обёрток. // [M-180-namespace-static-generic-mono]: namespace/type-static generic-методы // (style `Serde.decode[T]`) не мономорфизируются, потому API — free-функции. /// T → JsonValue (DOM, without stringification). #impl(Serialize)-derived. #unstable export fn json_to_value[T Serialize](v T) -> Result[JsonValue, SerError] { mut s = JsonSerializer.new() v.serialize(s)? if s.has_root { Ok(s.root) } else { Ok(JsonValue.null()) } } /// T → compact JSON. Result (R1/D325). #unstable export fn json_encode[T Serialize](v T) -> Result[str, SerError] { mut s = JsonSerializer.new() v.serialize(s)? if s.has_root { Ok(s.root.to_str()) } else { Ok(JsonValue.null().to_str()) } } /// T → pretty JSON (2-space indentation). #unstable export fn json_encode_pretty[T Serialize](v T) -> Result[str, SerError] { mut s = JsonSerializer.new() v.serialize(s)? if s.has_root { Ok(s.root.to_str_pretty()) } else { Ok(JsonValue.null().to_str_pretty()) } } /// JsonValue (already-parsed DOM) → T. #unstable export fn json_from_value[T Deserialize](v JsonValue) -> Result[T, DeError] { mut d = JsonDeserializer.new(v, 0, 128) T.deserialize(d) } /// JSON string → T. Json.parse + drive Deserialize. Path/location in DeError. #unstable export fn json_decode[T Deserialize](s str) -> Result[T, DeError] { match Json.parse(s) { Ok(v) => { mut d = JsonDeserializer.new(v, 0, 128) T.deserialize(d) } Err(e) => Err(DeError.new(Syntax(parse_err_msg(e)))) } } /// Configurable max-depth (Q14); defaults to 128 in decode. #unstable export fn json_decode_with[T Deserialize](s str, max_depth int) -> Result[T, DeError] { match Json.parse(s) { Ok(v) => { mut d = JsonDeserializer.new(v, 0, max_depth) T.deserialize(d) } Err(e) => Err(DeError.new(Syntax(parse_err_msg(e)))) } } /// []u8 (UTF-8) → T (byte-first input; Plan 178 Body.@json). #unstable export fn json_decode_bytes[T Deserialize](b []u8) -> Result[T, DeError] { match b.to_str() { Ok(s) => { match Json.parse(s) { Ok(v) => { mut d = JsonDeserializer.new(v, 0, 128) T.deserialize(d) } Err(e) => Err(DeError.new(Syntax(parse_err_msg(e)))) } } Err(e) => Err(DeError.new(Syntax("invalid utf-8 at byte ${e.byte_offset}"))) } }