Skip to main content

rig_core/
json_utils.rs

1use serde::de::{self, Deserializer, MapAccess, SeqAccess, Visitor};
2use serde::{Deserialize, Serialize, Serializer};
3use std::collections::HashMap;
4use std::convert::Infallible;
5use std::fmt;
6use std::marker::PhantomData;
7use std::str::FromStr;
8
9/// `skip_serializing_if` helper: serde requires a `fn(&bool) -> bool`, so the
10/// trivially-copy lint does not apply here.
11#[allow(clippy::trivially_copy_pass_by_ref)]
12pub(crate) fn is_false(value: &bool) -> bool {
13    !value
14}
15
16/// Serializes a `HashMap` in lexicographic key order, propagating serializer errors.
17/// Stable ordering avoids randomized map iteration changing request bytes.
18pub fn serialize_map_sorted<S, V>(
19    map: &HashMap<String, V>,
20    serializer: S,
21) -> Result<S::Ok, S::Error>
22where
23    S: Serializer,
24    V: Serialize,
25{
26    let mut entries: Vec<_> = map.iter().collect();
27    entries.sort_by_key(|(key, _)| *key);
28    serializer.collect_map(entries)
29}
30
31/// [`serialize_map_sorted`] for an optional map.
32///
33/// Pairs with `#[serde(skip_serializing_if = "Option::is_none")]`: serde still
34/// routes `Some` through this function, and the `None` arm only runs for a field
35/// that is serialized unconditionally.
36pub fn serialize_optional_map_sorted<S, V>(
37    map: &Option<HashMap<String, V>>,
38    serializer: S,
39) -> Result<S::Ok, S::Error>
40where
41    S: Serializer,
42    V: Serialize,
43{
44    match map {
45        Some(map) => serialize_map_sorted(map, serializer),
46        None => serializer.serialize_none(),
47    }
48}
49
50/// Renders compact JSON with lexicographically sorted object keys at every depth,
51/// independent of serde_json's `preserve_order` feature. Array order is retained.
52pub fn to_canonical_string(value: &serde_json::Value) -> String {
53    fn sorted(value: &serde_json::Value) -> serde_json::Value {
54        match value {
55            serde_json::Value::Object(map) => {
56                let mut entries: Vec<_> = map.iter().collect();
57                entries.sort_by_key(|(key, _)| *key);
58                let mut out = serde_json::Map::new();
59                for (key, value) in entries {
60                    out.insert(key.clone(), sorted(value));
61                }
62                serde_json::Value::Object(out)
63            }
64            serde_json::Value::Array(items) => {
65                serde_json::Value::Array(items.iter().map(sorted).collect())
66            }
67            serde_json::Value::Null
68            | serde_json::Value::Bool(_)
69            | serde_json::Value::Number(_)
70            | serde_json::Value::String(_) => value.clone(),
71        }
72    }
73    sorted(value).to_string()
74}
75
76pub fn merge(a: serde_json::Value, b: serde_json::Value) -> serde_json::Value {
77    match (a, b) {
78        (serde_json::Value::Object(mut a_map), serde_json::Value::Object(b_map)) => {
79            b_map.into_iter().for_each(|(key, value)| {
80                a_map.insert(key, value);
81            });
82            serde_json::Value::Object(a_map)
83        }
84        (a, _) => a,
85    }
86}
87
88/// Applies a request builder's `additional_params` call: `None` clears, and a
89/// value merges over the parameters earlier calls set. Merging combines JSON
90/// objects key by key; earlier parameters that are not an object are kept.
91pub(crate) fn merge_params(
92    existing: Option<serde_json::Value>,
93    params: Option<serde_json::Value>,
94) -> Option<serde_json::Value> {
95    Some(match (existing, params?) {
96        (Some(existing), params) => merge(existing, params),
97        (None, params) => params,
98    })
99}
100
101// Callers require the image or audio feature.
102#[cfg_attr(not(any(feature = "image", feature = "audio")), allow(dead_code))]
103pub fn merge_inplace(a: &mut serde_json::Value, b: serde_json::Value) {
104    if let (serde_json::Value::Object(a_map), serde_json::Value::Object(b_map)) = (a, b) {
105        b_map.into_iter().for_each(|(key, value)| {
106            a_map.insert(key, value);
107        });
108    }
109}
110
111/// Normalize a provider-wire field that may contain encoded JSON in a string.
112///
113/// This deliberately unwraps [`serde_json::Value::String`] and is only for
114/// provider decoding, before a value enters Rig's canonical message model.
115pub fn value_to_json_string(value: &serde_json::Value) -> String {
116    match value {
117        serde_json::Value::String(s) => s.clone(),
118        other => other.to_string(),
119    }
120}
121
122/// Serializes compact JSON, retaining quotes and escaping for string values.
123pub fn serialize_json_value(value: &serde_json::Value) -> String {
124    value.to_string()
125}
126
127/// Deserializes strings verbatim and other non-null values as compact JSON text.
128/// Null becomes `None`; fields that may be absent need a serde default.
129/// Object key order depends on serde_json's enabled features.
130pub fn deserialize_json_string_or_value<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
131where
132    D: Deserializer<'de>,
133{
134    let value = Option::<serde_json::Value>::deserialize(deserializer)?;
135    Ok(match value {
136        None | Some(serde_json::Value::Null) => None,
137        Some(v) => Some(value_to_json_string(&v)),
138    })
139}
140
141/// Parse tool arguments from a streamed string payload.
142/// Some providers emit an empty string for parameterless tool calls; normalize that to `{}`.
143pub fn parse_tool_arguments(arguments: &str) -> serde_json::Result<serde_json::Value> {
144    if arguments.trim().is_empty() {
145        return Ok(serde_json::Value::Object(serde_json::Map::new()));
146    }
147
148    serde_json::from_str(arguments)
149}
150
151/// The longest object a cut-off JSON text `text` still states: open strings,
152/// arrays and objects are closed and a dangling key or separator dropped, as
153/// pi's streaming JSON parse does. `None` when no prefix states an object.
154pub fn parse_partial_object(text: &str) -> Option<serde_json::Map<String, serde_json::Value>> {
155    // Bounds the work on a large cut-off input: the last valid cut is
156    // almost always near its end.
157    const MAX_ATTEMPTS: usize = 256;
158    let mut stack = Vec::new();
159    let mut in_string = false;
160    let mut escaped = false;
161    // Prefix lengths a value just ended at, with the containers open there.
162    let mut cuts: Vec<(usize, Vec<u8>, bool)> = Vec::new();
163    let bytes = text.as_bytes();
164    for (at, byte) in bytes.iter().enumerate() {
165        if in_string {
166            match (escaped, *byte) {
167                (true, _) => escaped = false,
168                (false, b'\\') => escaped = true,
169                (false, b'"') => {
170                    in_string = false;
171                    cuts.push((at + 1, stack.clone(), false));
172                }
173                (false, _) => {}
174            }
175            continue;
176        }
177        match byte {
178            b'"' => in_string = true,
179            b'{' | b'[' => {
180                stack.push(*byte);
181                cuts.push((at + 1, stack.clone(), false));
182            }
183            b'}' | b']' => {
184                stack.pop();
185                cuts.push((at + 1, stack.clone(), false));
186            }
187            b if b.is_ascii_alphanumeric() || *b == b'.' || *b == b'-' || *b == b'+' => {
188                let next = bytes.get(at + 1).copied();
189                if !next.is_some_and(|n| {
190                    n.is_ascii_alphanumeric() || n == b'.' || n == b'-' || n == b'+'
191                }) {
192                    cuts.push((at + 1, stack.clone(), false));
193                }
194            }
195            _ => {}
196        }
197    }
198    if in_string {
199        let at = text.len() - usize::from(escaped);
200        cuts.push((at, stack.clone(), true));
201    }
202    cuts.iter()
203        .rev()
204        .take(MAX_ATTEMPTS)
205        .find_map(|(at, open, quote)| {
206            let mut candidate = text.get(..*at)?.to_owned();
207            if *quote {
208                candidate.push('"');
209            }
210            for container in open.iter().rev() {
211                candidate.push(if *container == b'{' { '}' } else { ']' });
212            }
213            match serde_json::from_str(&candidate) {
214                Ok(serde_json::Value::Object(object)) => Some(object),
215                _ => None,
216            }
217        })
218}
219
220/// The best object a tool call's argument text states so far, for showing a
221/// call while it streams. It never fails: complete JSON parses as is, text
222/// with raw control characters or invalid escapes is repaired first, a
223/// cut-off prefix keeps the longest object it states
224/// ([`parse_partial_object`]), and anything else is an empty object, as is
225/// a top level that is not an object.
226///
227/// It is for display only. A call's arguments are what its end states, and
228/// only those are validated and executed.
229///
230/// ```
231/// use rig_core::streaming::parse_partial_arguments;
232///
233/// let partial = parse_partial_arguments(r#"{"path": "notes.md", "content": "Hel"#);
234/// assert_eq!(partial["path"], "notes.md");
235/// assert_eq!(partial["content"], "Hel");
236/// ```
237pub fn parse_partial_arguments(text: &str) -> serde_json::Map<String, serde_json::Value> {
238    if text.trim().is_empty() {
239        return serde_json::Map::new();
240    }
241    let repaired = repair_json(text);
242    let complete = serde_json::from_str(text)
243        .ok()
244        .or_else(|| serde_json::from_str(&repaired).ok());
245    if let Some(value) = complete {
246        return match value {
247            serde_json::Value::Object(object) => object,
248            _ => serde_json::Map::new(),
249        };
250    }
251    let cut = without_partial_escape(&repaired);
252    parse_partial_object(cut)
253        .or_else(|| parse_partial_object(text))
254        .unwrap_or_default()
255}
256
257/// `text` with raw control characters inside strings escaped and each
258/// backslash that starts no valid escape doubled, as models sometimes write
259/// them.
260fn repair_json(text: &str) -> String {
261    let mut repaired = String::with_capacity(text.len());
262    let mut in_string = false;
263    let mut chars = text.chars().peekable();
264    while let Some(c) = chars.next() {
265        if !in_string {
266            in_string = c == '"';
267            repaired.push(c);
268            continue;
269        }
270        match c {
271            '"' => {
272                in_string = false;
273                repaired.push(c);
274            }
275            '\\' => match chars.peek() {
276                Some('"' | '\\' | '/' | 'b' | 'f' | 'n' | 'r' | 't' | 'u') => {
277                    repaired.push(c);
278                    if let Some(next) = chars.next() {
279                        repaired.push(next);
280                    }
281                }
282                // A trailing backslash may begin an escape the next fragment ends.
283                None => repaired.push(c),
284                Some(_) => repaired.push_str("\\\\"),
285            },
286            '\n' => repaired.push_str("\\n"),
287            '\r' => repaired.push_str("\\r"),
288            '\t' => repaired.push_str("\\t"),
289            c if c.is_control() => {
290                use std::fmt::Write as _;
291                // Writing to a `String` cannot fail.
292                let _ = write!(repaired, "\\u{:04x}", u32::from(c));
293            }
294            c => repaired.push(c),
295        }
296    }
297    repaired
298}
299
300/// `text` without a `\u` escape its end cuts short, which would otherwise
301/// cost the whole string it ends.
302fn without_partial_escape(text: &str) -> &str {
303    let bytes = text.as_bytes();
304    let tail = bytes.len().saturating_sub(5);
305    let Some(at) = (tail..bytes.len())
306        .rev()
307        .find(|at| bytes.get(*at..*at + 2) == Some(b"\\u"))
308    else {
309        return text;
310    };
311    let digits = bytes.get(at + 2..).unwrap_or_default();
312    // A preceding backslash run of odd length escapes this backslash itself.
313    let escaping = bytes
314        .get(..at)
315        .unwrap_or_default()
316        .iter()
317        .rev()
318        .take_while(|byte| **byte == b'\\')
319        .count();
320    if digits.len() < 4 && digits.iter().all(u8::is_ascii_hexdigit) && escaping % 2 == 0 {
321        text.get(..at).unwrap_or(text)
322    } else {
323        text
324    }
325}
326
327/// Serde adapters for JSON encoded inside strings. Empty or whitespace-only
328/// strings deserialize to empty objects.
329pub mod stringified_json {
330    use super::parse_tool_arguments;
331    use serde::{self, Deserialize, Deserializer, Serializer};
332
333    pub fn serialize<S>(value: &serde_json::Value, serializer: S) -> Result<S::Ok, S::Error>
334    where
335        S: Serializer,
336    {
337        let s = value.to_string();
338        serializer.serialize_str(&s)
339    }
340
341    pub fn deserialize<'de, D>(deserializer: D) -> Result<serde_json::Value, D::Error>
342    where
343        D: Deserializer<'de>,
344    {
345        let s = String::deserialize(deserializer)?;
346        if s.trim().is_empty() {
347            return Ok(serde_json::Value::Object(serde_json::Map::new()));
348        }
349        serde_json::from_str(&s).map_err(serde::de::Error::custom)
350    }
351
352    /// Parses string contents as JSON and passes other values through unchanged.
353    /// Empty or whitespace-only strings become empty objects.
354    pub fn deserialize_maybe_stringified<'de, D>(
355        deserializer: D,
356    ) -> Result<serde_json::Value, D::Error>
357    where
358        D: Deserializer<'de>,
359    {
360        match serde_json::Value::deserialize(deserializer)? {
361            serde_json::Value::String(s) => {
362                parse_tool_arguments(&s).map_err(serde::de::Error::custom)
363            }
364            other => Ok(other),
365        }
366    }
367}
368
369/// Deserializes a string or object as one item, a sequence as multiple items,
370/// and null as an empty list. Strings use [`FromStr`]; objects use [`Deserialize`].
371pub fn string_or_vec<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
372where
373    T: Deserialize<'de> + FromStr<Err = Infallible>,
374    D: Deserializer<'de>,
375{
376    struct StringOrVec<T>(PhantomData<fn() -> T>);
377
378    impl<'de, T> Visitor<'de> for StringOrVec<T>
379    where
380        T: Deserialize<'de> + FromStr<Err = Infallible>,
381    {
382        type Value = Vec<T>;
383
384        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
385            formatter.write_str("a string, sequence, or null")
386        }
387
388        fn visit_str<E>(self, value: &str) -> Result<Vec<T>, E>
389        where
390            E: de::Error,
391        {
392            let item = FromStr::from_str(value).map_err(de::Error::custom)?;
393            Ok(vec![item])
394        }
395
396        fn visit_seq<A>(self, seq: A) -> Result<Vec<T>, A::Error>
397        where
398            A: SeqAccess<'de>,
399        {
400            Deserialize::deserialize(de::value::SeqAccessDeserializer::new(seq))
401        }
402
403        fn visit_map<M>(self, map: M) -> Result<Vec<T>, M::Error>
404        where
405            M: MapAccess<'de>,
406        {
407            let item = Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))?;
408            Ok(vec![item])
409        }
410
411        fn visit_none<E>(self) -> Result<Vec<T>, E>
412        where
413            E: de::Error,
414        {
415            Ok(vec![])
416        }
417
418        fn visit_unit<E>(self) -> Result<Vec<T>, E>
419        where
420            E: de::Error,
421        {
422            Ok(vec![])
423        }
424    }
425
426    deserializer.deserialize_any(StringOrVec(PhantomData))
427}
428
429/// Deserializes `T`, mapping an explicit `null` to `T::default()`.
430///
431/// Driven through `deserialize_option` rather than `deserialize_any`; the two
432/// agree only for self-describing formats, which is all rig decodes (JSON).
433pub fn null_or_default<'de, T, D>(deserializer: D) -> Result<T, D::Error>
434where
435    T: Deserialize<'de> + Default,
436    D: Deserializer<'de>,
437{
438    Ok(Option::<T>::deserialize(deserializer)?.unwrap_or_default())
439}
440
441#[cfg(test)]
442mod tests;
443
444/// Lenient reads of a provider reply: each accessor gives `None`, or an
445/// empty slice, for a field that is missing or of another type, and never
446/// fails. Decoders read replies through it, so a field no block or finish
447/// needs can never fail a reply.
448///
449/// ```
450/// use rig_core::json_utils::Lenient;
451/// use serde_json::json;
452///
453/// let reply = json!({"id": "r1", "usage": {"input_tokens": "7"}, "output": null});
454/// assert_eq!(reply.str("id"), Some("r1"));
455/// assert_eq!(reply.at("/usage/input_tokens").and_then(Lenient::as_u64_lenient), Some(7));
456/// assert!(reply.arr("output").is_empty());
457/// ```
458pub trait Lenient {
459    /// The string at `key`.
460    fn str(&self, key: &str) -> Option<&str>;
461    /// The unsigned integer at `key`, from a number or a numeric string.
462    fn u64(&self, key: &str) -> Option<u64>;
463    /// The integer at `key`, from a number or a numeric string.
464    fn i64(&self, key: &str) -> Option<i64>;
465    /// The number at `key`, from a number or a numeric string.
466    fn f64(&self, key: &str) -> Option<f64>;
467    /// The boolean at `key`.
468    fn bool(&self, key: &str) -> Option<bool>;
469    /// The object at `key`.
470    fn obj(&self, key: &str) -> Option<&serde_json::Map<String, serde_json::Value>>;
471    /// The array at `key`, empty when absent or of another type.
472    fn arr(&self, key: &str) -> &[serde_json::Value];
473    /// The value at a JSON pointer, `None` for `null`.
474    fn at(&self, pointer: &str) -> Option<&serde_json::Value>;
475    /// This value as an unsigned integer, from a number or a numeric string.
476    fn as_u64_lenient(&self) -> Option<u64>;
477}
478
479impl Lenient for serde_json::Value {
480    fn str(&self, key: &str) -> Option<&str> {
481        self.get(key)?.as_str()
482    }
483
484    fn u64(&self, key: &str) -> Option<u64> {
485        self.get(key)?.as_u64_lenient()
486    }
487
488    fn i64(&self, key: &str) -> Option<i64> {
489        match self.get(key)? {
490            serde_json::Value::Number(number) => number.as_i64(),
491            serde_json::Value::String(text) => text.trim().parse().ok(),
492            _ => None,
493        }
494    }
495
496    fn f64(&self, key: &str) -> Option<f64> {
497        match self.get(key)? {
498            serde_json::Value::Number(number) => number.as_f64(),
499            serde_json::Value::String(text) => text.trim().parse().ok(),
500            _ => None,
501        }
502    }
503
504    fn bool(&self, key: &str) -> Option<bool> {
505        self.get(key)?.as_bool()
506    }
507
508    fn obj(&self, key: &str) -> Option<&serde_json::Map<String, serde_json::Value>> {
509        self.get(key)?.as_object()
510    }
511
512    fn arr(&self, key: &str) -> &[serde_json::Value] {
513        self.get(key)
514            .and_then(serde_json::Value::as_array)
515            .map_or(&[], Vec::as_slice)
516    }
517
518    fn at(&self, pointer: &str) -> Option<&serde_json::Value> {
519        self.pointer(pointer).filter(|value| !value.is_null())
520    }
521
522    fn as_u64_lenient(&self) -> Option<u64> {
523        match self {
524            serde_json::Value::Number(number) => number.as_u64().or_else(|| {
525                number
526                    .as_f64()
527                    .filter(|float| *float >= 0.0 && float.fract() == 0.0)
528                    .map(|float| float as u64)
529            }),
530            serde_json::Value::String(text) => text.trim().parse().ok(),
531            _ => None,
532        }
533    }
534}