Skip to main content

agent_commander/streaming/
ndjson.rs

1//! NDJSON (Newline Delimited JSON) utilities
2//! https://github.com/ndjson/ndjson-spec
3
4use serde_json::Value;
5
6/// Parse a single NDJSON line
7///
8/// # Arguments
9/// * `line` - Line to parse
10///
11/// # Returns
12/// Parsed JSON value or None if invalid
13pub fn parse_ndjson_line(line: &str) -> Option<Value> {
14    let trimmed = line.trim();
15
16    // Empty lines are ignored in NDJSON
17    if trimmed.is_empty() {
18        return None;
19    }
20
21    // Must start with { or [ to be valid JSON
22    if !trimmed.starts_with('{') && !trimmed.starts_with('[') {
23        return None;
24    }
25
26    serde_json::from_str(trimmed).ok()
27}
28
29/// Stringify an object to NDJSON line
30///
31/// # Arguments
32/// * `value` - Value to stringify
33/// * `compact` - Use compact JSON (no indentation)
34///
35/// # Returns
36/// NDJSON line with trailing newline
37pub fn stringify_ndjson_line(value: &Value, compact: bool) -> String {
38    if value.is_null() {
39        return String::new();
40    }
41
42    let json = if compact {
43        serde_json::to_string(value).unwrap_or_default()
44    } else {
45        serde_json::to_string_pretty(value).unwrap_or_default()
46    };
47
48    format!("{}\n", json)
49}
50
51/// Parse multiple NDJSON lines
52///
53/// # Arguments
54/// * `data` - Data containing multiple lines
55///
56/// # Returns
57/// Vector of parsed JSON values
58pub fn parse_ndjson(data: &str) -> Vec<Value> {
59    data.lines()
60        .filter_map(|line| parse_ndjson_line(line))
61        .collect()
62}
63
64/// Stringify multiple objects to NDJSON
65///
66/// # Arguments
67/// * `values` - Values to stringify
68/// * `compact` - Use compact JSON
69///
70/// # Returns
71/// NDJSON string
72pub fn stringify_ndjson(values: &[Value], compact: bool) -> String {
73    values
74        .iter()
75        .map(|v| stringify_ndjson_line(v, compact))
76        .collect()
77}
78
79// Tests are in rust/tests/streaming_tests.rs