Skip to main content

serde_toon/
lib.rs

1//! Serde-compatible TOON v3.0 encoder/decoder with optional v1.5 features.
2//!
3//! # Examples
4//!
5//! Quick encode/decode:
6//!
7//! ```rust
8//! use serde::Serialize;
9//! use serde_toon::toon;
10//!
11//! #[derive(Serialize)]
12//! struct User {
13//!     name: String,
14//!     age: u32,
15//! }
16//!
17//! let user = User {
18//!     name: "Ada Lovelace".to_string(),
19//!     age: 37,
20//! };
21//! let toon = toon!(encode: user)?;
22//! let toon_from_json = toon!(encode_json: r#"{"name":"Grace Hopper"}"#)?;
23//! let value = toon!("name: Ada Lovelace")?;
24//! assert_eq!(toon, "name: Ada Lovelace\nage: 37");
25//! assert_eq!(toon_from_json, "name: Grace Hopper");
26//! assert_eq!(value, serde_json::json!({"name": "Ada Lovelace"}));
27//! # Ok::<(), serde_toon::Error>(())
28//! ```
29//!
30//! Encode to TOON:
31//!
32//! ```rust
33//! use serde::{Deserialize, Serialize};
34//! use serde_toon::to_string;
35//!
36//! #[derive(Debug, Serialize, Deserialize, PartialEq)]
37//! struct User {
38//!     name: String,
39//!     age: u32,
40//! }
41//!
42//! let user = User {
43//!     name: "Ada Lovelace".to_string(),
44//!     age: 37,
45//! };
46//!
47//! let toon = to_string(&user)?;
48//! assert_eq!(toon, "name: Ada Lovelace\nage: 37");
49//! # Ok::<(), serde_toon::Error>(())
50//! ```
51//!
52//! Decode back:
53//!
54//! ```rust
55//! use serde::Deserialize;
56//! use serde_toon::from_str;
57//!
58//! #[derive(Debug, Deserialize, PartialEq)]
59//! struct User {
60//!     name: String,
61//!     age: u32,
62//! }
63//!
64//! let toon = "name: Ada Lovelace\nage: 37";
65//! let round_trip: User = from_str(toon)?;
66//! assert_eq!(
67//!     round_trip,
68//!     User {
69//!         name: "Ada Lovelace".to_string(),
70//!         age: 37
71//!     }
72//! );
73//! # Ok::<(), serde_toon::Error>(())
74//! ```
75//!
76//! JSON string round-trip:
77//!
78//! ```rust
79//! use serde_toon::{from_str, to_string_from_json_str};
80//!
81//! let json = r#"{"name":"Grace Hopper","field":"computer science","year":1952}"#;
82//! let toon = to_string_from_json_str(json)?;
83//! assert_eq!(
84//!     toon,
85//!     "name: Grace Hopper\nfield: computer science\nyear: 1952"
86//! );
87//!
88//! let back_to_json = serde_json::to_string(&from_str::<serde_json::Value>(&toon)?)?;
89//! assert_eq!(back_to_json, json);
90//! # Ok::<(), Box<dyn std::error::Error>>(())
91//! ```
92//!
93//! Untyped values:
94//!
95//! ```rust
96//! use serde_toon::Value;
97//!
98//! let value: Value = serde_toon::from_str("name: Margaret Hamilton\nage: 32")?;
99//! assert_eq!(value, serde_json::json!({"name": "Margaret Hamilton", "age": 32}));
100//! # Ok::<(), serde_toon::Error>(())
101//! ```
102//!
103//! Custom options:
104//!
105//! ```rust
106//! use serde_toon::{Delimiter, EncodeOptions, Indent, KeyFolding};
107//!
108//! let opts = EncodeOptions::new()
109//!     .with_indent(Indent::spaces(4))
110//!     .with_delimiter(Delimiter::Pipe)
111//!     .with_key_folding(KeyFolding::Safe)
112//!     .with_flatten_depth(Some(2));
113//! let toon = serde_toon::to_string_with_options(&serde_json::json!({"items": ["a", "b"]}), &opts)?;
114//! assert_eq!(toon, "items[2|]: a|b");
115//! # Ok::<(), serde_toon::Error>(())
116//! ```
117//!
118//! ```rust
119//! use serde_toon::{DecodeOptions, ExpandPaths, Indent};
120//!
121//! let opts = DecodeOptions::new()
122//!     .with_indent(Indent::spaces(4))
123//!     .with_strict(false)
124//!     .with_expand_paths(ExpandPaths::Safe);
125//! let value: serde_json::Value = serde_toon::from_str_with_options("a.b: 1", &opts)?;
126//! assert_eq!(value, serde_json::json!({"a": {"b": 1}}));
127//! # Ok::<(), serde_toon::Error>(())
128//! ```
129//!
130//! # Removed APIs
131//!
132//! ```compile_fail
133//! use serde_toon::tabular;
134//! ```
135
136pub mod arena;
137pub mod canonical;
138pub mod decode;
139pub mod encode;
140pub mod error;
141pub mod num;
142pub mod options;
143pub mod text;
144
145use std::io::{BufRead, Read, Write};
146
147pub use crate::error::{Error, ErrorKind, ErrorStage, Location};
148pub use crate::options::{
149    DecodeOptions, Delimiter, EncodeOptions, ExpandPaths, Indent, KeyFolding,
150};
151pub use canonical::{encode_canonical, CanonicalProfile};
152use serde::de::DeserializeOwned;
153use serde::Serialize;
154pub use serde_json::Value;
155
156pub type Result<T> = std::result::Result<T, Error>;
157
158pub fn to_string<T: Serialize>(value: &T) -> Result<String> {
159    to_string_with_options(value, &EncodeOptions::default())
160}
161
162pub fn to_string_with_options<T: Serialize>(value: &T, options: &EncodeOptions) -> Result<String> {
163    encode::to_string(value, options)
164}
165
166pub fn to_string_into<T: Serialize>(value: &T, out: &mut String) -> Result<()> {
167    to_string_into_with_options(value, &EncodeOptions::default(), out)
168}
169
170pub fn to_string_into_with_options<T: Serialize>(
171    value: &T,
172    options: &EncodeOptions,
173    out: &mut String,
174) -> Result<()> {
175    encode::to_string_into(value, options, out)
176}
177
178pub fn to_string_from_json_str(input: &str) -> Result<String> {
179    to_string_from_json_str_with_options(input, &EncodeOptions::default())
180}
181
182pub fn to_string_from_json_str_with_options(
183    input: &str,
184    options: &EncodeOptions,
185) -> Result<String> {
186    encode::to_string_from_json_str(input, options)
187}
188
189pub fn to_vec<T: Serialize>(value: &T) -> Result<Vec<u8>> {
190    to_vec_with_options(value, &EncodeOptions::default())
191}
192
193pub fn to_vec_with_options<T: Serialize>(value: &T, options: &EncodeOptions) -> Result<Vec<u8>> {
194    encode::to_vec(value, options)
195}
196
197pub fn to_writer<T: Serialize, W: Write>(writer: W, value: &T) -> Result<()> {
198    to_writer_with_options(writer, value, &EncodeOptions::default())
199}
200
201pub fn to_writer_with_options<T: Serialize, W: Write>(
202    writer: W,
203    value: &T,
204    options: &EncodeOptions,
205) -> Result<()> {
206    encode::to_writer(writer, value, options)
207}
208
209pub fn from_str<T: DeserializeOwned>(input: &str) -> Result<T> {
210    from_str_with_options(input, &DecodeOptions::default())
211}
212
213pub fn from_str_with_options<T: DeserializeOwned>(
214    input: &str,
215    options: &DecodeOptions,
216) -> Result<T> {
217    decode::from_str(input, options)
218}
219
220#[cfg(feature = "parallel")]
221pub fn from_str_parallel<T: DeserializeOwned + Send>(input: &str) -> Result<Vec<T>> {
222    from_str_parallel_with_options(input, &DecodeOptions::default())
223}
224
225#[cfg(feature = "parallel")]
226pub fn from_str_parallel_with_options<T: DeserializeOwned + Send>(
227    input: &str,
228    options: &DecodeOptions,
229) -> Result<Vec<T>> {
230    decode::from_str_parallel(input, options)
231}
232
233pub fn from_slice<T: DeserializeOwned>(input: &[u8]) -> Result<T> {
234    from_slice_with_options(input, &DecodeOptions::default())
235}
236
237pub fn from_slice_with_options<T: DeserializeOwned>(
238    input: &[u8],
239    options: &DecodeOptions,
240) -> Result<T> {
241    decode::from_slice(input, options)
242}
243
244/// Decode a value from a reader by buffering the entire input into memory.
245///
246/// `from_reader` reads all bytes from `reader` into a `Vec<u8>` before decoding, so
247/// large or untrusted inputs can exhaust memory. For incremental, bounded-memory
248/// processing, prefer [`from_reader_streaming`] or
249/// [`from_reader_streaming_with_options`]. See [`DecodeOptions`] for alternate
250/// decoding behavior.
251pub fn from_reader<T: DeserializeOwned, R: Read>(reader: R) -> Result<T> {
252    from_reader_with_options(reader, &DecodeOptions::default())
253}
254
255/// Decode a value from a reader with explicit [`DecodeOptions`].
256///
257/// This buffers the entire input into memory, so large or untrusted inputs can
258/// exhaust memory. If you need incremental, bounded-memory processing, prefer
259/// [`from_reader_streaming_with_options`] (or [`from_reader_streaming`] with
260/// defaults).
261pub fn from_reader_with_options<T: DeserializeOwned, R: Read>(
262    reader: R,
263    options: &DecodeOptions,
264) -> Result<T> {
265    decode::from_reader(reader, options)
266}
267
268pub fn from_reader_streaming<T: DeserializeOwned, R: BufRead>(reader: R) -> Result<T> {
269    from_reader_streaming_with_options(reader, &DecodeOptions::default())
270}
271
272pub fn from_reader_streaming_with_options<T: DeserializeOwned, R: BufRead>(
273    reader: R,
274    options: &DecodeOptions,
275) -> Result<T> {
276    decode::from_reader_streaming(reader, options)
277}
278
279pub fn decode_to_value(input: &str) -> Result<Value> {
280    decode_to_value_with_options(input, &DecodeOptions::default())
281}
282
283pub fn decode_to_value_with_options(input: &str, options: &DecodeOptions) -> Result<Value> {
284    let value = decode::from_str_value(input, options)?;
285    Ok(canonicalize_numbers(value))
286}
287
288pub fn decode_to_value_auto<S: AsRef<str>>(input: S) -> Result<Value> {
289    decode_to_value_auto_with_options(input, &DecodeOptions::default())
290}
291
292pub fn decode_to_value_auto_with_options<S: AsRef<str>>(
293    input: S,
294    options: &DecodeOptions,
295) -> Result<Value> {
296    let input = input.as_ref();
297    match detect_auto_kind(input) {
298        AutoDetectKind::Json | AutoDetectKind::Uncertain => {
299            match serde_json::from_str::<Value>(input) {
300                Ok(value) => Ok(canonicalize_numbers(value)),
301                Err(json_err) => match decode_to_value_with_options(input, options) {
302                    Ok(value) => Ok(value),
303                    Err(toon_err) => Err(auto_detect_error(json_err, toon_err)),
304                },
305            }
306        }
307        AutoDetectKind::Toon => match decode_to_value_with_options(input, options) {
308            Ok(value) => Ok(value),
309            Err(toon_err) => match serde_json::from_str::<Value>(input) {
310                Ok(value) => Ok(canonicalize_numbers(value)),
311                Err(json_err) => Err(auto_detect_error(json_err, toon_err)),
312            },
313        },
314    }
315}
316
317#[derive(Debug, Clone, Copy, PartialEq, Eq)]
318enum AutoDetectKind {
319    Json,
320    Toon,
321    Uncertain,
322}
323
324fn detect_auto_kind(input: &str) -> AutoDetectKind {
325    let first_non_ws = input.chars().find(|ch| !ch.is_whitespace());
326    let toon_key_pos = find_toon_key_token(input);
327    if let Some(ch) = first_non_ws {
328        if ch == '{' || ch == '[' {
329            if let Some(json_key_pos) = find_json_quoted_key(input) {
330                if toon_key_pos.is_none() || Some(json_key_pos) < toon_key_pos {
331                    return AutoDetectKind::Json;
332                }
333            }
334        }
335        if ch.is_ascii_alphabetic() || ch == '_' {
336            return AutoDetectKind::Toon;
337        }
338    } else {
339        return AutoDetectKind::Uncertain;
340    }
341    if toon_key_pos.is_some() {
342        return AutoDetectKind::Toon;
343    }
344    AutoDetectKind::Uncertain
345}
346
347fn find_json_quoted_key(input: &str) -> Option<usize> {
348    let bytes = input.as_bytes();
349    let mut idx = 0;
350    while idx < bytes.len() {
351        if bytes[idx] == b'"' {
352            let start = idx;
353            idx += 1;
354            let mut escape = false;
355            while idx < bytes.len() {
356                let byte = bytes[idx];
357                if escape {
358                    escape = false;
359                    idx += 1;
360                    continue;
361                }
362                if byte == b'\\' {
363                    escape = true;
364                    idx += 1;
365                    continue;
366                }
367                if byte == b'"' {
368                    idx += 1;
369                    break;
370                }
371                idx += 1;
372            }
373            if idx >= bytes.len() {
374                break;
375            }
376            let mut lookahead = idx;
377            while lookahead < bytes.len()
378                && matches!(bytes[lookahead], b' ' | b'\t' | b'\r' | b'\n')
379            {
380                lookahead += 1;
381            }
382            if lookahead < bytes.len() && bytes[lookahead] == b':' {
383                return Some(start);
384            }
385            idx = lookahead;
386            continue;
387        }
388        idx += 1;
389    }
390    None
391}
392
393fn find_toon_key_token(input: &str) -> Option<usize> {
394    let mut offset = 0;
395    for line in input.split_terminator('\n') {
396        let bytes = line.as_bytes();
397        let mut idx = 0;
398        while idx < bytes.len() && matches!(bytes[idx], b' ' | b'\t' | b'\r') {
399            idx += 1;
400        }
401        if idx < bytes.len() && is_ident_start_byte(bytes[idx]) {
402            let start = offset + idx;
403            idx += 1;
404            while idx < bytes.len() && is_ident_continue_byte(bytes[idx]) {
405                idx += 1;
406            }
407            while idx < bytes.len() && matches!(bytes[idx], b' ' | b'\t') {
408                idx += 1;
409            }
410            if idx < bytes.len() && bytes[idx] == b':' {
411                return Some(start);
412            }
413        }
414        offset += line.len() + 1;
415    }
416    None
417}
418
419fn is_ident_start_byte(byte: u8) -> bool {
420    byte.is_ascii_alphabetic() || byte == b'_'
421}
422
423fn is_ident_continue_byte(byte: u8) -> bool {
424    byte.is_ascii_alphanumeric() || byte == b'_' || byte == b'.'
425}
426
427fn auto_detect_error(json_err: serde_json::Error, toon_err: Error) -> Error {
428    Error::decode(format!(
429        "input is neither valid JSON nor TOON: json error: {json_err}; toon error: {toon_err}"
430    ))
431}
432
433fn canonicalize_numbers(value: Value) -> Value {
434    match value {
435        Value::Array(items) => Value::Array(items.into_iter().map(canonicalize_numbers).collect()),
436        Value::Object(map) => {
437            let mapped = map
438                .into_iter()
439                .map(|(key, value)| (key, canonicalize_numbers(value)))
440                .collect();
441            Value::Object(mapped)
442        }
443        Value::Number(number) => {
444            let canonical = crate::num::number::format_json_number(&number);
445            match serde_json::from_str::<Value>(&canonical) {
446                Ok(Value::Number(number)) => Value::Number(number),
447                _ => Value::Number(number),
448            }
449        }
450        other => other,
451    }
452}
453
454pub fn validate_str(input: &str) -> Result<()> {
455    validate_str_with_options(input, &DecodeOptions::default())
456}
457
458pub fn validate_str_with_options(input: &str, options: &DecodeOptions) -> Result<()> {
459    decode::validate_str(input, options)
460}
461
462#[macro_export]
463/// Parse a JSON or TOON string into a `serde_json::Value`, or encode values into TOON.
464///
465/// This macro calls `decode_to_value_auto`, returning a `Result<Value>`.
466///
467/// # Examples
468///
469/// ```rust
470/// use serde_toon::toon;
471///
472/// let value = toon!("name: \"Snoopy\"\nage: 5")?;
473/// assert_eq!(value, serde_json::json!({"name": "Snoopy", "age": 5}));
474/// # Ok::<(), serde_toon::Error>(())
475/// ```
476///
477/// ```rust
478/// use serde_toon::toon;
479///
480/// let toon = toon!(encode_json: r#"{"name":"Grace Hopper"}"#)?;
481/// assert_eq!(toon, "name: Grace Hopper");
482/// # Ok::<(), serde_toon::Error>(())
483/// ```
484macro_rules! toon {
485    (encode: $input:expr) => {
486        $crate::to_string(&$input)
487    };
488    (encode: $input:expr, $options:expr) => {
489        $crate::to_string_with_options(&$input, $options)
490    };
491    (encode_json: $input:expr) => {
492        $crate::to_string_from_json_str($input)
493    };
494    (encode_json: $input:expr, $options:expr) => {
495        $crate::to_string_from_json_str_with_options($input, $options)
496    };
497    ($input:expr) => {
498        $crate::decode_to_value_auto($input)
499    };
500    ($input:expr, $options:expr) => {
501        $crate::decode_to_value_auto_with_options($input, $options)
502    };
503}