on master help sign in
Replace hand-rolled JSON parser and encoder with serde_json
commitd6dac588ab991ac8a96df6e8ab6683865e55d748 authoriko <mail@iko.soy> dateWed 25 Mar 2026, 11:04 (6 months ago) parent062b7b9
4 files changed+82−220
Cargo.lock+1 Cargo.toml+1 crates/knot-runtime/Cargo.toml+1 crates/knot-runtime/src/lib.rs+79−220
Cargo.lock+1
@@ -857,6 +857,7 @@ dependencies = [
857857 "num-traits",
858858 "ratatui",
859859 "rusqlite",
860+ "serde_json",
860861 "tiny_http",
861862 "ureq",
862863 "x25519-dalek",
Cargo.toml+1
@@ -22,4 +22,5 @@ ureq = "2"
2222num-bigint = "0.4"
2323num-traits = "0.2"
2424num-integer = "0.1"
25+serde_json = "1"
2526ariadne = "0.6"
crates/knot-runtime/Cargo.toml+1
@@ -18,4 +18,5 @@ getrandom = "0.3"
1818ed25519-dalek = { version = "2", default-features = false }
1919x25519-dalek = { version = "2", features = ["static_secrets"] }
2020chacha20poly1305 = "0.10"
21+serde_json = { workspace = true }
2122ureq = { workspace = true }
crates/knot-runtime/src/lib.rs+79−220
@@ -2902,183 +2902,48 @@ pub extern "C" fn knot_json_encode(v: *mut Value) -> *mut Value {
29022902pub extern "C" fn knot_json_decode(v: *mut Value) -> *mut Value {
29032903 match unsafe { as_ref(v) } {
29042904 Value::Text(s) => {
2905− let s = s.trim();
2906− let (val, _) = parse_json_value(s);
2907− val
2905+ match serde_json::from_str::<serde_json::Value>(s) {
2906+ Ok(json) => json_to_value(&json),
2907+ Err(e) => panic!("knot runtime: parseJson failed: {}", e),
2908+ }
29082909 }
29092910 _ => panic!("knot runtime: parseJson expected Text, got {}", type_name(v)),
29102911 }
29112912}
29122913
2913−/// Recursive JSON parser. Returns (parsed_value, rest_of_input).
2914−fn parse_json_value(s: &str) -> (*mut Value, &str) {
2915− let s = s.trim();
2916− if s.is_empty() {
2917− return (alloc(Value::Unit), s);
2918− }
2919− match s.as_bytes()[0] {
2920− b'"' => parse_json_string(s),
2921− b'{' => parse_json_obj(s),
2922− b'[' => parse_json_array(s),
2923− b't' if s.starts_with("true") => (alloc_bool(true), &s[4..]),
2924− b'f' if s.starts_with("false") => (alloc_bool(false), &s[5..]),
2925− b'n' if s.starts_with("null") => (alloc(Value::Unit), &s[4..]),
2926− _ => parse_json_number(s),
2927− }
2928−}
2929−
2930−fn parse_json_string(s: &str) -> (*mut Value, &str) {
2931− debug_assert!(s.starts_with('"'));
2932− let mut chars = s[1..].chars();
2933− let mut result = String::new();
2934− loop {
2935− match chars.next() {
2936− None => break,
2937− Some('"') => {
2938− let rest = chars.as_str();
2939− return (alloc(Value::Text(result)), rest);
2914+/// Convert a serde_json::Value into a Knot *mut Value.
2915+fn json_to_value(json: &serde_json::Value) -> *mut Value {
2916+ match json {
2917+ serde_json::Value::Null => alloc(Value::Unit),
2918+ serde_json::Value::Bool(b) => alloc_bool(*b),
2919+ serde_json::Value::Number(n) => {
2920+ if let Some(i) = n.as_i64() {
2921+ alloc_int(BigInt::from(i))
2922+ } else if let Some(u) = n.as_u64() {
2923+ alloc_int(BigInt::from(u))
2924+ } else if let Some(f) = n.as_f64() {
2925+ alloc_float(f)
2926+ } else {
2927+ alloc_int(BigInt::ZERO)
29402928 }
2941− Some('\\') => match chars.next() {
2942− Some('"') => result.push('"'),
2943− Some('\\') => result.push('\\'),
2944− Some('/') => result.push('/'),
2945− Some('n') => result.push('\n'),
2946− Some('r') => result.push('\r'),
2947− Some('t') => result.push('\t'),
2948− Some('u') => {
2949− let hex: String = chars.by_ref().take(4).collect();
2950− if let Ok(cp) = u32::from_str_radix(&hex, 16) {
2951− if (0xD800..=0xDBFF).contains(&cp) {
2952− // High surrogate — expect \uXXXX low surrogate
2953− let mut low_cp = 0u32;
2954− if chars.next() == Some('\\') && chars.next() == Some('u') {
2955− let hex2: String = chars.by_ref().take(4).collect();
2956− if let Ok(lc) = u32::from_str_radix(&hex2, 16) {
2957− low_cp = lc;
2958− }
2959− }
2960− if (0xDC00..=0xDFFF).contains(&low_cp) {
2961− let combined = 0x10000 + ((cp - 0xD800) << 10) + (low_cp - 0xDC00);
2962− if let Some(c) = char::from_u32(combined) {
2963− result.push(c);
2964− }
2965− }
2966− } else if let Some(c) = char::from_u32(cp) {
2967− result.push(c);
2968− }
2969− }
2970− }
2971− Some(c) => {
2972− result.push('\\');
2973− result.push(c);
2974− }
2975− None => break,
2976− },
2977− Some(c) => result.push(c),
2978− }
2979− }
2980− (alloc(Value::Text(result)), "")
2981−}
2982−
2983−fn parse_json_number(s: &str) -> (*mut Value, &str) {
2984− let mut end = 0;
2985− let mut is_float = false;
2986− let bytes = s.as_bytes();
2987− if end < bytes.len() && (bytes[end] == b'-' || bytes[end] == b'+') {
2988− end += 1;
2989− }
2990− while end < bytes.len() && bytes[end].is_ascii_digit() {
2991− end += 1;
2992− }
2993− if end < bytes.len() && bytes[end] == b'.' {
2994− is_float = true;
2995− end += 1;
2996− while end < bytes.len() && bytes[end].is_ascii_digit() {
2997− end += 1;
2998− }
2999− }
3000− if end < bytes.len() && (bytes[end] == b'e' || bytes[end] == b'E') {
3001− is_float = true;
3002− end += 1;
3003− if end < bytes.len() && (bytes[end] == b'-' || bytes[end] == b'+') {
3004− end += 1;
3005− }
3006− while end < bytes.len() && bytes[end].is_ascii_digit() {
3007− end += 1;
30082929 }
3009− }
3010− let num_str = &s[..end];
3011− let rest = &s[end..];
3012− if is_float {
3013− let n: f64 = num_str.parse().unwrap_or(0.0);
3014− (alloc_float(n), rest)
3015− } else {
3016− let n: BigInt = num_str.parse().unwrap_or(BigInt::ZERO);
3017− (alloc_int(n), rest)
3018− }
3019−}
3020−
3021−fn parse_json_obj(s: &str) -> (*mut Value, &str) {
3022− debug_assert!(s.starts_with('{'));
3023− let mut rest = s[1..].trim();
3024− let mut fields: Vec<RecordField> = Vec::new();
3025− if rest.starts_with('}') {
3026− return (alloc(Value::Unit), &rest[1..]);
3027− }
3028− loop {
3029− // Parse key (must be a string)
3030− if !rest.starts_with('"') {
3031− break;
2930+ serde_json::Value::String(s) => alloc(Value::Text(s.clone())),
2931+ serde_json::Value::Array(arr) => {
2932+ let items: Vec<*mut Value> = arr.iter().map(json_to_value).collect();
2933+ alloc(Value::Relation(items))
30322934 }
3033− let (key_val, after_key) = parse_json_string(rest);
3034− let key = match unsafe { as_ref(key_val) } {
3035− Value::Text(s) => s.clone(),
3036− _ => unreachable!(),
3037− };
3038− rest = after_key.trim();
3039− // Skip colon
3040− if rest.starts_with(':') {
3041− rest = rest[1..].trim();
3042− }
3043− // Parse value
3044− let (val, after_val) = parse_json_value(rest);
3045− rest = after_val.trim();
3046− fields.push(RecordField { name: key, value: val });
3047− // Skip comma or end
3048− if rest.starts_with(',') {
3049− rest = rest[1..].trim();
3050− } else if rest.starts_with('}') {
3051− rest = &rest[1..];
3052− break;
3053− } else {
3054− break;
3055− }
3056− }
3057− fields.sort_by(|a, b| a.name.cmp(&b.name));
3058− (alloc(Value::Record(fields)), rest)
3059−}
3060−
3061−fn parse_json_array(s: &str) -> (*mut Value, &str) {
3062− debug_assert!(s.starts_with('['));
3063− let mut rest = s[1..].trim();
3064− let mut items: Vec<*mut Value> = Vec::new();
3065− if rest.starts_with(']') {
3066− return (alloc(Value::Relation(items)), &rest[1..]);
3067− }
3068− loop {
3069− let (val, after_val) = parse_json_value(rest);
3070− rest = after_val.trim();
3071− items.push(val);
3072− if rest.starts_with(',') {
3073− rest = rest[1..].trim();
3074− } else if rest.starts_with(']') {
3075− rest = &rest[1..];
3076− break;
3077− } else {
3078− break;
2935+ serde_json::Value::Object(obj) => {
2936+ if obj.is_empty() {
2937+ return alloc(Value::Unit);
2938+ }
2939+ let mut fields: Vec<RecordField> = obj
2940+ .iter()
2941+ .map(|(k, v)| RecordField { name: k.clone(), value: json_to_value(v) })
2942+ .collect();
2943+ fields.sort_by(|a, b| a.name.cmp(&b.name));
2944+ alloc(Value::Record(fields))
30792945 }
30802946 }
3081− (alloc(Value::Relation(items)), rest)
30822947}
30832948
30842949// ── Standard library: utility operations ──────────────────────
@@ -6596,49 +6461,48 @@ fn base64_encode(data: &[u8]) -> String {
65966461}
65976462
65986463fn value_to_json(v: *mut Value) -> String {
6464+ serde_json::to_string(&value_to_serde_json(v)).unwrap_or_else(|_| "null".to_string())
6465+}
6466+
6467+/// Convert a Knot *mut Value into a serde_json::Value.
6468+fn value_to_serde_json(v: *mut Value) -> serde_json::Value {
65996469 if v.is_null() {
6600− return "null".to_string();
6470+ return serde_json::Value::Null;
66016471 }
66026472 match unsafe { as_ref(v) } {
6603− Value::Int(n) => n.to_string(),
6604− Value::Float(n) => n.to_string(),
6605− Value::Text(s) => {
6606− let escaped = s
6607− .replace('\\', "\\\\")
6608− .replace('"', "\\\"")
6609− .replace('\n', "\\n")
6610− .replace('\r', "\\r")
6611− .replace('\t', "\\t");
6612− format!("\"{}\"", escaped)
6613− }
6614− Value::Bool(b) => if *b { "true" } else { "false" }.to_string(),
6615− Value::Bytes(b) => {
6616− format!("\"{}\"", base64_encode(b))
6473+ Value::Int(n) => {
6474+ if let Some(i) = n.to_i64() {
6475+ serde_json::Value::Number(i.into())
6476+ } else {
6477+ // BigInt too large for i64 — encode as string
6478+ serde_json::Value::String(n.to_string())
6479+ }
66176480 }
6618− Value::Unit => "{}".to_string(),
6481+ Value::Float(n) => {
6482+ serde_json::json!(*n)
6483+ }
6484+ Value::Text(s) => serde_json::Value::String(s.clone()),
6485+ Value::Bool(b) => serde_json::Value::Bool(*b),
6486+ Value::Bytes(b) => serde_json::Value::String(base64_encode(b)),
6487+ Value::Unit => serde_json::Value::Object(serde_json::Map::new()),
66196488 Value::Record(fields) => {
6620− let parts: Vec<String> = fields
6621− .iter()
6622− .map(|f| {
6623− let escaped_name = f.name.replace('\\', "\\\\").replace('"', "\\\"");
6624− format!("\"{}\":{}", escaped_name, value_to_json(f.value))
6625− })
6626− .collect();
6627− format!("{{{}}}", parts.join(","))
6489+ let mut map = serde_json::Map::with_capacity(fields.len());
6490+ for f in fields {
6491+ map.insert(f.name.clone(), value_to_serde_json(f.value));
6492+ }
6493+ serde_json::Value::Object(map)
66286494 }
66296495 Value::Relation(rows) => {
6630− let parts: Vec<String> = rows.iter().map(|r| value_to_json(*r)).collect();
6631− format!("[{}]", parts.join(","))
6496+ serde_json::Value::Array(rows.iter().map(|r| value_to_serde_json(*r)).collect())
66326497 }
66336498 Value::Constructor(tag, payload) => {
6634− let p = value_to_json(*payload);
6635− let escaped_tag = tag
6636− .replace('\\', "\\\\")
6637− .replace('"', "\\\"");
6638− format!("{{\"tag\":\"{}\",\"value\":{}}}", escaped_tag, p)
6499+ let mut map = serde_json::Map::with_capacity(2);
6500+ map.insert("tag".into(), serde_json::Value::String(tag.clone()));
6501+ map.insert("value".into(), value_to_serde_json(*payload));
6502+ serde_json::Value::Object(map)
66396503 }
6640− Value::Function(_, _, src) => format!("\"<function: {}>\"", src),
6641− Value::IO(_, _) => "\"<<IO>>\"".to_string(),
6504+ Value::Function(_, _, src) => serde_json::Value::String(format!("<function: {}>", src)),
6505+ Value::IO(_, _) => serde_json::Value::String("<<IO>>".into()),
66426506 }
66436507}
66446508
@@ -6818,7 +6682,10 @@ pub extern "C" fn knot_http_listen(
68186682 if debug_enabled() {
68196683 eprintln!("[HTTP] body: {}", body_str);
68206684 }
6821− let (body_val, _) = parse_json_value(&body_str);
6685+ let body_val = match serde_json::from_str::<serde_json::Value>(&body_str) {
6686+ Ok(json) => json_to_value(&json),
6687+ Err(_) => alloc(Value::Unit),
6688+ };
68226689 match unsafe { as_ref(body_val) } {
68236690 Value::Record(body_fields) => {
68246691 for (bname, _bty) in &entry_body_fields {
@@ -7173,8 +7040,10 @@ pub extern "C" fn knot_http_fetch_io(
71737040 let body_text = response.into_string().unwrap_or_default();
71747041 let has_resp_schema = matches!(unsafe { as_ref(resp_desc) }, Value::Text(s) if !s.is_empty());
71757042 let parsed_body = if has_resp_schema {
7176− let (val, _) = parse_json_value(&body_text);
7177− val
7043+ match serde_json::from_str::<serde_json::Value>(&body_text) {
7044+ Ok(json) => json_to_value(&json),
7045+ Err(_) => alloc(Value::Unit),
7046+ }
71787047 } else {
71797048 alloc(Value::Text(body_text))
71807049 };
@@ -7234,25 +7103,16 @@ fn fetch_build_url(base: &str, path_pattern: &str, payload: *mut Value) -> Strin
72347103
72357104/// Build a JSON body string from a field descriptor and record payload.
72367105fn fetch_build_body(body_desc: &str, payload: *mut Value) -> String {
7237− let mut json = String::from("{");
7238− let mut first = true;
7106+ let mut map = serde_json::Map::new();
72397107 for field_desc in body_desc.split(',') {
72407108 if field_desc.is_empty() {
72417109 continue;
72427110 }
72437111 let (name, _ty) = field_desc.split_once(':').unwrap_or((field_desc, "text"));
72447112 let field_val = knot_record_field(payload, name.as_ptr(), name.len());
7245− if !first {
7246− json.push(',');
7247− }
7248− first = false;
7249− json.push('"');
7250− json.push_str(name);
7251− json.push_str("\":");
7252− json.push_str(&value_to_json(field_val));
7113+ map.insert(name.to_string(), value_to_serde_json(field_val));
72537114 }
7254− json.push('}');
7255− json
7115+ serde_json::to_string(&map).unwrap_or_else(|_| "{}".to_string())
72567116}
72577117
72587118/// Build a query string from a field descriptor and record payload.
@@ -7674,11 +7534,10 @@ fn parse_response_fields(s: &str) -> Vec<(String, String)> {
76747534}
76757535
76767536fn json_escape(s: &str) -> String {
7677− s.replace('\\', "\\\\")
7678− .replace('"', "\\\"")
7679− .replace('\n', "\\n")
7680− .replace('\r', "\\r")
7681− .replace('\t', "\\t")
7537+ // serde_json::to_string produces a quoted string with proper escaping;
7538+ // strip the surrounding quotes for use in manually-built JSON.
7539+ let quoted = serde_json::to_string(s).unwrap_or_else(|_| format!("\"{}\"", s));
7540+ quoted[1..quoted.len() - 1].to_string()
76827541}
76837542
76847543// ── Hash index for equi-join optimization ──────────────────────────