Skip to main content

ferrox_models/grammar/json_schema/
value.rs

1//! An order-preserving JSON value.
2//!
3//! `serde_json::Value` stores objects in a `BTreeMap` unless the crate's
4//! `preserve_order` feature is on, and that feature is a workspace-wide
5//! switch this module is not allowed to flip. Property order is not
6//! cosmetic here: [`super::converter`] emits the *required* properties of
7//! an object in the order `properties` declares them, so the order a
8//! schema was written in is part of the language the grammar accepts.
9//! llama.cpp gets this from `nlohmann::ordered_json`.
10//!
11//! So the converter runs on this type instead, which keeps objects as a
12//! `Vec<(String, JsonValue)>` and therefore preserves document order when
13//! parsed from text with [`JsonValue::parse`]. A `serde_json::Value` can
14//! still be converted in ([`JsonValue::from`]), but it can only carry the
15//! order that `Value` itself had -- see the note on
16//! [`super::json_schema_to_grammar_value`].
17
18use serde::de::{Deserializer, MapAccess, SeqAccess, Visitor};
19use serde::Deserialize;
20use std::fmt;
21use std::fmt::Write as _;
22
23/// A JSON document that remembers the order its object keys came in.
24#[derive(Debug, Clone, PartialEq)]
25pub enum JsonValue {
26    Null,
27    Bool(bool),
28    Number(serde_json::Number),
29    String(String),
30    Array(Vec<JsonValue>),
31    Object(Vec<(String, JsonValue)>),
32}
33
34impl JsonValue {
35    /// Parse JSON text, keeping object keys in document order.
36    pub fn parse(text: &str) -> Result<Self, serde_json::Error> {
37        serde_json::from_str(text)
38    }
39
40    /// The member named `key`, if this is an object that has one. On a
41    /// duplicate key the first wins, matching `nlohmann::json`'s parser.
42    pub fn get(&self, key: &str) -> Option<&JsonValue> {
43        match self {
44            JsonValue::Object(entries) => entries.iter().find(|(k, _)| k == key).map(|(_, v)| v),
45            _ => None,
46        }
47    }
48
49    /// Whether this is an object with a member named `key`.
50    pub fn contains_key(&self, key: &str) -> bool {
51        self.get(key).is_some()
52    }
53
54    pub fn as_object(&self) -> Option<&[(String, JsonValue)]> {
55        match self {
56            JsonValue::Object(entries) => Some(entries.as_slice()),
57            _ => None,
58        }
59    }
60
61    pub fn as_array(&self) -> Option<&[JsonValue]> {
62        match self {
63            JsonValue::Array(items) => Some(items.as_slice()),
64            _ => None,
65        }
66    }
67
68    pub fn as_str(&self) -> Option<&str> {
69        match self {
70            JsonValue::String(s) => Some(s.as_str()),
71            _ => None,
72        }
73    }
74
75    pub fn as_bool(&self) -> Option<bool> {
76        match self {
77            JsonValue::Bool(b) => Some(*b),
78            _ => None,
79        }
80    }
81
82    /// The value as a non-negative integer, for `minItems` and friends.
83    pub fn as_u64(&self) -> Option<u64> {
84        match self {
85            JsonValue::Number(n) => n.as_u64(),
86            _ => None,
87        }
88    }
89
90    /// The JSON type name, for error messages.
91    pub fn kind(&self) -> &'static str {
92        match self {
93            JsonValue::Null => "null",
94            JsonValue::Bool(_) => "boolean",
95            JsonValue::Number(_) => "number",
96            JsonValue::String(_) => "string",
97            JsonValue::Array(_) => "array",
98            JsonValue::Object(_) => "object",
99        }
100    }
101
102    /// `nlohmann::json::dump()`: compact, no spaces. The `const` and
103    /// `enum` rules are literally this text wrapped in GBNF quotes, so it
104    /// has to agree with what a JSON encoder would emit for the same
105    /// value or the grammar forbids the document the schema allows.
106    pub fn dump(&self) -> String {
107        let mut out = String::new();
108        self.dump_into(&mut out);
109        out
110    }
111
112    fn dump_into(&self, out: &mut String) {
113        match self {
114            JsonValue::Null => out.push_str("null"),
115            JsonValue::Bool(true) => out.push_str("true"),
116            JsonValue::Bool(false) => out.push_str("false"),
117            JsonValue::Number(n) => {
118                let _ = write!(out, "{n}");
119            }
120            JsonValue::String(s) => escape_json_string(s, out),
121            JsonValue::Array(items) => {
122                out.push('[');
123                for (i, item) in items.iter().enumerate() {
124                    if i > 0 {
125                        out.push(',');
126                    }
127                    item.dump_into(out);
128                }
129                out.push(']');
130            }
131            JsonValue::Object(entries) => {
132                out.push('{');
133                for (i, (k, v)) in entries.iter().enumerate() {
134                    if i > 0 {
135                        out.push(',');
136                    }
137                    escape_json_string(k, out);
138                    out.push(':');
139                    v.dump_into(out);
140                }
141                out.push('}');
142            }
143        }
144    }
145}
146
147/// JSON string escaping as `nlohmann::json::dump()` does it: the seven
148/// short escapes, `\u00xx` for the remaining C0 controls, everything else
149/// verbatim. `/` is *not* escaped, and neither are non-ASCII codepoints.
150fn escape_json_string(s: &str, out: &mut String) {
151    out.push('"');
152    for c in s.chars() {
153        match c {
154            '"' => out.push_str("\\\""),
155            '\\' => out.push_str("\\\\"),
156            '\u{08}' => out.push_str("\\b"),
157            '\u{0c}' => out.push_str("\\f"),
158            '\n' => out.push_str("\\n"),
159            '\r' => out.push_str("\\r"),
160            '\t' => out.push_str("\\t"),
161            c if (c as u32) < 0x20 => {
162                let _ = write!(out, "\\u{:04x}", c as u32);
163            }
164            c => out.push(c),
165        }
166    }
167    out.push('"');
168}
169
170impl From<&serde_json::Value> for JsonValue {
171    fn from(value: &serde_json::Value) -> Self {
172        match value {
173            serde_json::Value::Null => JsonValue::Null,
174            serde_json::Value::Bool(b) => JsonValue::Bool(*b),
175            serde_json::Value::Number(n) => JsonValue::Number(n.clone()),
176            serde_json::Value::String(s) => JsonValue::String(s.clone()),
177            serde_json::Value::Array(items) => {
178                JsonValue::Array(items.iter().map(JsonValue::from).collect())
179            }
180            serde_json::Value::Object(map) => JsonValue::Object(
181                map.iter()
182                    .map(|(k, v)| (k.clone(), JsonValue::from(v)))
183                    .collect(),
184            ),
185        }
186    }
187}
188
189impl<'de> Deserialize<'de> for JsonValue {
190    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
191        deserializer.deserialize_any(JsonValueVisitor)
192    }
193}
194
195struct JsonValueVisitor;
196
197impl<'de> Visitor<'de> for JsonValueVisitor {
198    type Value = JsonValue;
199
200    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        f.write_str("any JSON value")
202    }
203
204    fn visit_unit<E>(self) -> Result<JsonValue, E> {
205        Ok(JsonValue::Null)
206    }
207
208    fn visit_none<E>(self) -> Result<JsonValue, E> {
209        Ok(JsonValue::Null)
210    }
211
212    fn visit_some<D: Deserializer<'de>>(self, d: D) -> Result<JsonValue, D::Error> {
213        d.deserialize_any(self)
214    }
215
216    fn visit_bool<E>(self, v: bool) -> Result<JsonValue, E> {
217        Ok(JsonValue::Bool(v))
218    }
219
220    fn visit_i64<E>(self, v: i64) -> Result<JsonValue, E> {
221        Ok(JsonValue::Number(v.into()))
222    }
223
224    fn visit_u64<E>(self, v: u64) -> Result<JsonValue, E> {
225        Ok(JsonValue::Number(v.into()))
226    }
227
228    fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<JsonValue, E> {
229        // A non-finite float cannot be written back as JSON; serde_json
230        // maps it to null on the way out, so do the same here rather than
231        // inventing a literal no encoder would produce.
232        Ok(match serde_json::Number::from_f64(v) {
233            Some(n) => JsonValue::Number(n),
234            None => JsonValue::Null,
235        })
236    }
237
238    fn visit_str<E>(self, v: &str) -> Result<JsonValue, E> {
239        Ok(JsonValue::String(v.to_string()))
240    }
241
242    fn visit_string<E>(self, v: String) -> Result<JsonValue, E> {
243        Ok(JsonValue::String(v))
244    }
245
246    fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<JsonValue, A::Error> {
247        let mut items = Vec::new();
248        while let Some(item) = seq.next_element()? {
249            items.push(item);
250        }
251        Ok(JsonValue::Array(items))
252    }
253
254    fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<JsonValue, A::Error> {
255        // `next_entry` yields in document order, which is the whole point
256        // of this type.
257        let mut entries: Vec<(String, JsonValue)> = Vec::new();
258        while let Some((k, v)) = map.next_entry::<String, JsonValue>()? {
259            if !entries.iter().any(|(existing, _)| *existing == k) {
260                entries.push((k, v));
261            }
262        }
263        Ok(JsonValue::Object(entries))
264    }
265}