Skip to main content

jsonrepair_rs/
lib.rs

1//! # jsonrepair-rs
2//!
3//! Repair broken JSON — fix quotes, commas, comments, trailing content,
4//! and 30+ other issues commonly found in LLM outputs.
5//!
6//! Port of the JavaScript [jsonrepair](https://github.com/josdejong/jsonrepair) library.
7//!
8//! ## Usage
9//!
10//! ```
11//! use jsonrepair_rs::jsonrepair;
12//!
13//! // Fix single quotes (whitespace preserved)
14//! let result = jsonrepair("{'name': 'John'}").unwrap();
15//! assert_eq!(result, r#"{"name": "John"}"#);
16//!
17//! // Fix trailing commas
18//! let result = jsonrepair(r#"{"a": 1, "b": 2,}"#).unwrap();
19//! assert_eq!(result, r#"{"a": 1, "b": 2}"#);
20//!
21//! // Convert Python keywords
22//! let result = jsonrepair(r#"{"flag": True, "value": None}"#).unwrap();
23//! assert_eq!(result, r#"{"flag": true, "value": null}"#);
24//! ```
25
26mod chars;
27mod error;
28mod parser;
29
30#[cfg(feature = "serde")]
31pub use error::JsonRepairParseError;
32pub use error::{JsonRepairError, JsonRepairErrorKind};
33
34/// Error returned by writer-based repair helpers.
35#[derive(Debug)]
36#[non_exhaustive]
37pub enum JsonRepairWriteError {
38    /// The input could not be repaired safely.
39    Repair(JsonRepairError),
40    /// The repaired JSON could not be written to the destination.
41    Write(std::io::Error),
42}
43
44impl std::fmt::Display for JsonRepairWriteError {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        match self {
47            Self::Repair(err) => err.fmt(f),
48            Self::Write(err) => write!(f, "failed to write repaired JSON: {err}"),
49        }
50    }
51}
52
53impl std::error::Error for JsonRepairWriteError {
54    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
55        match self {
56            Self::Repair(err) => Some(err),
57            Self::Write(err) => Some(err),
58        }
59    }
60}
61
62impl From<JsonRepairError> for JsonRepairWriteError {
63    fn from(err: JsonRepairError) -> Self {
64        Self::Repair(err)
65    }
66}
67
68impl From<std::io::Error> for JsonRepairWriteError {
69    fn from(err: std::io::Error) -> Self {
70        Self::Write(err)
71    }
72}
73
74/// Error returned by reader-to-writer repair helpers.
75#[derive(Debug)]
76#[non_exhaustive]
77pub enum JsonRepairStreamError {
78    /// The input stream could not be read as UTF-8 text.
79    Read(std::io::Error),
80    /// The input could not be repaired safely.
81    Repair(JsonRepairError),
82    /// The repaired JSON could not be written to the destination.
83    Write(std::io::Error),
84}
85
86impl std::fmt::Display for JsonRepairStreamError {
87    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88        match self {
89            Self::Read(err) => write!(f, "failed to read JSON input: {err}"),
90            Self::Repair(err) => err.fmt(f),
91            Self::Write(err) => write!(f, "failed to write repaired JSON: {err}"),
92        }
93    }
94}
95
96impl std::error::Error for JsonRepairStreamError {
97    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
98        match self {
99            Self::Read(err) => Some(err),
100            Self::Repair(err) => Some(err),
101            Self::Write(err) => Some(err),
102        }
103    }
104}
105
106/// Repair a broken JSON string and write the valid JSON to a writer.
107///
108/// This is a writer convenience API. It repairs the input first, then writes
109/// the repaired JSON bytes to the provided [`std::io::Write`] destination.
110pub fn jsonrepair_to_writer<W>(input: &str, writer: &mut W) -> Result<(), JsonRepairWriteError>
111where
112    W: std::io::Write + ?Sized,
113{
114    let repaired = jsonrepair(input)?;
115    writer.write_all(repaired.as_bytes())?;
116    Ok(())
117}
118
119/// Repair JSON-like text from a reader and write valid JSON to a writer.
120///
121/// This is the first streaming-oriented API surface: callers can connect files,
122/// stdin, stdout, sockets, or buffers without receiving an owned repaired
123/// [`String`]. The current parser still needs the complete input and repaired
124/// output buffered inside the crate before writing; this preserves exact repair
125/// behavior while leaving room for a future lower-memory parser.
126pub fn jsonrepair_reader_to_writer<R, W>(
127    mut reader: R,
128    writer: &mut W,
129) -> Result<(), JsonRepairStreamError>
130where
131    R: std::io::Read,
132    W: std::io::Write + ?Sized,
133{
134    let mut input = String::new();
135    reader
136        .read_to_string(&mut input)
137        .map_err(JsonRepairStreamError::Read)?;
138
139    let repaired = jsonrepair(&input).map_err(JsonRepairStreamError::Repair)?;
140    writer
141        .write_all(repaired.as_bytes())
142        .map_err(JsonRepairStreamError::Write)?;
143
144    Ok(())
145}
146
147/// Repair a broken JSON string, returning valid JSON.
148///
149/// Handles 30+ categories of issues including:
150/// - Single/curly quotes → double quotes
151/// - Trailing/missing commas
152/// - Comments (`//`, `/* */`, `#`)
153/// - Python keywords (`True`, `False`, `None`)
154/// - JavaScript keywords (`undefined`, `NaN`, `Infinity`)
155/// - Markdown code fences
156/// - JSONP wrappers
157/// - Unquoted keys and strings
158/// - Truncated JSON (auto-closes brackets)
159/// - String concatenation (`"a" + "b"`)
160/// - Invalid escape sequences
161/// - Leading zeros, truncated numbers
162/// - MongoDB constructors (`ObjectId(...)`)
163/// - NDJSON (newline-delimited JSON)
164/// - Ellipsis operators (`...`)
165///
166/// Returns `Err(JsonRepairError)` if the input cannot be repaired.
167pub fn jsonrepair(input: &str) -> Result<String, JsonRepairError> {
168    let repairer = parser::JsonRepairer::new(input);
169    repairer.repair()
170}
171
172/// Repair a broken JSON string and parse it into [`serde_json::Value`].
173///
174/// This helper is available with the `serde` feature.
175#[cfg(feature = "serde")]
176pub fn jsonrepair_value(input: &str) -> Result<serde_json::Value, JsonRepairParseError> {
177    jsonrepair_parse(input)
178}
179
180/// Repair a broken JSON string and deserialize it into the requested type.
181///
182/// This helper is available with the `serde` feature.
183#[cfg(feature = "serde")]
184pub fn jsonrepair_parse<T>(input: &str) -> Result<T, JsonRepairParseError>
185where
186    T: serde::de::DeserializeOwned,
187{
188    let repaired = jsonrepair(input)?;
189    serde_json::from_str(&repaired).map_err(JsonRepairParseError::from)
190}