use crate::error::{Result, SqzError};
const TOON_PREFIX: &str = "TOON:";
pub struct ToonEncoder;
impl ToonEncoder {
pub fn encode(&self, json: &serde_json::Value) -> Result<String> {
let mut buf = String::with_capacity(128);
buf.push_str(TOON_PREFIX);
encode_value(json, &mut buf);
Ok(buf)
}
pub fn decode(&self, encoded: &str) -> Result<serde_json::Value> {
let body = encoded
.strip_prefix(TOON_PREFIX)
.ok_or_else(|| SqzError::Other("not a TOON string: missing prefix".into()))?;
let mut parser = Parser::new(body);
let value = parser
.parse_value()
.map_err(|e| SqzError::Other(format!("TOON decode error: {e}")))?;
parser
.expect_eof()
.map_err(|e| SqzError::Other(format!("TOON decode error: {e}")))?;
Ok(value)
}
pub fn is_json(input: &str) -> bool {
let trimmed = input.trim();
if trimmed.is_empty() {
return false;
}
serde_json::from_str::<serde_json::Value>(trimmed).is_ok()
}
}
fn encode_value(v: &serde_json::Value, buf: &mut String) {
encode_value_in(v, buf, true);
}
fn encode_value_in(v: &serde_json::Value, buf: &mut String, tables: bool) {
match v {
serde_json::Value::Null => buf.push_str("null"),
serde_json::Value::Bool(b) => buf.push_str(if *b { "true" } else { "false" }),
serde_json::Value::Number(n) => {
buf.push_str(&serde_json::to_string(&serde_json::Value::Number(n.clone()))
.unwrap_or_else(|_| n.to_string()));
}
serde_json::Value::String(s) => encode_string(s, buf),
serde_json::Value::Array(arr) => {
if tables && encode_table(arr, buf) {
return;
}
buf.push('[');
for (i, item) in arr.iter().enumerate() {
if i > 0 {
buf.push(',');
}
encode_value_in(item, buf, tables);
}
buf.push(']');
}
serde_json::Value::Object(map) => {
buf.push('{');
for (i, (k, val)) in map.iter().enumerate() {
if i > 0 {
buf.push(',');
}
encode_key(k, buf);
buf.push(':');
encode_value_in(val, buf, tables);
}
buf.push('}');
}
}
}
fn encode_key(k: &str, buf: &mut String) {
if is_simple_key(k) {
buf.push_str(k);
} else {
encode_string(k, buf);
}
}
fn encode_table(arr: &[serde_json::Value], buf: &mut String) -> bool {
if arr.len() < 2 {
return false;
}
let mut keys: Vec<&str> = Vec::new();
let mut seen = std::collections::HashSet::new();
let mut filled = 0usize;
for item in arr {
let serde_json::Value::Object(map) = item else {
return false;
};
filled += map.len();
for k in map.keys() {
if seen.insert(k.as_str()) {
keys.push(k);
}
}
}
if keys.is_empty() || filled * 2 < arr.len() * keys.len() {
return false;
}
let mut table = String::from("[#");
for (i, k) in keys.iter().enumerate() {
if i > 0 {
table.push(',');
}
encode_key(k, &mut table);
}
for item in arr {
table.push('\n');
for (i, k) in keys.iter().enumerate() {
if i > 0 {
table.push(',');
}
if let Some(val) = item.get(*k) {
encode_value_in(val, &mut table, false);
}
}
}
table.push(']');
let mut plain = String::from("[");
for (i, item) in arr.iter().enumerate() {
if i > 0 {
plain.push(',');
}
encode_value_in(item, &mut plain, false);
}
plain.push(']');
if table.len() < plain.len() {
buf.push_str(&table);
true
} else {
false
}
}
fn encode_string(s: &str, buf: &mut String) {
buf.push('"');
for ch in s.chars() {
match ch {
'"' => buf.push_str("\\\""),
'\\' => buf.push_str("\\\\"),
'\n' => buf.push_str("\\n"),
'\r' => buf.push_str("\\r"),
'\t' => buf.push_str("\\t"),
c if c.is_control() || c == '\u{2028}' || c == '\u{2029}' => {
buf.push_str(&format!("\\u{:04x}", c as u32));
}
c => buf.push(c),
}
}
buf.push('"');
}
fn is_simple_key(k: &str) -> bool {
if k.is_empty() {
return false;
}
let mut chars = k.chars();
let first = chars.next().unwrap();
if !first.is_ascii_alphabetic() && first != '_' {
return false;
}
if !chars.all(|c| c.is_ascii_alphanumeric() || c == '_') {
return false;
}
!matches!(k, "true" | "false" | "null")
}
struct Parser<'a> {
src: &'a [u8],
pos: usize,
}
impl<'a> Parser<'a> {
fn new(s: &'a str) -> Self {
Self {
src: s.as_bytes(),
pos: 0,
}
}
fn peek(&self) -> Option<u8> {
self.src.get(self.pos).copied()
}
fn advance(&mut self) -> Option<u8> {
let b = self.src.get(self.pos).copied();
if b.is_some() {
self.pos += 1;
}
b
}
fn expect_byte(&mut self, expected: u8) -> std::result::Result<(), String> {
match self.advance() {
Some(b) if b == expected => Ok(()),
Some(b) => Err(format!(
"expected '{}' got '{}' at pos {}",
expected as char, b as char, self.pos - 1
)),
None => Err(format!("unexpected EOF, expected '{}'", expected as char)),
}
}
fn expect_eof(&self) -> std::result::Result<(), String> {
if self.pos == self.src.len() {
Ok(())
} else {
Err(format!(
"trailing data at pos {}: {:?}",
self.pos,
&self.src[self.pos..]
))
}
}
fn parse_value(&mut self) -> std::result::Result<serde_json::Value, String> {
match self.peek() {
Some(b'{') => self.parse_object(),
Some(b'[') => self.parse_array(),
Some(b'"') => Ok(serde_json::Value::String(self.parse_string()?)),
Some(b't') => {
self.expect_literal(b"true")?;
Ok(serde_json::Value::Bool(true))
}
Some(b'f') => {
self.expect_literal(b"false")?;
Ok(serde_json::Value::Bool(false))
}
Some(b'n') => {
self.expect_literal(b"null")?;
Ok(serde_json::Value::Null)
}
Some(b'-') | Some(b'0'..=b'9') => self.parse_number(),
Some(b) => Err(format!("unexpected byte '{}' at pos {}", b as char, self.pos)),
None => Err("unexpected EOF".into()),
}
}
fn expect_literal(&mut self, lit: &[u8]) -> std::result::Result<(), String> {
for &expected in lit {
match self.advance() {
Some(b) if b == expected => {}
Some(b) => {
return Err(format!(
"expected '{}' got '{}' at pos {}",
expected as char,
b as char,
self.pos - 1
))
}
None => return Err("unexpected EOF in literal".into()),
}
}
Ok(())
}
fn parse_object(&mut self) -> std::result::Result<serde_json::Value, String> {
self.expect_byte(b'{')?;
let mut map = serde_json::Map::new();
if self.peek() == Some(b'}') {
self.advance();
return Ok(serde_json::Value::Object(map));
}
loop {
let key = self.parse_key()?;
self.expect_byte(b':')?;
let val = self.parse_value()?;
map.insert(key, val);
match self.peek() {
Some(b',') => {
self.advance();
}
Some(b'}') => {
self.advance();
break;
}
Some(b) => {
return Err(format!(
"expected ',' or '}}' got '{}' at pos {}",
b as char, self.pos
))
}
None => return Err("unexpected EOF in object".into()),
}
}
Ok(serde_json::Value::Object(map))
}
fn parse_key(&mut self) -> std::result::Result<String, String> {
match self.peek() {
Some(b'"') => self.parse_string(),
Some(b) if (b as char).is_ascii_alphabetic() || b == b'_' => {
self.parse_bare_key()
}
Some(b) => Err(format!(
"expected key at pos {}, got '{}'",
self.pos,
b as char
)),
None => Err("unexpected EOF expecting key".into()),
}
}
fn parse_bare_key(&mut self) -> std::result::Result<String, String> {
let start = self.pos;
while let Some(b) = self.peek() {
if (b as char).is_ascii_alphanumeric() || b == b'_' {
self.advance();
} else {
break;
}
}
let key = std::str::from_utf8(&self.src[start..self.pos])
.map_err(|e| e.to_string())?
.to_owned();
Ok(key)
}
fn parse_array(&mut self) -> std::result::Result<serde_json::Value, String> {
self.expect_byte(b'[')?;
if self.peek() == Some(b'#') {
return self.parse_table();
}
let mut arr = Vec::new();
if self.peek() == Some(b']') {
self.advance();
return Ok(serde_json::Value::Array(arr));
}
loop {
arr.push(self.parse_value()?);
match self.peek() {
Some(b',') => {
self.advance();
}
Some(b']') => {
self.advance();
break;
}
Some(b) => {
return Err(format!(
"expected ',' or ']' got '{}' at pos {}",
b as char, self.pos
))
}
None => return Err("unexpected EOF in array".into()),
}
}
Ok(serde_json::Value::Array(arr))
}
fn parse_table(&mut self) -> std::result::Result<serde_json::Value, String> {
self.expect_byte(b'#')?;
let mut keys = vec![self.parse_key()?];
while self.peek() == Some(b',') {
self.advance();
keys.push(self.parse_key()?);
}
let mut rows = Vec::new();
while self.peek() == Some(b'\n') {
self.advance();
let mut map = serde_json::Map::new();
for (i, key) in keys.iter().enumerate() {
if i > 0 {
self.expect_byte(b',')?;
}
if !matches!(self.peek(), Some(b',' | b'\n' | b']') | None) {
map.insert(key.clone(), self.parse_value()?);
}
}
rows.push(serde_json::Value::Object(map));
}
self.expect_byte(b']')?;
Ok(serde_json::Value::Array(rows))
}
fn parse_string(&mut self) -> std::result::Result<String, String> {
self.expect_byte(b'"')?;
let mut bytes: Vec<u8> = Vec::new();
loop {
match self.advance() {
None => return Err("unterminated string".into()),
Some(b'"') => break,
Some(b'\\') => {
match self.advance() {
Some(b'"') => bytes.push(b'"'),
Some(b'\\') => bytes.push(b'\\'),
Some(b'/') => bytes.push(b'/'),
Some(b'n') => bytes.push(b'\n'),
Some(b'r') => bytes.push(b'\r'),
Some(b't') => bytes.push(b'\t'),
Some(b'b') => bytes.push(b'\x08'),
Some(b'f') => bytes.push(b'\x0C'),
Some(b'u') => {
let hex = self.take_n(4)?;
let code = u32::from_str_radix(&hex, 16)
.map_err(|e| format!("bad \\u escape: {e}"))?;
let ch = if (0xD800..=0xDBFF).contains(&code) {
self.expect_byte(b'\\')?;
self.expect_byte(b'u')?;
let hex2 = self.take_n(4)?;
let low = u32::from_str_radix(&hex2, 16)
.map_err(|e| format!("bad \\u escape in low surrogate: {e}"))?;
if !(0xDC00..=0xDFFF).contains(&low) {
return Err(format!("expected low surrogate, got U+{low:04X}"));
}
let scalar = 0x10000 + ((code - 0xD800) << 10) + (low - 0xDC00);
char::from_u32(scalar)
.ok_or_else(|| format!("invalid surrogate pair scalar U+{scalar:X}"))?
} else {
char::from_u32(code)
.ok_or_else(|| format!("invalid unicode codepoint {code}"))?
};
let mut tmp = [0u8; 4];
let encoded = ch.encode_utf8(&mut tmp);
bytes.extend_from_slice(encoded.as_bytes());
}
Some(b) => {
return Err(format!("unknown escape \\{}", b as char))
}
None => return Err("EOF in escape".into()),
}
}
Some(b) => {
bytes.push(b);
}
}
}
String::from_utf8(bytes).map_err(|e| format!("invalid UTF-8 in string: {e}"))
}
fn take_n(&mut self, n: usize) -> std::result::Result<String, String> {
if self.pos + n > self.src.len() {
return Err("unexpected EOF".into());
}
let slice = &self.src[self.pos..self.pos + n];
self.pos += n;
std::str::from_utf8(slice)
.map(|s| s.to_owned())
.map_err(|e| e.to_string())
}
fn parse_number(&mut self) -> std::result::Result<serde_json::Value, String> {
let start = self.pos;
if self.peek() == Some(b'-') {
self.advance();
}
while matches!(self.peek(), Some(b'0'..=b'9')) {
self.advance();
}
if self.peek() == Some(b'.') {
self.advance();
while matches!(self.peek(), Some(b'0'..=b'9')) {
self.advance();
}
}
if matches!(self.peek(), Some(b'e') | Some(b'E')) {
self.advance();
if matches!(self.peek(), Some(b'+') | Some(b'-')) {
self.advance();
}
while matches!(self.peek(), Some(b'0'..=b'9')) {
self.advance();
}
}
let num_str = std::str::from_utf8(&self.src[start..self.pos])
.map_err(|e| e.to_string())?;
let n: serde_json::Number = num_str
.parse()
.map_err(|e| format!("bad number '{num_str}': {e}"))?;
Ok(serde_json::Value::Number(n))
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
use serde_json::json;
fn arb_json_value() -> impl Strategy<Value = serde_json::Value> {
let leaf = prop_oneof![
Just(serde_json::Value::Null),
any::<bool>().prop_map(serde_json::Value::Bool),
any::<i64>().prop_map(|n| serde_json::json!(n)),
any::<f64>()
.prop_filter("must be finite", |f| f.is_finite())
.prop_map(|f| serde_json::json!(f)),
".*".prop_map(serde_json::Value::String),
];
leaf.prop_recursive(
4, 64, 8, |inner| {
prop_oneof![
prop::collection::vec(inner.clone(), 0..8)
.prop_map(serde_json::Value::Array),
prop::collection::hash_map(".*", inner, 0..8).prop_map(|m| {
serde_json::Value::Object(m.into_iter().collect())
}),
]
},
)
}
proptest! {
#[test]
fn prop_toon_round_trip(v in arb_json_value()) {
let encoded = ToonEncoder.encode(&v).expect("encode should not fail");
let decoded = ToonEncoder.decode(&encoded).expect("decode should not fail");
prop_assert_eq!(decoded, v);
}
}
fn arb_large_json_object() -> impl Strategy<Value = serde_json::Value> {
let arb_leaf_string = "[a-z]{4,12}".prop_map(serde_json::Value::String);
let arb_inner = prop::collection::hash_map(
"[a-z]{4,8}",
arb_leaf_string.clone(),
5..8usize,
)
.prop_map(|m| serde_json::Value::Object(m.into_iter().collect()));
let arb_mid = prop::collection::hash_map(
"[a-z]{4,8}",
prop_oneof![
1 => arb_leaf_string.clone(),
2 => arb_inner,
],
5..8usize,
)
.prop_map(|m| serde_json::Value::Object(m.into_iter().collect()));
prop::collection::hash_map(
"[a-z]{4,8}",
arb_mid,
8..12usize,
)
.prop_map(|m| serde_json::Value::Object(m.into_iter().collect()))
}
proptest! {
#[test]
fn prop_toon_token_reduction(v in arb_large_json_object()) {
let pretty = serde_json::to_string_pretty(&v)
.expect("pretty-print should not fail");
prop_assume!(pretty.len() >= 100);
let encoded = ToonEncoder.encode(&v).expect("encode should not fail");
let encoded_content_len = encoded.len().saturating_sub(TOON_PREFIX.len());
let threshold = (pretty.len() as f64 * 0.70).ceil() as usize;
prop_assert!(
encoded_content_len <= threshold,
"encoded content length {} is not at most 70% of pretty length {} (threshold {})\npretty:\n{}\nencoded: {}",
encoded_content_len,
pretty.len(),
threshold,
pretty,
encoded,
);
}
}
fn enc(v: &serde_json::Value) -> String {
ToonEncoder.encode(v).unwrap()
}
fn rt(v: serde_json::Value) -> serde_json::Value {
let encoded = ToonEncoder.encode(&v).unwrap();
ToonEncoder.decode(&encoded).unwrap()
}
#[test]
fn roundtrip_null() {
assert_eq!(rt(json!(null)), json!(null));
}
#[test]
fn roundtrip_bool() {
assert_eq!(rt(json!(true)), json!(true));
assert_eq!(rt(json!(false)), json!(false));
}
#[test]
fn roundtrip_number() {
assert_eq!(rt(json!(42)), json!(42));
assert_eq!(rt(json!(3.14)), json!(3.14));
assert_eq!(rt(json!(-7)), json!(-7));
}
#[test]
fn roundtrip_string() {
assert_eq!(rt(json!("hello")), json!("hello"));
assert_eq!(rt(json!("with \"quotes\"")), json!("with \"quotes\""));
assert_eq!(rt(json!("line\nnewline")), json!("line\nnewline"));
}
#[test]
fn roundtrip_array() {
let v = json!([1, "two", true, null, [3, 4]]);
assert_eq!(rt(v.clone()), v);
}
#[test]
fn roundtrip_object() {
let v = json!({"name": "Alice", "age": 30, "active": true});
assert_eq!(rt(v.clone()), v);
}
#[test]
fn roundtrip_nested() {
let v = json!({
"user": {"id": 1, "name": "Bob"},
"tags": ["rust", "json"],
"meta": null
});
assert_eq!(rt(v.clone()), v);
}
#[test]
fn roundtrip_quoted_key() {
let v = json!({"my-key": 1, "123start": 2});
assert_eq!(rt(v.clone()), v);
}
#[test]
fn roundtrip_empty_object() {
assert_eq!(rt(json!({})), json!({}));
}
#[test]
fn roundtrip_empty_array() {
assert_eq!(rt(json!([])), json!([]));
}
#[test]
fn roundtrip_empty_string() {
assert_eq!(rt(json!("")), json!(""));
}
#[test]
fn prefix_present() {
let s = enc(&json!({"a": 1}));
assert!(s.starts_with("TOON:"), "encoded: {s}");
}
#[test]
fn simple_key_unquoted() {
let s = enc(&json!({"name": "Alice"}));
assert!(s.contains("name:"), "encoded: {s}");
assert!(!s.contains("\"name\""), "encoded: {s}");
}
#[test]
fn complex_key_quoted() {
let s = enc(&json!({"my-key": 1}));
assert!(s.contains("\"my-key\""), "encoded: {s}");
}
#[test]
fn no_spaces_in_array() {
let s = enc(&json!([1, 2, 3]));
let body = s.strip_prefix("TOON:").unwrap();
assert!(!body.contains(' '), "body: {body}");
}
#[test]
fn control_chars_are_escaped() {
let v = json!({"key": "tab\there\u{0}nul\u{7f}del\u{2028}ls", "num": 42});
let s = enc(&v);
assert!(!s.chars().any(|c| c.is_control()), "raw control char in {s:?}");
assert!(!s.contains('\u{2028}'), "raw line separator in {s:?}");
assert_eq!(rt(v.clone()), v);
}
#[test]
fn non_ascii_text_is_kept_as_is() {
let v = json!({"title": "Design note 📄", "jp": "こんにちは", "de": "Größe"});
let s = enc(&v);
assert!(s.contains("📄") && s.contains("こんにちは") && s.contains("Größe"), "{s}");
assert!(!s.contains("\\u"), "no \\u escapes for printable text: {s}");
assert_eq!(rt(v.clone()), v);
}
#[test]
fn uniform_objects_become_a_table() {
let v = json!([
{"id": 1, "name": "alpha"},
{"id": 2, "name": "beta"},
{"id": 3, "name": "gamma"}
]);
assert_eq!(enc(&v), "TOON:[#id,name\n1,\"alpha\"\n2,\"beta\"\n3,\"gamma\"]");
assert_eq!(rt(v.clone()), v);
}
#[test]
fn table_marks_absent_keys_with_empty_cells() {
let v = json!([
{"a": 1, "b": "x", "c": null},
{"a": 2, "c": false},
{"a": 3, "b": "", "c": true}
]);
let s = enc(&v);
assert!(s.starts_with("TOON:[#a,b,c\n"), "{s}");
assert!(s.contains("\n2,,false\n"), "absent b is an empty cell: {s}");
assert!(s.contains("\n3,\"\",true]"), "empty string stays distinct: {s}");
assert_eq!(rt(v.clone()), v);
}
#[test]
fn table_rows_stay_on_one_line() {
let v = json!({"items": [
{"id": 1, "user": {"login": "ana"}, "labels": [{"name": "bug"}, {"name": "ui"}], "body": "a\nb"},
{"id": 2, "user": {"login": "wei"}, "labels": [{"name": "docs"}, {"name": "x"}], "body": "c"},
{"id": 3, "user": {"login": "bot"}, "labels": [], "body": "d,e"}
]});
let s = enc(&v);
assert_eq!(s.lines().count(), 4, "header plus one line per row: {s}");
assert!(s.contains("labels:") || s.contains(",labels"), "{s}");
assert_eq!(rt(v.clone()), v);
}
#[test]
fn sparse_or_mixed_arrays_stay_plain() {
let sparse = json!([{"a": 1}, {"b": 2}, {"c": 3}, {"d": 4}]);
assert!(!enc(&sparse).contains("[#"), "{}", enc(&sparse));
assert_eq!(rt(sparse.clone()), sparse);
let mixed = json!([{"a": 1}, {"a": 2}, 3]);
assert!(!enc(&mixed).contains("[#"), "{}", enc(&mixed));
assert_eq!(rt(mixed.clone()), mixed);
let single = json!([{"a": 1, "b": 2}]);
assert_eq!(enc(&single), "TOON:[{a:1,b:2}]");
}
#[test]
fn table_keys_are_quoted_when_needed() {
let v = json!([
{"my-key": 1, "true": "t", "a b": [1, 2]},
{"my-key": 2, "true": "f", "a b": []}
]);
assert!(enc(&v).starts_with("TOON:[#"), "{}", enc(&v));
assert_eq!(rt(v.clone()), v);
}
#[test]
fn decode_rejects_ragged_rows() {
assert!(ToonEncoder.decode("TOON:[#a,b\n1]").is_err(), "too few cells");
assert!(ToonEncoder.decode("TOON:[#a\n1,2]").is_err(), "too many cells");
assert!(ToonEncoder.decode("TOON:[#\n1]").is_err(), "no keys");
}
#[test]
fn is_json_valid() {
assert!(ToonEncoder::is_json(r#"{"a":1}"#));
assert!(ToonEncoder::is_json("[1,2,3]"));
assert!(ToonEncoder::is_json("42"));
assert!(ToonEncoder::is_json("\"hello\""));
assert!(ToonEncoder::is_json("null"));
assert!(ToonEncoder::is_json("true"));
}
#[test]
fn is_json_invalid() {
assert!(!ToonEncoder::is_json("not json"));
assert!(!ToonEncoder::is_json("{bad}"));
assert!(!ToonEncoder::is_json(""));
assert!(!ToonEncoder::is_json(" "));
}
#[test]
fn is_json_whitespace_trimmed() {
assert!(ToonEncoder::is_json(" { \"a\": 1 } "));
}
proptest! {
#[test]
fn prop_cross_tokenizer_determinism(v in arb_json_value()) {
let encoded = ToonEncoder.encode(&v).expect("encode should not fail");
let char_count = encoded.chars().count() as f64;
let claude_tokens = char_count / 3.5;
let gpt_tokens = char_count / 4.0;
let gemini_tokens = char_count / 3.8;
let max_estimate = claude_tokens.max(gpt_tokens).max(gemini_tokens);
let min_estimate = claude_tokens.min(gpt_tokens).min(gemini_tokens);
if min_estimate > 0.0 {
let ratio = max_estimate / min_estimate;
prop_assert!(
ratio <= 1.15,
"token count estimates diverge by more than 15%: \
claude={:.2}, gpt={:.2}, gemini={:.2}, ratio={:.4}\nencoded: {:?}",
claude_tokens, gpt_tokens, gemini_tokens, ratio, encoded
);
}
}
}
#[test]
fn decode_rejects_non_toon() {
assert!(ToonEncoder.decode("not a toon string").is_err());
}
#[test]
fn decode_rejects_trailing_data() {
assert!(ToonEncoder.decode("TOON:42garbage").is_err());
}
}