Skip to main content

lean_ctx/core/patterns/
json_schema.rs

1pub fn compress(output: &str) -> Option<String> {
2    let trimmed = output.trim();
3
4    if !trimmed.starts_with('{') && !trimmed.starts_with('[') {
5        return None;
6    }
7
8    let val: serde_json::Value = match serde_json::from_str(trimmed) {
9        Ok(v) => v,
10        Err(_) => return None,
11    };
12
13    let schema = extract_schema(&val, 0);
14    Some(schema)
15}
16
17fn extract_schema(val: &serde_json::Value, depth: usize) -> String {
18    let indent = "  ".repeat(depth);
19    match val {
20        serde_json::Value::Object(map) => {
21            if map.is_empty() {
22                return format!("{indent}{{}}");
23            }
24            if depth > 3 {
25                return format!("{indent}{{...{} keys}}", map.len());
26            }
27
28            let mut entries = Vec::new();
29            for (key, value) in map.iter().take(20) {
30                let type_str = type_of(value);
31                match value {
32                    serde_json::Value::Object(inner) if !inner.is_empty() && depth < 3 => {
33                        let nested = extract_schema(value, depth + 1);
34                        entries.push(format!("{indent}  {key}: {{\n{nested}\n{indent}  }}"));
35                    }
36                    serde_json::Value::Array(arr) if !arr.is_empty() => {
37                        let item_type = if let Some(first) = arr.first() {
38                            type_of(first)
39                        } else {
40                            "any".to_string()
41                        };
42                        entries.push(format!("{indent}  {key}: [{item_type}...{}]", arr.len()));
43                    }
44                    _ => {
45                        entries.push(format!("{indent}  {key}: {type_str}"));
46                    }
47                }
48            }
49            if map.len() > 20 {
50                entries.push(format!("{indent}  ...+{} more keys", map.len() - 20));
51            }
52            entries.join("\n")
53        }
54        serde_json::Value::Array(arr) => {
55            if arr.is_empty() {
56                return format!("{indent}[]");
57            }
58            let first_schema = extract_schema(&arr[0], depth + 1);
59            format!(
60                "{indent}[{} items, each:\n{first_schema}\n{indent}]",
61                arr.len()
62            )
63        }
64        other => format!("{indent}{}", type_of(other)),
65    }
66}
67
68fn type_of(val: &serde_json::Value) -> String {
69    match val {
70        serde_json::Value::Null => "null".to_string(),
71        serde_json::Value::Bool(_) => "bool".to_string(),
72        serde_json::Value::Number(n) => {
73            if n.is_f64() {
74                "float".to_string()
75            } else {
76                "int".to_string()
77            }
78        }
79        serde_json::Value::String(s) => {
80            if s.len() > 50 {
81                format!("str({})", s.len())
82            } else {
83                "str".to_string()
84            }
85        }
86        serde_json::Value::Array(arr) => format!("[...{}]", arr.len()),
87        serde_json::Value::Object(map) => format!("{{...{} keys}}", map.len()),
88    }
89}