diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a855e84e..6f49fef70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -639,6 +639,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `input_schema_input` only when it differs from the call's input under the AI SDK's `isDeepEqualData` (`aimux-core`, to-response-messages.ts:133-146). +- Partial-JSON parsing returns one enum (`aimux-core`): + `aimux_core::json_repair::parse_partial_json`, `ParsePartialJsonResult` and + `ParsePartialJsonState` (also re-exported from `aimux_core::util`) are + removed; use `PartialJson::parse(&str)` (doc alias `parsePartialJson`). Its + variants `Parsed(Value)`, `Repaired(Value)` and `Failed` carry the value, so + a value without a parse state or a state without its value cannot be built; + `value()` / `into_value()` return it for either successful variant. + Upstream's `undefined-input` state is the caller's `Option`: + `parse_partial_json(text)` becomes `text.map(PartialJson::parse)`. Outcomes + and values are unchanged; `fix_json` is unchanged. + **Rust (aimux-stream)** - `SseStream` is now a thin adapter over the `sse-stream` crate (WHATWG diff --git a/aimux-core/src/json_repair.rs b/aimux-core/src/json_repair.rs index feb31f4fa..c38dbc98e 100644 --- a/aimux-core/src/json_repair.rs +++ b/aimux-core/src/json_repair.rs @@ -1,433 +1,352 @@ -//! JSON repair and partial-JSON parsing helpers. +//! JSON repair and partial-JSON parsing. //! -//! Ports of `fix-json.ts` and `parse-partial-json.ts` from the Vercel AI SDK -//! `util` package. +//! Port of `packages/ai/src/util/fix-json.ts` and +//! `packages/ai/src/util/parse-partial-json.ts` from the Vercel AI SDK. The +//! scanner states keep their upstream names in the variant docs so upstream +//! changes can be located. +//! +//! Core's own paths do not call it: a final tool-call input is parsed as +//! given, never repaired (RFC-0042 Β§3.2). It stays public because `ai` +//! exports `parsePartialJson`, for callers that show streamed partial JSON +//! (tool-input deltas, partial objects) before it is complete. +//! +//! The scanner steps through UTF-16 code units as upstream does, so a +//! character outside the Basic Multilingual Plane (an emoji, say) is two +//! steps: `"[1πŸ˜€"` becomes `"[1πŸ˜€]"` here too. + +use std::ops::ControlFlow; use serde_json::Value; -// =========================================================================== -// fixJson <- packages/ai/src/util/fix-json.ts -// =========================================================================== +/// Repairs a possibly truncated JSON text into JSON a parser accepts. +/// +/// Keeps the longest prefix that ends on a complete token and appends the +/// closing tokens (`"`, `}`, `]`, or the rest of `true`/`false`/`null`) for +/// every value still open. Text after the top-level value is dropped. The +/// result is not validated: input that is invalid for other reasons stays +/// invalid. +/// +/// ``` +/// use aimux_core::json_repair::fix_json; +/// +/// assert_eq!(fix_json(r#"{"items": [1, 2, {"done": tr"#), r#"{"items": [1, 2, {"done": true}]}"#); +/// assert_eq!(fix_json(r#"{"key": "val"#), r#"{"key": "val"}"#); +/// assert_eq!(fix_json("[1, "), "[1]"); +/// assert_eq!(fix_json("[1πŸ˜€"), "[1πŸ˜€]"); // as upstream, which counts UTF-16 code units +/// ``` +#[doc(alias = "fixJson")] +#[must_use] +pub fn fix_json(input: &str) -> String { + let mut chars = input.char_indices(); + let mut repair = Repair::default(); + + // Upstream `ROOT`: characters before the first value start are skipped. + if let Some((i, c, frame)) = chars + .by_ref() + .find_map(|(i, c)| Frame::opened_by(c).map(|frame| (i, c, frame))) + { + repair.open_value(i, c, frame); + } + 'scan: for (i, c) in chars { + // Upstream reads one UTF-16 code unit at a time, so a character + // outside the BMP takes two steps. Neither half is a JSON token, and + // neither is `c`, so stepping `c` twice is stepping its two halves; + // a mark on either keeps the whole character. + for _ in 0..c.len_utf16() { + if repair.step(i, c).is_break() { + break 'scan; + } + } + } + repair.finish(input) +} -/// Scanner state for [`fix_json`]. Mirrors the `State` union in `fix-json.ts`. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum FixState { - Root, - Finish, - InsideString, - InsideStringEscape, - InsideStringUnicodeEscape, - InsideLiteral, - InsideNumber, - InsideObjectStart, - InsideObjectKey, - InsideObjectAfterKey, - InsideObjectBeforeValue, - InsideObjectAfterValue, - InsideObjectAfterComma, - InsideArrayStart, - InsideArrayAfterValue, - InsideArrayAfterComma, +/// A value that is open at the current position, with where the scanner is +/// inside it. +#[derive(Clone, Copy)] +enum Frame { + Object(ObjectPos), + Array(ArrayPos), + String(StringPos), + /// `INSIDE_NUMBER`. + Number, + /// `INSIDE_LITERAL`; holds the part of `true`/`false`/`null` not seen yet. + Literal(&'static str), } -/// Single-pass scanner/repairer for partial JSON, ported line-for-line from -/// `fix-json.ts`. -/// -/// Given a (possibly truncated) piece of JSON, returns the longest prefix that -/// can be closed into valid JSON by appending the minimal set of closing -/// tokens (`"`, `}`, `]`, or the remainder of an in-progress literal). -struct Fixer<'a> { - chars: &'a [char], - stack: Vec, - last_valid_index: isize, // -1 == nothing valid yet - literal_start: Option, - unicode_escape_digits: usize, +#[derive(Clone, Copy)] +enum ObjectPos { + /// `INSIDE_OBJECT_START`: after `{`. + Start, + /// `INSIDE_OBJECT_KEY`: inside a key. + Key, + /// `INSIDE_OBJECT_AFTER_KEY`: after a key, before `:`. + AfterKey, + /// `INSIDE_OBJECT_BEFORE_VALUE`: after `:`. + BeforeValue, + /// `INSIDE_OBJECT_AFTER_VALUE`. + AfterValue, + /// `INSIDE_OBJECT_AFTER_COMMA`. + AfterComma, } -impl<'a> Fixer<'a> { - fn new(chars: &'a [char]) -> Self { - Self { - chars, - stack: vec![FixState::Root], - last_valid_index: -1, - literal_start: None, - unicode_escape_digits: 0, +#[derive(Clone, Copy)] +enum ArrayPos { + /// `INSIDE_ARRAY_START`: after `[`. + Start, + /// `INSIDE_ARRAY_AFTER_VALUE`. + AfterValue, + /// `INSIDE_ARRAY_AFTER_COMMA`. + AfterComma, +} + +#[derive(Clone, Copy)] +enum StringPos { + /// `INSIDE_STRING`. + Plain, + /// `INSIDE_STRING_ESCAPE`: after `\`. + Escape, + /// `INSIDE_STRING_UNICODE_ESCAPE`: after `\u` and `digits` hex digits. + UnicodeEscape { digits: u8 }, +} + +impl Frame { + /// The frame a value starting with `c` opens (upstream `processValueStart`). + fn opened_by(c: char) -> Option { + match c { + '"' => Some(Self::String(StringPos::Plain)), + 'f' => Some(Self::Literal("alse")), + 't' => Some(Self::Literal("rue")), + 'n' => Some(Self::Literal("ull")), + '-' | '0'..='9' => Some(Self::Number), + '{' => Some(Self::Object(ObjectPos::Start)), + '[' => Some(Self::Array(ArrayPos::Start)), + _ => None, } } - fn is_hex_digit(c: char) -> bool { - c.is_ascii_hexdigit() + /// What closes this frame when the input ends inside it. + fn closing(self) -> &'static str { + match self { + Self::Object(_) => "}", + Self::Array(_) => "]", + Self::String(_) => "\"", + Self::Number => "", + Self::Literal(rest) => rest, + } } +} - /// Port of `processValueStart`. - fn process_value_start(&mut self, c: char, i: usize, swap_state: FixState) { - match c { - '"' => { - self.last_valid_index = i as isize; - self.stack.pop(); - self.stack.push(swap_state); - self.stack.push(FixState::InsideString); - } - 'f' | 't' | 'n' => { - self.last_valid_index = i as isize; - self.literal_start = Some(i); - self.stack.pop(); - self.stack.push(swap_state); - self.stack.push(FixState::InsideLiteral); - } - '-' => { - self.stack.pop(); - self.stack.push(swap_state); - self.stack.push(FixState::InsideNumber); - } - '0'..='9' => { - self.last_valid_index = i as isize; - self.stack.pop(); - self.stack.push(swap_state); - self.stack.push(FixState::InsideNumber); - } - '{' => { - self.last_valid_index = i as isize; - self.stack.pop(); - self.stack.push(swap_state); - self.stack.push(FixState::InsideObjectStart); - } - '[' => { - self.last_valid_index = i as isize; - self.stack.pop(); - self.stack.push(swap_state); - self.stack.push(FixState::InsideArrayStart); - } - // Unmatched characters do nothing (matches the TS switch which has - // no default arm). - _ => {} - } +#[derive(Default)] +struct Repair { + /// Open values, outermost first. Empty before the top-level value starts + /// (upstream `ROOT`) and after it closes (upstream `FINISH`). + open: Vec, + /// Byte length of the longest prefix that ends on a complete token + /// (upstream `lastValidIndex + 1`, in bytes instead of code units). + valid_len: usize, +} + +impl Repair { + /// Marks the input up to and including `c` (at byte `i`) as keepable. + fn mark(&mut self, i: usize, c: char) { + self.valid_len = i + c.len_utf8(); } - /// Port of `processAfterObjectValue`. - fn process_after_object_value(&mut self, c: char, i: usize) { - match c { - ',' => { - self.stack.pop(); - self.stack.push(FixState::InsideObjectAfterComma); - } - '}' => { - self.last_valid_index = i as isize; - self.stack.pop(); - } - _ => {} + /// Marks `c` and closes the innermost value. + fn close(&mut self, i: usize, c: char) { + self.mark(i, c); + self.open.pop(); + } + + /// Pushes the value `c` starts. A lone `-` is not a number yet, so it is + /// not marked. + fn open_value(&mut self, i: usize, c: char, frame: Frame) { + if c != '-' { + self.mark(i, c); } + self.open.push(frame); } - /// Port of `processAfterArrayValue`. - fn process_after_array_value(&mut self, c: char, i: usize) { - match c { - ',' => { - self.stack.pop(); - self.stack.push(FixState::InsideArrayAfterComma); + /// Upstream `processAfterObjectValue` / `processAfterArrayValue`. + fn after_value(&mut self, i: usize, c: char) { + let Some(top) = self.open.last_mut() else { + return; + }; + match (*top, c) { + (Frame::Object(ObjectPos::AfterValue), ',') => { + *top = Frame::Object(ObjectPos::AfterComma); } - ']' => { - self.last_valid_index = i as isize; - self.stack.pop(); + (Frame::Array(ArrayPos::AfterValue), ',') => *top = Frame::Array(ArrayPos::AfterComma), + (Frame::Object(ObjectPos::AfterValue), '}') + | (Frame::Array(ArrayPos::AfterValue), ']') => { + self.close(i, c); } _ => {} } } - fn run(&mut self) { - for (i, &c) in self.chars.iter().enumerate() { - let current = *self - .stack - .last() - .expect("fix-json stack is never empty (seeded with Root)"); - - match current { - FixState::Root => self.process_value_start(c, i, FixState::Finish), - - FixState::InsideObjectStart => match c { - '"' => { - self.stack.pop(); - self.stack.push(FixState::InsideObjectKey); - } - '}' => { - self.last_valid_index = i as isize; - self.stack.pop(); - } - _ => {} - }, + /// Closes a number or literal at `c`, which then goes to the enclosing + /// container. `c` is not reprocessed in any other state. + fn end_scalar(&mut self, i: usize, c: char) { + self.open.pop(); + self.after_value(i, c); + } - FixState::InsideObjectAfterComma => { - if c == '"' { - self.stack.pop(); - self.stack.push(FixState::InsideObjectKey); - } + /// Feeds the character `c` at byte `i`. Breaks once the top-level value + /// has closed (upstream `FINISH`): the rest of the input is ignored. + fn step(&mut self, i: usize, c: char) -> ControlFlow<()> { + let Some(top) = self.open.last_mut() else { + return ControlFlow::Break(()); + }; + match *top { + Frame::Object(pos) => match (pos, c) { + (ObjectPos::Start | ObjectPos::AfterComma, '"') => { + *top = Frame::Object(ObjectPos::Key); } - - FixState::InsideObjectKey => { - if c == '"' { - self.stack.pop(); - self.stack.push(FixState::InsideObjectAfterKey); + (ObjectPos::Start, '}') => self.close(i, c), + (ObjectPos::Key, '"') => *top = Frame::Object(ObjectPos::AfterKey), + (ObjectPos::AfterKey, ':') => *top = Frame::Object(ObjectPos::BeforeValue), + (ObjectPos::BeforeValue, _) => { + if let Some(value) = Frame::opened_by(c) { + *top = Frame::Object(ObjectPos::AfterValue); + self.open_value(i, c, value); } } - - FixState::InsideObjectAfterKey => { - if c == ':' { - self.stack.pop(); - self.stack.push(FixState::InsideObjectBeforeValue); + (ObjectPos::AfterValue, _) => self.after_value(i, c), + _ => {} + }, + Frame::Array(pos) => match (pos, c) { + (ArrayPos::Start | ArrayPos::AfterValue, ']') => self.close(i, c), + (ArrayPos::Start, _) => { + if let Some(value) = Frame::opened_by(c) { + *top = Frame::Array(ArrayPos::AfterValue); + self.open_value(i, c, value); } + // Upstream marks every character here, even one that + // starts no value. + self.mark(i, c); } - - FixState::InsideObjectBeforeValue => { - self.process_value_start(c, i, FixState::InsideObjectAfterValue); - } - - FixState::InsideObjectAfterValue => { - self.process_after_object_value(c, i); - } - - FixState::InsideString => match c { - '"' => { - self.stack.pop(); - self.last_valid_index = i as isize; - } - '\\' => { - self.stack.push(FixState::InsideStringEscape); - } - _ => { - self.last_valid_index = i as isize; - } - }, - - FixState::InsideArrayStart => match c { - ']' => { - self.last_valid_index = i as isize; - self.stack.pop(); - } - _ => { - self.last_valid_index = i as isize; - self.process_value_start(c, i, FixState::InsideArrayAfterValue); - } - }, - - FixState::InsideArrayAfterValue => match c { - ',' => { - self.stack.pop(); - self.stack.push(FixState::InsideArrayAfterComma); - } - ']' => { - self.last_valid_index = i as isize; - self.stack.pop(); + (ArrayPos::AfterValue, ',') => *top = Frame::Array(ArrayPos::AfterComma), + (ArrayPos::AfterValue, _) => self.mark(i, c), + (ArrayPos::AfterComma, _) => { + if let Some(value) = Frame::opened_by(c) { + *top = Frame::Array(ArrayPos::AfterValue); + self.open_value(i, c, value); } - _ => { - self.last_valid_index = i as isize; - } - }, - - FixState::InsideArrayAfterComma => { - self.process_value_start(c, i, FixState::InsideArrayAfterValue); } - - FixState::InsideStringEscape => { - self.stack.pop(); - if c == 'u' { - self.unicode_escape_digits = 0; - self.stack.push(FixState::InsideStringUnicodeEscape); - } else { - self.last_valid_index = i as isize; - } + }, + Frame::String(pos) => match (pos, c) { + (StringPos::Plain, '"') => self.close(i, c), + (StringPos::Plain, '\\') => *top = Frame::String(StringPos::Escape), + (StringPos::Plain, _) => self.mark(i, c), + (StringPos::Escape, 'u') => { + *top = Frame::String(StringPos::UnicodeEscape { digits: 0 }); } - - FixState::InsideStringUnicodeEscape => { - if Self::is_hex_digit(c) { - self.unicode_escape_digits += 1; - if self.unicode_escape_digits == 4 { - self.stack.pop(); - self.last_valid_index = i as isize; - } - } + (StringPos::Escape, _) => { + *top = Frame::String(StringPos::Plain); + self.mark(i, c); } - - FixState::InsideNumber => match c { - '0'..='9' => { - self.last_valid_index = i as isize; - } - 'e' | 'E' | '-' | '.' => {} - ',' => { - self.stack.pop(); - if self.stack.last() == Some(&FixState::InsideArrayAfterValue) { - self.process_after_array_value(c, i); - } - if self.stack.last() == Some(&FixState::InsideObjectAfterValue) { - self.process_after_object_value(c, i); - } - } - '}' => { - self.stack.pop(); - if self.stack.last() == Some(&FixState::InsideObjectAfterValue) { - self.process_after_object_value(c, i); - } - } - ']' => { - self.stack.pop(); - if self.stack.last() == Some(&FixState::InsideArrayAfterValue) { - self.process_after_array_value(c, i); - } - } - _ => { - self.stack.pop(); - } - }, - - FixState::InsideLiteral => { - let start = self - .literal_start - .expect("literal_start is set whenever INSIDE_LITERAL is pushed"); - let partial: String = self.chars[start..=i].iter().collect(); - if !"false".starts_with(&partial) - && !"true".starts_with(&partial) - && !"null".starts_with(&partial) - { - self.stack.pop(); - match self.stack.last() { - Some(FixState::InsideObjectAfterValue) => { - self.process_after_object_value(c, i); - } - Some(FixState::InsideArrayAfterValue) => { - self.process_after_array_value(c, i); - } - _ => {} - } + (StringPos::UnicodeEscape { digits }, _) if c.is_ascii_hexdigit() => { + if digits == 3 { + *top = Frame::String(StringPos::Plain); + self.mark(i, c); } else { - self.last_valid_index = i as isize; + *top = Frame::String(StringPos::UnicodeEscape { digits: digits + 1 }); } } - - // FINISH (and any unhandled state): the TS switch has no case - // for FINISH, so trailing characters after the top-level value - // completes are ignored. - FixState::Finish => {} - } + (StringPos::UnicodeEscape { .. }, _) => {} + }, + Frame::Number => match c { + '0'..='9' => self.mark(i, c), + 'e' | 'E' | '-' | '.' => {} + _ => self.end_scalar(i, c), + }, + Frame::Literal(rest) => match rest.strip_prefix(c) { + Some(rest) => { + *top = Frame::Literal(rest); + self.mark(i, c); + } + None => self.end_scalar(i, c), + }, } + ControlFlow::Continue(()) } - fn finish(self) -> String { - let mut result: String = if self.last_valid_index >= 0 { - self.chars[..=(self.last_valid_index as usize)] - .iter() - .collect() - } else { - String::new() - }; - - // Walk the stack top-to-bottom, appending the minimal closing tokens. - // Mirrors the second `for` loop in `fix-json.ts`. - for state in self.stack.iter().rev() { - match state { - FixState::InsideString => result.push('"'), - FixState::InsideObjectKey - | FixState::InsideObjectAfterKey - | FixState::InsideObjectAfterComma - | FixState::InsideObjectStart - | FixState::InsideObjectBeforeValue - | FixState::InsideObjectAfterValue => result.push('}'), - FixState::InsideArrayStart - | FixState::InsideArrayAfterComma - | FixState::InsideArrayAfterValue => result.push(']'), - FixState::InsideLiteral => { - let start = self - .literal_start - .expect("literal_start is set whenever INSIDE_LITERAL is pushed"); - let partial: String = self.chars[start..].iter().collect(); - let plen = partial.chars().count(); - if "true".starts_with(&partial) { - result.push_str(&"true"[plen..]); - } else if "false".starts_with(&partial) { - result.push_str(&"false"[plen..]); - } else if "null".starts_with(&partial) { - result.push_str(&"null"[plen..]); - } - } - // ROOT / FINISH / INSIDE_NUMBER / *_ESCAPE / *_UNICODE_ESCAPE - // have no closing token in the TS source. - _ => {} - } + fn finish(self, input: &str) -> String { + let closing: usize = self.open.iter().map(|frame| frame.closing().len()).sum(); + let mut out = String::with_capacity(self.valid_len + closing); + // `valid_len` only ever ends a character from `char_indices`, so it + // is a char boundary. + out.push_str(&input[..self.valid_len]); + for frame in self.open.iter().rev() { + out.push_str(frame.closing()); } - - result + out } } -/// Repairs a (possibly partial) JSON string into valid JSON. +/// Outcome of [`PartialJson::parse`]. /// -/// Port of `fixJson` in `packages/ai/src/util/fix-json.ts`. -#[must_use] -pub fn fix_json(input: &str) -> String { - let chars: Vec = input.chars().collect(); - let mut fixer = Fixer::new(&chars); - fixer.run(); - fixer.finish() -} - -// =========================================================================== -// parsePartialJson <- packages/ai/src/util/parse-partial-json.ts -// =========================================================================== - -/// Outcome category returned by [`parse_partial_json`]. Mirrors the `state` -/// union in `parse-partial-json.ts`. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ParsePartialJsonState { - /// `undefined` was passed as input. - UndefinedInput, - /// The input parsed as-is, without repair. - SuccessfulParse, - /// The input only parsed after running [`fix_json`]. - RepairedParse, - /// The input could not be parsed even after repair. - FailedParse, -} - -/// Result of [`parse_partial_json`]. -#[derive(Debug, Clone, PartialEq)] -pub struct ParsePartialJsonResult { - pub value: Option, - pub state: ParsePartialJsonState, -} - -/// Best-effort parse of a (possibly partial) JSON string. +/// ``` +/// use aimux_core::json_repair::PartialJson; +/// use serde_json::json; /// -/// Port of `parsePartialJson` in `packages/ai/src/util/parse-partial-json.ts`. +/// // A streamed tool-call argument cut off mid-way. +/// let args = PartialJson::parse(r#"{"city": "Par"#); +/// assert_eq!(args, PartialJson::Repaired(json!({"city": "Par"}))); +/// assert_eq!(args.value(), Some(&json!({"city": "Par"}))); /// -/// The TS original is `async` only because its `safeParseJSON` helper is -/// async; the Rust port is synchronous (it uses `serde_json::from_str` as the -/// equivalent of `safeParseJSON`). -#[must_use] -pub fn parse_partial_json(json_text: Option<&str>) -> ParsePartialJsonResult { - let Some(text) = json_text else { - return ParsePartialJsonResult { - value: None, - state: ParsePartialJsonState::UndefinedInput, - }; - }; +/// assert_eq!(PartialJson::parse("[1, 2]").into_value(), Some(json!([1, 2]))); +/// assert_eq!(PartialJson::parse("not json"), PartialJson::Failed); +/// +/// // Upstream's `undefined-input` state is the caller's `Option`: +/// let text: Option<&str> = None; +/// assert_eq!(text.map(PartialJson::parse), None); +/// ``` +#[doc(alias = "parsePartialJson")] +#[derive(Debug, Clone, PartialEq)] +#[non_exhaustive] +pub enum PartialJson { + /// The text parsed as-is (upstream `successful-parse`). + Parsed(Value), + /// The text parsed only after [`fix_json`] (upstream `repaired-parse`). + Repaired(Value), + /// The text did not parse even after repair (upstream `failed-parse`). + Failed, +} - // First try: parse the raw text (== `safeParseJSON({ text })`). - if let Ok(value) = serde_json::from_str::(text) { - return ParsePartialJsonResult { - value: Some(value), - state: ParsePartialJsonState::SuccessfulParse, - }; +impl PartialJson { + /// Parses `text`, retrying with [`fix_json`] if it does not parse as-is. + /// + /// Upstream's `parsePartialJson` is `async` only because its JSON parser + /// is; this is synchronous. + #[must_use] + pub fn parse(text: &str) -> Self { + if let Ok(value) = serde_json::from_str(text) { + return Self::Parsed(value); + } + match serde_json::from_str(&fix_json(text)) { + Ok(value) => Self::Repaired(value), + Err(_) => Self::Failed, + } } - // Second try: repair then parse (== `safeParseJSON({ text: fixJson(text) })`). - let fixed = fix_json(text); - if let Ok(value) = serde_json::from_str::(&fixed) { - return ParsePartialJsonResult { - value: Some(value), - state: ParsePartialJsonState::RepairedParse, - }; + /// The parsed value, repaired or not. + #[must_use] + pub fn value(&self) -> Option<&Value> { + match self { + Self::Parsed(value) | Self::Repaired(value) => Some(value), + Self::Failed => None, + } } - ParsePartialJsonResult { - value: None, - state: ParsePartialJsonState::FailedParse, + /// Takes the parsed value, repaired or not. + #[must_use] + pub fn into_value(self) -> Option { + match self { + Self::Parsed(value) | Self::Repaired(value) => Some(value), + Self::Failed => None, + } } } diff --git a/aimux-core/src/util.rs b/aimux-core/src/util.rs index c2f68c41f..aa9c0e958 100644 --- a/aimux-core/src/util.rs +++ b/aimux-core/src/util.rs @@ -2,14 +2,12 @@ //! //! This module is a facade β€” the implementations live in dedicated modules: //! -//! - `json_repair` β€” `fix_json` + `parse_partial_json` (from `fix-json.ts` +//! - `json_repair` β€” `fix_json` + `PartialJson::parse` (from `fix-json.ts` //! and `parse-partial-json.ts`) //! - `math` β€” `cosine_similarity` (from `cosine-similarity.ts`) //! //! The re-exports below keep `aimux_core::util::*` paths working for existing //! consumers. -pub use crate::json_repair::{ - ParsePartialJsonResult, ParsePartialJsonState, fix_json, parse_partial_json, -}; +pub use crate::json_repair::{PartialJson, fix_json}; pub use crate::math::{UtilError, cosine_similarity}; diff --git a/aimux-core/tests/fixtures/json_repair_cases.jsonl b/aimux-core/tests/fixtures/json_repair_cases.jsonl new file mode 100644 index 000000000..23be02d0f --- /dev/null +++ b/aimux-core/tests/fixtures/json_repair_cases.jsonl @@ -0,0 +1,740 @@ +{"input":"","fixed":"","state":"failed"} +{"input":"-","fixed":"","state":"failed"} +{"input":"-.","fixed":"","state":"failed"} +{"input":"1e","fixed":"1","state":"repaired","value":1} +{"input":"\"\\u","fixed":"\"\"","state":"repaired","value":""} +{"input":"\"\\u00","fixed":"\"\"","state":"repaired","value":""} +{"input":"\"\\ud83d","fixed":"\"\\ud83d\"","state":"failed"} +{"input":"\"\\uD83D\\uDE00","fixed":"\"\\uD83D\\uDE00\"","state":"repaired","value":"πŸ˜€"} +{"input":"\"πŸ˜€","fixed":"\"πŸ˜€\"","state":"repaired","value":"πŸ˜€"} +{"input":"[\"π„ž","fixed":"[\"π„ž\"]","state":"repaired","value":["π„ž"]} +{"input":"{\"a\":tr","fixed":"{\"a\":true}","state":"repaired","value":{"a":true}} +{"input":"[nu","fixed":"[null]","state":"repaired","value":[null]} +{"input":"[[[{\"k\":[fals","fixed":"[[[{\"k\":[false]}]]]","state":"repaired","value":[[[{"k":[false]}]]]} +{"input":"{\"__proto__\":1}","fixed":"{\"__proto__\":1}","state":"parsed","value":{"__proto__":1}} +{"input":"9007199254740993","fixed":"9007199254740993","state":"parsed","value":9007199254740993} +{"input":"{\"a\":1,","fixed":"{\"a\":1}","state":"repaired","value":{"a":1}} +{"input":"[1,","fixed":"[1]","state":"repaired","value":[1]} +{"input":"{\"a\"","fixed":"{}","state":"repaired","value":{}} +{"input":"{\"a\":","fixed":"{}","state":"repaired","value":{}} +{"input":"\"abc\\","fixed":"\"abc\"","state":"repaired","value":"abc"} +{"input":" ","fixed":"","state":"failed"} +{"input":"}","fixed":"","state":"failed"} +{"input":"]","fixed":"","state":"failed"} +{"input":"x","fixed":"","state":"failed"} +{"input":"[1]x","fixed":"[1]","state":"repaired","value":[1]} +{"input":"{\"a\":\"b\"}junk","fixed":"{\"a\":\"b\"}","state":"repaired","value":{"a":"b"}} +{"input":"++","fixed":"","state":"failed"} +{"input":"+,","fixed":"","state":"failed"} +{"input":"e+-","fixed":"","state":"failed"} +{"input":"+.","fixed":"","state":"failed"} +{"input":"+:","fixed":"","state":"failed"} +{"input":"+[","fixed":"+[]","state":"failed"} +{"input":"+]","fixed":"","state":"failed"} +{"input":"+a","fixed":"","state":"failed"} +{"input":"+πŸ˜€","fixed":"","state":"failed"} +{"input":"\\n+\\","fixed":"\\n","state":"failed"} +{"input":"n+9","fixed":"n","state":"failed"} +{"input":"+1","fixed":"+1","state":"failed"} +{"input":"+e","fixed":"","state":"failed"} +{"input":"+\u0000","fixed":"","state":"failed"} +{"input":"+\"","fixed":"+\"\"","state":"failed"} +{"input":"+\t","fixed":"","state":"failed"} +{"input":"+{","fixed":"+{}","state":"failed"} +{"input":":+}","fixed":"","state":"failed"} +{"input":"{+}","fixed":"{+}","state":"failed"} +{"input":",+","fixed":"","state":"failed"} +{"input":",,","fixed":"","state":"failed"} +{"input":",-","fixed":"","state":"failed"} +{"input":",.","fixed":"","state":"failed"} +{"input":",:","fixed":"","state":"failed"} +{"input":",[","fixed":",[]","state":"failed"} +{"input":",]","fixed":"","state":"failed"} +{"input":",n","fixed":",null","state":"failed"} +{"input":",πŸ˜€","fixed":"","state":"failed"} +{"input":",\\","fixed":"","state":"failed"} +{"input":"f[,1","fixed":"f","state":"failed"} +{"input":",0","fixed":",0","state":"failed"} +{"input":",E","fixed":"","state":"failed"} +{"input":",\u0000","fixed":"","state":"failed"} +{"input":",\"","fixed":",\"\"","state":"failed"} +{"input":"\r\"\n,\"","fixed":"\r\"\n,\"","state":"failed"} +{"input":", ","fixed":"","state":"failed"} +{"input":",{","fixed":",{}","state":"failed"} +{"input":",}","fixed":"","state":"failed"} +{"input":"{,}","fixed":"{,}","state":"failed"} +{"input":"-+","fixed":"","state":"failed"} +{"input":"-,","fixed":"","state":"failed"} +{"input":"--","fixed":"","state":"failed"} +{"input":"-:","fixed":"","state":"failed"} +{"input":"-[","fixed":"","state":"failed"} +{"input":"-]","fixed":"","state":"failed"} +{"input":"[\\\\-]","fixed":"[\\\\-]","state":"failed"} +{"input":"-x","fixed":"","state":"failed"} +{"input":"-π„ž","fixed":"","state":"failed"} +{"input":"-\\","fixed":"","state":"failed"} +{"input":"\\\"-9","fixed":"\\\"-9\"","state":"failed"} +{"input":"--0","fixed":"--0","state":"failed"} +{"input":"-e","fixed":"","state":"failed"} +{"input":"-'","fixed":"","state":"failed"} +{"input":"-\"","fixed":"","state":"failed"} +{"input":"\"\\\\valu\\-\"","fixed":"\"\\\\valu\\-\"","state":"failed"} +{"input":"-\n","fixed":"","state":"failed"} +{"input":"-{","fixed":"","state":"failed"} +{"input":"-}","fixed":"","state":"failed"} +{"input":"{-}","fixed":"{-}","state":"failed"} +{"input":".+","fixed":"","state":"failed"} +{"input":".,","fixed":"","state":"failed"} +{"input":".-","fixed":"","state":"failed"} +{"input":"..","fixed":"","state":"failed"} +{"input":".:","fixed":"","state":"failed"} +{"input":"+.[","fixed":"+.[]","state":"failed"} +{"input":".]","fixed":"","state":"failed"} +{"input":"[70.]","fixed":"[70.]","state":"failed"} +{"input":".u","fixed":"","state":"failed"} +{"input":".πŸ˜€","fixed":"","state":"failed"} +{"input":".\\","fixed":"","state":"failed"} +{"input":"f.0","fixed":"f","state":"failed"} +{"input":".1","fixed":".1","state":"failed"} +{"input":".E","fixed":"","state":"failed"} +{"input":".'","fixed":"","state":"failed"} +{"input":".\"","fixed":".\"\"","state":"failed"} +{"input":". ","fixed":"","state":"failed"} +{"input":".{","fixed":".{}","state":"failed"} +{"input":".}","fixed":"","state":"failed"} +{"input":"{\t.}","fixed":"{\t.}","state":"failed"} +{"input":":+","fixed":"","state":"failed"} +{"input":":,","fixed":"","state":"failed"} +{"input":":-","fixed":"","state":"failed"} +{"input":":.","fixed":"","state":"failed"} +{"input":"::","fixed":"","state":"failed"} +{"input":":[","fixed":":[]","state":"failed"} +{"input":":]","fixed":"","state":"failed"} +{"input":"[\\n+\"__proto__\":]","fixed":"[\\n+\"__proto__\":]","state":"failed"} +{"input":":a","fixed":"","state":"failed"} +{"input":":πŸ˜€","fixed":"","state":"failed"} +{"input":":\\","fixed":"","state":"failed"} +{"input":"f:0","fixed":"f","state":"failed"} +{"input":":0","fixed":":0","state":"failed"} +{"input":":e","fixed":"","state":"failed"} +{"input":":/","fixed":"","state":"failed"} +{"input":":\"","fixed":":\"\"","state":"failed"} +{"input":"}\":\"","fixed":"}\":\"","state":"failed"} +{"input":":\r","fixed":"","state":"failed"} +{"input":":{","fixed":":{}","state":"failed"} +{"input":":}","fixed":"","state":"failed"} +{"input":"\r\n{\r\n\"truekey\\/}\":\r\n-1230\"__proto__\":}","fixed":"\r\n{\r\n\"truekey\\/}\":\r\n-1230\"__proto__\":}","state":"failed"} +{"input":"[+","fixed":"[+]","state":"failed"} +{"input":"[,","fixed":"[,]","state":"failed"} +{"input":"[-","fixed":"[-]","state":"failed"} +{"input":"[.","fixed":"[.]","state":"failed"} +{"input":"[:","fixed":"[:]","state":"failed"} +{"input":"//[[","fixed":"//[[]]","state":"failed"} +{"input":"'1E\\n[]","fixed":"'1","state":"failed"} +{"input":"NaN[]","fixed":"NaN[]","state":"failed"} +{"input":"[a","fixed":"[a]","state":"failed"} +{"input":"[π„ž","fixed":"[π„ž]","state":"failed"} +{"input":"[\\","fixed":"[\\]","state":"failed"} +{"input":":[0","fixed":":[0]","state":"failed"} +{"input":"[e","fixed":"[e]","state":"failed"} +{"input":"['","fixed":"[']","state":"failed"} +{"input":":[\"","fixed":":[\"\"]","state":"failed"} +{"input":"\"\\[\"","fixed":"\"\\[\"","state":"failed"} +{"input":"][\n","fixed":"][\n]","state":"failed"} +{"input":"u[{","fixed":"u[{}]","state":"failed"} +{"input":"[}","fixed":"[}]","state":"failed"} +{"input":"{\n\"δΈ­Γ©\": true,\"\":[]\r\n,\"\":\"1\"\t[}","fixed":"{\n\"δΈ­Γ©\": true,\"\":[]\r\n,\"\":\"1\"\t[}","state":"failed"} +{"input":"]+","fixed":"","state":"failed"} +{"input":"],","fixed":"","state":"failed"} +{"input":"]-","fixed":"","state":"failed"} +{"input":"].","fixed":"","state":"failed"} +{"input":"]:","fixed":"","state":"failed"} +{"input":"][","fixed":"][]","state":"failed"} +{"input":"]]","fixed":"","state":"failed"} +{"input":"[[1-e+21]]","fixed":"[[1-e+21]]","state":"failed"} +{"input":"]u","fixed":"","state":"failed"} +{"input":"]π„ž","fixed":"","state":"failed"} +{"input":"]\\","fixed":"","state":"failed"} +{"input":"f]9","fixed":"f","state":"failed"} +{"input":"]0","fixed":"]0","state":"failed"} +{"input":"]e","fixed":"","state":"failed"} +{"input":"]/","fixed":"","state":"failed"} +{"input":"]\"","fixed":"]\"\"","state":"failed"} +{"input":"\"\n]\"","fixed":"\"\n]\"","state":"failed"} +{"input":"]\r","fixed":"","state":"failed"} +{"input":"]{","fixed":"]{}","state":"failed"} +{"input":"-]}","fixed":"","state":"failed"} +{"input":"{]}","fixed":"{]}","state":"failed"} +{"input":"n+","fixed":"n","state":"failed"} +{"input":"u,","fixed":"","state":"failed"} +{"input":"n-","fixed":"n","state":"failed"} +{"input":"a.","fixed":"","state":"failed"} +{"input":"u:","fixed":"","state":"failed"} +{"input":"a[","fixed":"a[]","state":"failed"} +{"input":"a]","fixed":"","state":"failed"} +{"input":"[unll]","fixed":"[unll]","state":"failed"} +{"input":"ff","fixed":"f","state":"failed"} +{"input":"\\null","fixed":"\\null","state":"failed"} +{"input":"uπŸ˜€","fixed":"","state":"failed"} +{"input":"t\\","fixed":"t","state":"failed"} +{"input":"f0","fixed":"f","state":"failed"} +{"input":"a1","fixed":"a1","state":"failed"} +{"input":"ae","fixed":"","state":"failed"} +{"input":"+true","fixed":"+true","state":"failed"} +{"input":"f'","fixed":"f","state":"failed"} +{"input":"f\"","fixed":"f","state":"failed"} +{"input":"b\"a\"","fixed":"b\"a\"","state":"failed"} +{"input":"t\r","fixed":"t","state":"failed"} +{"input":"n{","fixed":"n","state":"failed"} +{"input":"f}","fixed":"f","state":"failed"} +{"input":"{u}","fixed":"{u}","state":"failed"} +{"input":"πŸ˜€+","fixed":"","state":"failed"} +{"input":"πŸ˜€,","fixed":"","state":"failed"} +{"input":"π„ž-","fixed":"","state":"failed"} +{"input":"π„ž.","fixed":"","state":"failed"} +{"input":"π„ž:","fixed":"","state":"failed"} +{"input":"π„ž[","fixed":"π„ž[]","state":"failed"} +{"input":"πŸ˜€]","fixed":"","state":"failed"} +{"input":"[ nul\\\\π„ž]","fixed":"[ nul\\\\π„ž]","state":"failed"} +{"input":"π„žu","fixed":"","state":"failed"} +{"input":"πŸ˜€π„ž","fixed":"","state":"failed"} +{"input":"πŸ˜€\\","fixed":"","state":"failed"} +{"input":"tπ„ž0","fixed":"t","state":"failed"} +{"input":"π„ž1","fixed":"π„ž1","state":"failed"} +{"input":"π„žE","fixed":"","state":"failed"} +{"input":"πŸ˜€/","fixed":"","state":"failed"} +{"input":"πŸ˜€\"","fixed":"πŸ˜€\"\"","state":"failed"} +{"input":"b\"π„ž\"","fixed":"b\"π„ž\"","state":"failed"} +{"input":"πŸ˜€ ","fixed":"","state":"failed"} +{"input":"π„ž{","fixed":"π„ž{}","state":"failed"} +{"input":"\nπŸ˜€}","fixed":"","state":"failed"} +{"input":"\r\n{π„ž}","fixed":"\r\n{π„ž}","state":"failed"} +{"input":"\\+","fixed":"","state":"failed"} +{"input":"\\,","fixed":"","state":"failed"} +{"input":"\\-","fixed":"","state":"failed"} +{"input":"\\.","fixed":"","state":"failed"} +{"input":"\\:","fixed":"","state":"failed"} +{"input":"\\[","fixed":"\\[]","state":"failed"} +{"input":"\\]","fixed":"","state":"failed"} +{"input":"[ \"n\"\\]","fixed":"[ \"n\"\\]","state":"failed"} +{"input":"\\n","fixed":"\\null","state":"failed"} +{"input":"\\πŸ˜€","fixed":"","state":"failed"} +{"input":"\\\\","fixed":"","state":"failed"} +{"input":"\"\\1","fixed":"\"\\1\"","state":"failed"} +{"input":"\\1","fixed":"\\1","state":"failed"} +{"input":"\\e","fixed":"","state":"failed"} +{"input":"\\'","fixed":"","state":"failed"} +{"input":"\\\"","fixed":"\\\"\"","state":"failed"} +{"input":"}\"πŸ˜€\\\\\"","fixed":"}\"πŸ˜€\\\\\"","state":"failed"} +{"input":"\\\t","fixed":"","state":"failed"} +{"input":"\\{","fixed":"\\{}","state":"failed"} +{"input":"\\}","fixed":"","state":"failed"} +{"input":"{\\}","fixed":"{\\}","state":"failed"} +{"input":"\u00000+","fixed":"\u00000","state":"failed"} +{"input":"n0,","fixed":"n","state":"failed"} +{"input":":1-","fixed":":1","state":"failed"} +{"input":"00.","fixed":"00","state":"failed"} +{"input":"e0:","fixed":"e0","state":"failed"} +{"input":"f0[","fixed":"f","state":"failed"} +{"input":"t1]","fixed":"t","state":"failed"} +{"input":"+
[0]","fixed":"+
[0]","state":"failed"} +{"input":"n9f","fixed":"n","state":"failed"} +{"input":".0πŸ˜€","fixed":".0","state":"failed"} +{"input":"/9\\","fixed":"/9","state":"failed"} +{"input":"t42","fixed":"t","state":"failed"} +{"input":"00","fixed":"00","state":"failed"} +{"input":"f1E","fixed":"f","state":"failed"} +{"input":"/1\u0000","fixed":"/1","state":"failed"} +{"input":"t1\"","fixed":"t","state":"failed"} +{"input":"\\\\\"1\"","fixed":"\\\\\"1\"","state":"failed"} +{"input":".0\n","fixed":".0","state":"failed"} +{"input":"09{","fixed":"09","state":"failed"} +{"input":"'1}","fixed":"'1","state":"failed"} +{"input":"{\rae42}","fixed":"{\rae42}","state":"failed"} +{"input":"e+","fixed":"","state":"failed"} +{"input":"e,","fixed":"","state":"failed"} +{"input":"E-","fixed":"","state":"failed"} +{"input":"E.","fixed":"","state":"failed"} +{"input":"E:","fixed":"","state":"failed"} +{"input":"E[","fixed":"E[]","state":"failed"} +{"input":"E]","fixed":"","state":"failed"} +{"input":"ru[te]","fixed":"ru[te]","state":"failed"} +{"input":"ea","fixed":"","state":"failed"} +{"input":"eπŸ˜€","fixed":"","state":"failed"} +{"input":"E\\","fixed":"","state":"failed"} +{"input":"tE1","fixed":"t","state":"failed"} +{"input":"e1","fixed":"e1","state":"failed"} +{"input":"Ee","fixed":"","state":"failed"} +{"input":"e'","fixed":"","state":"failed"} +{"input":"e\"","fixed":"e\"\"","state":"failed"} +{"input":"ru\"te\"","fixed":"ru\"te\"","state":"failed"} +{"input":"e\t","fixed":"","state":"failed"} +{"input":"e{","fixed":"e{}","state":"failed"} +{"input":"E}","fixed":"","state":"failed"} +{"input":"{\\true}","fixed":"{\\true}","state":"failed"} +{"input":"+","fixed":"","state":"failed"} +{"input":",","fixed":"","state":"failed"} +{"input":".","fixed":"","state":"failed"} +{"input":":","fixed":"","state":"failed"} +{"input":"π„ž","fixed":"","state":"failed"} +{"input":"\\","fixed":"","state":"failed"} +{"input":"E","fixed":"","state":"failed"} +{"input":"'","fixed":"","state":"failed"} +{"input":"\n","fixed":"","state":"failed"} +{"input":"/+","fixed":"","state":"failed"} +{"input":"\u0000,","fixed":"","state":"failed"} +{"input":"/-","fixed":"","state":"failed"} +{"input":"/.","fixed":"","state":"failed"} +{"input":"\u0000:","fixed":"","state":"failed"} +{"input":"\u0000[","fixed":"\u0000[]","state":"failed"} +{"input":"']","fixed":"","state":"failed"} +{"input":"\n[\\ud83d']","fixed":"\n[\\ud83d']","state":"failed"} +{"input":"'f","fixed":"'false","state":"failed"} +{"input":"'π„ž","fixed":"","state":"failed"} +{"input":"\u0000\\","fixed":"","state":"failed"} +{"input":"n'9","fixed":"n","state":"failed"} +{"input":"/0","fixed":"/0","state":"failed"} +{"input":"'E","fixed":"","state":"failed"} +{"input":"/'","fixed":"","state":"failed"} +{"input":"/\"","fixed":"/\"\"","state":"failed"} +{"input":"\\\"/\"","fixed":"\\\"/\"","state":"failed"} +{"input":"'\n","fixed":"","state":"failed"} +{"input":"\u0000{","fixed":"\u0000{}","state":"failed"} +{"input":"'}","fixed":"","state":"failed"} +{"input":"{/}","fixed":"{/}","state":"failed"} +{"input":"\\\"+","fixed":"\\\"+\"","state":"failed"} +{"input":"\\\",","fixed":"\\\",\"","state":"failed"} +{"input":"\\\"-","fixed":"\\\"-\"","state":"failed"} +{"input":"a\".","fixed":"a\".\"","state":"failed"} +{"input":"]\"\":","fixed":"]\"\"","state":"failed"} +{"input":"-\"[","fixed":"","state":"failed"} +{"input":"\\\"]","fixed":"\\\"]\"","state":"failed"} +{"input":"[\"\t\"]","fixed":"[\"\t\"]","state":"failed"} +{"input":"\\\"x","fixed":"\\\"x\"","state":"failed"} +{"input":"\\\"πŸ˜€","fixed":"\\\"πŸ˜€\"","state":"failed"} +{"input":"\\\"\\","fixed":"\\\"\"","state":"failed"} +{"input":"\\\"1","fixed":"\\\"1\"","state":"failed"} +{"input":"\\\"E","fixed":"\\\"E\"","state":"failed"} +{"input":"\"\u0000","fixed":"\"\u0000\"","state":"failed"} +{"input":"\\\"\"\"","fixed":"\\\"\"","state":"failed"} +{"input":"]\"\"","fixed":"]\"\"","state":"failed"} +{"input":"\"\t","fixed":"\"\t\"","state":"failed"} +{"input":"\\\"{","fixed":"\\\"{\"","state":"failed"} +{"input":"\\\"}","fixed":"\\\"}\"","state":"failed"} +{"input":"{\"\":a\":a\"}","fixed":"{\"\":a\":a\"}","state":"failed"} +{"input":" +","fixed":"","state":"failed"} +{"input":"\r,","fixed":"","state":"failed"} +{"input":" -","fixed":"","state":"failed"} +{"input":"\n.","fixed":"","state":"failed"} +{"input":"\r:","fixed":"","state":"failed"} +{"input":"]\n[","fixed":"]\n[]","state":"failed"} +{"input":"\t]","fixed":"","state":"failed"} +{"input":"[nllu\t]","fixed":"[nllu\t]","state":"failed"} +{"input":"\rx","fixed":"","state":"failed"} +{"input":"\tπŸ˜€","fixed":"","state":"failed"} +{"input":"\t\\","fixed":"","state":"failed"} +{"input":"-\n7","fixed":"","state":"failed"} +{"input":"+\n0","fixed":"+\n0","state":"failed"} +{"input":"\rE","fixed":"","state":"failed"} +{"input":"\n\u0000","fixed":"","state":"failed"} +{"input":".\n\"","fixed":".\n\"\"","state":"failed"} +{"input":"\"\t\"","fixed":"\"\t\"","state":"failed"} +{"input":"\r\n","fixed":"","state":"failed"} +{"input":"}\t{","fixed":"}\t{}","state":"failed"} +{"input":"\n}","fixed":"","state":"failed"} +{"input":"{a\"\":-7 }","fixed":"{a\"\":-7 }","state":"failed"} +{"input":"]/ {+","fixed":"]/ {}","state":"failed"} +{"input":"t{,","fixed":"t","state":"failed"} +{"input":"fals{-","fixed":"fals","state":"failed"} +{"input":".{.","fixed":".{}","state":"failed"} +{"input":"e{:","fixed":"e{}","state":"failed"} +{"input":",{[","fixed":",{}","state":"failed"} +{"input":"+{]","fixed":"+{}","state":"failed"} +{"input":"[ \",{} , \"\\\"{\\n\" \"\\uD83D\\uDE00{]","fixed":"[ \",{} , \"\\\"{\\n\" \"\\uD83D\\uDE00{]","state":"failed"} +{"input":"'{f","fixed":"'{}","state":"failed"} +{"input":"t{πŸ˜€","fixed":"t","state":"failed"} +{"input":"\\{\\","fixed":"\\{}","state":"failed"} +{"input":"n{9","fixed":"n","state":"failed"} +{"input":".[\t{E","fixed":".[\t{}]","state":"failed"} +{"input":"t{\u0000","fixed":"t","state":"failed"} +{"input":"πŸ˜€[{\"","fixed":"πŸ˜€[{}]","state":"failed"} +{"input":"\"\t1{\"","fixed":"\"\t1{\"","state":"failed"} +{"input":"+{\t","fixed":"+{}","state":"failed"} +{"input":"n{{","fixed":"n","state":"failed"} +{"input":"[{[{}","fixed":"[{[{}]","state":"failed"} +{"input":"{{}","fixed":"{{}","state":"failed"} +{"input":"}+","fixed":"","state":"failed"} +{"input":"},","fixed":"","state":"failed"} +{"input":"}-","fixed":"","state":"failed"} +{"input":"}.","fixed":"","state":"failed"} +{"input":"}:","fixed":"","state":"failed"} +{"input":"}[","fixed":"}[]","state":"failed"} +{"input":"}]","fixed":"","state":"failed"} +{"input":"[{}[{}]","fixed":"[{}[{}]","state":"failed"} +{"input":"}a","fixed":"","state":"failed"} +{"input":"}π„ž","fixed":"","state":"failed"} +{"input":"}\\","fixed":"","state":"failed"} +{"input":"f}0","fixed":"f","state":"failed"} +{"input":"}1","fixed":"}1","state":"failed"} +{"input":"}e","fixed":"","state":"failed"} +{"input":"}\u0000","fixed":"","state":"failed"} +{"input":"}\"","fixed":"}\"\"","state":"failed"} +{"input":"\"t\\}\"","fixed":"\"t\\}\"","state":"failed"} +{"input":"}\t","fixed":"","state":"failed"} +{"input":"}{","fixed":"}{}","state":"failed"} +{"input":"}}","fixed":"","state":"failed"} +{"input":"{\"\t{\":{}}","fixed":"{\"\t{\":{}}","state":"failed"} +{"input":"0e+1","fixed":"0","state":"parsed","value":0.0} +{"input":"\",\"","fixed":"\",\"","state":"parsed","value":","} +{"input":"-1","fixed":"-1","state":"parsed","value":-1} +{"input":"1.0","fixed":"1.0","state":"parsed","value":1.0} +{"input":"\"construc9.\"","fixed":"\"construc9.\"","state":"parsed","value":"construc9."} +{"input":"\":\"","fixed":"\":\"","state":"parsed","value":":"} +{"input":"[]","fixed":"[]","state":"parsed","value":[]} +{"input":"\"[\"","fixed":"\"[\"","state":"parsed","value":"["} +{"input":"[[]]","fixed":"[[]]","state":"parsed","value":[[]]} +{"input":"\"]\"","fixed":"\"]\"","state":"parsed","value":"]"} +{"input":"[] ","fixed":"[]","state":"parsed","value":[]} +{"input":"{\"}\":[]}","fixed":"{\"}\":[]}","state":"parsed","value":{"}":[]}} +{"input":"[null]","fixed":"[null]","state":"parsed","value":[null]} +{"input":"null","fixed":"null","state":"parsed","value":null} +{"input":"true","fixed":"true","state":"parsed","value":true} +{"input":"\"b\"","fixed":"\"b\"","state":"parsed","value":"b"} +{"input":"null\t","fixed":"null","state":"parsed","value":null} +{"input":"{ \"[\\\"\\u0041:\":null}","fixed":"{ \"[\\\"\\u0041:\":null}\"}","state":"parsed","value":{"[\"A:":null}} +{"input":"{\"\":null}","fixed":"{\"\":null}","state":"parsed","value":{"":null}} +{"input":"\"πŸ˜€\"","fixed":"\"πŸ˜€\"","state":"parsed","value":"πŸ˜€"} +{"input":"\"\\\\\"","fixed":"\"\\\\\"","state":"parsed","value":"\\"} +{"input":"[0]","fixed":"[0]","state":"parsed","value":[0]} +{"input":"1e+21","fixed":"1","state":"parsed","value":1e+21} +{"input":"10","fixed":"10","state":"parsed","value":10} +{"input":"\"1\"","fixed":"\"1\"","state":"parsed","value":"1"} +{"input":"1\t","fixed":"1","state":"parsed","value":1} +{"input":"{\"\\\":Γ©\":-7.000001E-21}","fixed":"{\"\\\":Γ©\":-7.000001E-21}\"}","state":"parsed","value":{"\":Γ©":-7.000001e-21}} +{"input":"{\"\":1}","fixed":"{\"\":1}","state":"parsed","value":{"":1}} +{"input":"[true]","fixed":"[true]","state":"parsed","value":[true]} +{"input":"7E2","fixed":"7E2","state":"parsed","value":700.0} +{"input":"\"ke\"","fixed":"\"ke\"","state":"parsed","value":"ke"} +{"input":"true ","fixed":"true","state":"parsed","value":true} +{"input":"{\"\":true}","fixed":"{\"\":true}","state":"parsed","value":{"":true}} +{"input":"7","fixed":"7","state":"parsed","value":7} +{"input":"\"\\/\"","fixed":"\"\\/\"","state":"parsed","value":"/"} +{"input":"[\"\"]","fixed":"[\"\"]","state":"parsed","value":[""]} +{"input":"\"\"","fixed":"\"\"","state":"parsed","value":""} +{"input":"\"\" ","fixed":"\"\"","state":"parsed","value":""} +{"input":"{\"\\\":\":{},\"nullΓ©\\uD83D\\uDE00\" :\r\n\"constructorπ„ž,\"}","fixed":"{\"\\\":\":{},\"}","state":"parsed","value":{"\":":{},"nullΓ©πŸ˜€":"constructorπ„ž,"}} +{"input":"{\"\":\"\"}","fixed":"{\"\":\"\"}","state":"parsed","value":{"":""}} +{"input":"[ ]","fixed":"[ ]","state":"parsed","value":[]} +{"input":" 9","fixed":" 9","state":"parsed","value":9} +{"input":"\" \"","fixed":"\" \"","state":"parsed","value":" "} +{"input":"1 ","fixed":"1","state":"parsed","value":1} +{"input":"{\t\"[\\/\": false, \"\\\":\":\n{}\r\n}","fixed":"{\t\"[\\/\": false, \"\\\":\":\n{}\r\n}\"}","state":"parsed","value":{"[/":false,"\":":{}}} +{"input":"{\t}","fixed":"{\t}","state":"parsed","value":{}} +{"input":"\"{\"","fixed":"\"{\"","state":"parsed","value":"{"} +{"input":"{}","fixed":"{}","state":"parsed","value":{}} +{"input":"[{}]","fixed":"[{}]","state":"parsed","value":[{}]} +{"input":"\"}\"","fixed":"\"}\"","state":"parsed","value":"}"} +{"input":"{}\n","fixed":"{}","state":"parsed","value":{}} +{"input":"{\r\n\"1key\\u0041\":\t[ true,true,true],\"__proto__a\\\":\" : {\"valueb\\u0041key\"\t: []}}","fixed":"{\r\n\"1key\\u0041\":\t[ true,true,true],\"__proto__a\\\":\" : {\"valueb\\u0041key\"\t: []}","state":"parsed","value":{"1keyA":[true,true,true],"__proto__a\":":{"valuebAkey":[]}}} +{"input":"{\"\":{}}","fixed":"{\"\":{}}","state":"parsed","value":{"":{}}} +{"input":"7e++","fixed":"7","state":"repaired","value":7} +{"input":"0E+,","fixed":"0","state":"repaired","value":0} +{"input":"0E+-","fixed":"0","state":"repaired","value":0} +{"input":"{+.","fixed":"{}","state":"repaired","value":{}} +{"input":"\"\"+:","fixed":"\"\"","state":"repaired","value":""} +{"input":"{}+[","fixed":"{}","state":"repaired","value":{}} +{"input":"\"+]","fixed":"\"+]\"","state":"repaired","value":"+]"} +{"input":"7+a","fixed":"7","state":"repaired","value":7} +{"input":"\"\\u\u0000+π„ž","fixed":"\"\"","state":"repaired","value":""} +{"input":"4+\\","fixed":"4","state":"repaired","value":4} +{"input":"{\u0000+1","fixed":"{}","state":"repaired","value":{}} +{"input":"0:+E","fixed":"0","state":"repaired","value":0} +{"input":"{}+'","fixed":"{}","state":"repaired","value":{}} +{"input":"1+\"","fixed":"1","state":"repaired","value":1} +{"input":"0+\r","fixed":"0","state":"repaired","value":0} +{"input":" {\"\"\r\n:falsefalse+{","fixed":" {\"\"\r\n:false}","state":"repaired","value":{"":false}} +{"input":"0+}","fixed":"0","state":"repaired","value":0} +{"input":"\"[,+","fixed":"\"[,+\"","state":"repaired","value":"[,+"} +{"input":"\",,","fixed":"\",,\"","state":"repaired","value":",,"} +{"input":"\t\",-","fixed":"\t\",-\"","state":"repaired","value":",-"} +{"input":"42,.","fixed":"42","state":"repaired","value":42} +{"input":"\",:","fixed":"\",:\"","state":"repaired","value":",:"} +{"input":"\",[","fixed":"\",[\"","state":"repaired","value":",["} +{"input":"\",]","fixed":"\",]\"","state":"repaired","value":",]"} +{"input":"\",b","fixed":"\",b\"","state":"repaired","value":",b"} +{"input":"\",π„ž","fixed":"\",π„ž\"","state":"repaired","value":",π„ž"} +{"input":"\",\\","fixed":"\",\"","state":"repaired","value":","} +{"input":"\",9","fixed":"\",9\"","state":"repaired","value":",9"} +{"input":"9,e","fixed":"9","state":"repaired","value":9} +{"input":"\",'","fixed":"\",'\"","state":"repaired","value":",'"} +{"input":"[\",\"","fixed":"[\",\"]","state":"repaired","value":[","]} +{"input":"{,\n","fixed":"{}","state":"repaired","value":{}} +{"input":"\",{","fixed":"\",{\"","state":"repaired","value":",{"} +{"input":"\",}","fixed":"\",}\"","state":"repaired","value":",}"} +{"input":" \"\"42-+","fixed":" \"\"","state":"repaired","value":""} +{"input":"{
-,","fixed":"{}","state":"repaired","value":{}} +{"input":"{}:--","fixed":"{}","state":"repaired","value":{}} +{"input":"[\"__p-.","fixed":"[\"__p-.\"]","state":"repaired","value":["__p-."]} +{"input":"0\u0000Γ©-:","fixed":"0","state":"repaired","value":0} +{"input":"{-[","fixed":"{}","state":"repaired","value":{}} +{"input":"7-]","fixed":"7","state":"repaired","value":7} +{"input":"\"-n","fixed":"\"-n\"","state":"repaired","value":"-n"} +{"input":"1-πŸ˜€","fixed":"1","state":"repaired","value":1} +{"input":"\"Γ©-\\","fixed":"\"Γ©-\"","state":"repaired","value":"Γ©-"} +{"input":"[-7","fixed":"[-7]","state":"repaired","value":[-7]} +{"input":"[]-E","fixed":"[]","state":"repaired","value":[]} +{"input":"42-'","fixed":"42","state":"repaired","value":42} +{"input":"-1-\"","fixed":"-1","state":"repaired","value":-1} +{"input":"0-\n","fixed":"0","state":"repaired","value":0} +{"input":"\"ke-{","fixed":"\"ke-{\"","state":"repaired","value":"ke-{"} +{"input":"\"-}","fixed":"\"-}\"","state":"repaired","value":"-}"} +{"input":"42.+","fixed":"42","state":"repaired","value":42} +{"input":"null.,","fixed":"null","state":"repaired","value":null} +{"input":"[].-","fixed":"[]","state":"repaired","value":[]} +{"input":"42..","fixed":"42","state":"repaired","value":42} +{"input":" 7.:","fixed":" 7","state":"repaired","value":7} +{"input":"\".[","fixed":"\".[\"","state":"repaired","value":".["} +{"input":"25\t[7.]","fixed":"25","state":"repaired","value":25} +{"input":"{.t","fixed":"{}","state":"repaired","value":{}} +{"input":"\".π„ž","fixed":"\".π„ž\"","state":"repaired","value":".π„ž"} +{"input":"\".\\","fixed":"\".\"","state":"repaired","value":"."} +{"input":"1\t.0","fixed":"1","state":"repaired","value":1} +{"input":"7.e","fixed":"7","state":"repaired","value":7} +{"input":"\".'","fixed":"\".'\"","state":"repaired","value":".'"} +{"input":"{.\"","fixed":"{}","state":"repaired","value":{}} +{"input":"7.\t","fixed":"7","state":"repaired","value":7} +{"input":"{}.{","fixed":"{}","state":"repaired","value":{}} +{"input":" 1.}","fixed":" 1","state":"repaired","value":1} +{"input":"\":+","fixed":"\":+\"","state":"repaired","value":":+"} +{"input":"\":,","fixed":"\":,\"","state":"repaired","value":":,"} +{"input":"{\"\":-","fixed":"{}","state":"repaired","value":{}} +{"input":"0/:.","fixed":"0","state":"repaired","value":0} +{"input":"\"::","fixed":"\"::\"","state":"repaired","value":"::"} +{"input":"{:[","fixed":"{}","state":"repaired","value":{}} +{"input":"\":]","fixed":"\":]\"","state":"repaired","value":":]"} +{"input":"\":v","fixed":"\":v\"","state":"repaired","value":":v"} +{"input":"\":π„ž","fixed":"\":π„ž\"","state":"repaired","value":":π„ž"} +{"input":"\":\\","fixed":"\":\"","state":"repaired","value":":"} +{"input":"\":1","fixed":"\":1\"","state":"repaired","value":":1"} +{"input":"\":e","fixed":"\":e\"","state":"repaired","value":":e"} +{"input":"0:\u0000","fixed":"0","state":"repaired","value":0} +{"input":"{\":\"","fixed":"{}","state":"repaired","value":{}} +{"input":"\": ","fixed":"\": \"","state":"repaired","value":": "} +{"input":"{:{","fixed":"{}","state":"repaired","value":{}} +{"input":"\":}","fixed":"\":}\"","state":"repaired","value":":}"} +{"input":"{[+","fixed":"{}","state":"repaired","value":{}} +{"input":"\"[,","fixed":"\"[,\"","state":"repaired","value":"[,"} +{"input":"{[-","fixed":"{}","state":"repaired","value":{}} +{"input":"{[.","fixed":"{}","state":"repaired","value":{}} +{"input":"\"[:","fixed":"\"[:\"","state":"repaired","value":"[:"} +{"input":"[[","fixed":"[[]]","state":"repaired","value":[[]]} +{"input":"[[]","fixed":"[[]]","state":"repaired","value":[[]]} +{"input":"[n","fixed":"[null]","state":"repaired","value":[null]} +{"input":"{[πŸ˜€","fixed":"{}","state":"repaired","value":{}} +{"input":"\"[\\","fixed":"\"[\"","state":"repaired","value":"["} +{"input":"[9","fixed":"[9]","state":"repaired","value":[9]} +{"input":"0[e","fixed":"0","state":"repaired","value":0} +{"input":"\"[_","fixed":"\"[_\"","state":"repaired","value":"[_"} +{"input":"[\"","fixed":"[\"\"]","state":"repaired","value":[""]} +{"input":"[ ","fixed":"[ ]","state":"repaired","value":[]} +{"input":"[{","fixed":"[{}]","state":"repaired","value":[{}]} +{"input":"0[}","fixed":"0","state":"repaired","value":0} +{"input":"[]+","fixed":"[]","state":"repaired","value":[]} +{"input":"[],","fixed":"[]","state":"repaired","value":[]} +{"input":"[]-","fixed":"[]","state":"repaired","value":[]} +{"input":"[].","fixed":"[]","state":"repaired","value":[]} +{"input":"[]:","fixed":"[]","state":"repaired","value":[]} +{"input":"[][","fixed":"[]","state":"repaired","value":[]} +{"input":"[]]","fixed":"[]","state":"repaired","value":[]} +{"input":"\"]x","fixed":"\"]x\"","state":"repaired","value":"]x"} +{"input":"[]π„ž","fixed":"[]","state":"repaired","value":[]} +{"input":"\"]\\","fixed":"\"]\"","state":"repaired","value":"]"} +{"input":"\"]9","fixed":"\"]9\"","state":"repaired","value":"]9"} +{"input":"[]e","fixed":"[]","state":"repaired","value":[]} +{"input":"[]\u0000","fixed":"[]","state":"repaired","value":[]} +{"input":"[]\"","fixed":"[]","state":"repaired","value":[]} +{"input":"\"] ","fixed":"\"] \"","state":"repaired","value":"] "} +{"input":"[]{","fixed":"[]","state":"repaired","value":[]} +{"input":"9]}","fixed":"9","state":"repaired","value":9} +{"input":"\"n+","fixed":"\"n+\"","state":"repaired","value":"n+"} +{"input":"\"a,","fixed":"\"a,\"","state":"repaired","value":"a,"} +{"input":"\"b-","fixed":"\"b-\"","state":"repaired","value":"b-"} +{"input":"\"k.","fixed":"\"k.\"","state":"repaired","value":"k."} +{"input":"\"a:","fixed":"\"a:\"","state":"repaired","value":"a:"} +{"input":"\"x[","fixed":"\"x[\"","state":"repaired","value":"x["} +{"input":"\"a]","fixed":"\"a]\"","state":"repaired","value":"a]"} +{"input":"fa","fixed":"false","state":"repaired","value":false} +{"input":"\"aπŸ˜€","fixed":"\"aπŸ˜€\"","state":"repaired","value":"aπŸ˜€"} +{"input":"\"b\\","fixed":"\"b\"","state":"repaired","value":"b"} +{"input":"\"b1","fixed":"\"b1\"","state":"repaired","value":"b1"} +{"input":"\"ke","fixed":"\"ke\"","state":"repaired","value":"ke"} +{"input":"\"a_","fixed":"\"a_\"","state":"repaired","value":"a_"} +{"input":"{a\"","fixed":"{}","state":"repaired","value":{}} +{"input":"{u ","fixed":"{}","state":"repaired","value":{}} +{"input":"\"a{","fixed":"\"a{\"","state":"repaired","value":"a{"} +{"input":"\"b}","fixed":"\"b}\"","state":"repaired","value":"b}"} +{"input":"{}π„ž+","fixed":"{}","state":"repaired","value":{}} +{"input":"\"πŸ˜€,","fixed":"\"πŸ˜€,\"","state":"repaired","value":"πŸ˜€,"} +{"input":"{:π„ž-","fixed":"{}","state":"repaired","value":{}} +{"input":"\"π„ž.","fixed":"\"π„ž.\"","state":"repaired","value":"π„ž."} +{"input":"\"πŸ˜€:","fixed":"\"πŸ˜€:\"","state":"repaired","value":"πŸ˜€:"} +{"input":"\"πŸ˜€[","fixed":"\"πŸ˜€[\"","state":"repaired","value":"πŸ˜€["} +{"input":"\"π„ž]","fixed":"\"π„ž]\"","state":"repaired","value":"π„ž]"} +{"input":"{πŸ˜€u","fixed":"{}","state":"repaired","value":{}} +{"input":"\"πŸ˜€πŸ˜€","fixed":"\"πŸ˜€πŸ˜€\"","state":"repaired","value":"πŸ˜€πŸ˜€"} +{"input":"\"πŸ˜€\\","fixed":"\"πŸ˜€\"","state":"repaired","value":"πŸ˜€"} +{"input":"{πŸ˜€9","fixed":"{}","state":"repaired","value":{}} +{"input":"{π„že","fixed":"{}","state":"repaired","value":{}} +{"input":"\"π„ž/","fixed":"\"π„ž/\"","state":"repaired","value":"π„ž/"} +{"input":"{πŸ˜€\"","fixed":"{}","state":"repaired","value":{}} +{"input":"\"π„ž ","fixed":"\"π„ž \"","state":"repaired","value":"π„ž "} +{"input":"\"π„ž{","fixed":"\"π„ž{\"","state":"repaired","value":"π„ž{"} +{"input":"\"π„ž}","fixed":"\"π„ž}\"","state":"repaired","value":"π„ž}"} +{"input":"\"\\\\+","fixed":"\"\\\\+\"","state":"repaired","value":"\\+"} +{"input":"\"\\\\,","fixed":"\"\\\\,\"","state":"repaired","value":"\\,"} +{"input":"0\\-","fixed":"0","state":"repaired","value":0} +{"input":"{}\\.","fixed":"{}","state":"repaired","value":{}} +{"input":"{\\\\:","fixed":"{}","state":"repaired","value":{}} +{"input":"\"\\\\[","fixed":"\"\\\\[\"","state":"repaired","value":"\\["} +{"input":"{\\]","fixed":"{}","state":"repaired","value":{}} +{"input":"\"\\t","fixed":"\"\\t\"","state":"repaired","value":"\t"} +{"input":"\"\\\\π„ž","fixed":"\"\\\\π„ž\"","state":"repaired","value":"\\π„ž"} +{"input":"9\\\\","fixed":"9","state":"repaired","value":9} +{"input":"0\\9","fixed":"0","state":"repaired","value":0} +{"input":"-0\\e","fixed":"-0","state":"repaired","value":-0.0} +{"input":"\"\\/","fixed":"\"\\/\"","state":"repaired","value":"/"} +{"input":"\"\\\"","fixed":"\"\\\"\"","state":"repaired","value":"\""} +{"input":"0\\ ","fixed":"0","state":"repaired","value":0} +{"input":"\"\\\\{","fixed":"\"\\\\{\"","state":"repaired","value":"\\{"} +{"input":"\"\\\\}","fixed":"\"\\\\}\"","state":"repaired","value":"\\}"} +{"input":"4+","fixed":"4","state":"repaired","value":4} +{"input":"9,","fixed":"9","state":"repaired","value":9} +{"input":"0-","fixed":"0","state":"repaired","value":0} +{"input":"1.","fixed":"1","state":"repaired","value":1} +{"input":"0:","fixed":"0","state":"repaired","value":0} +{"input":"1[","fixed":"1","state":"repaired","value":1} +{"input":"7]","fixed":"7","state":"repaired","value":7} +{"input":"0t","fixed":"0","state":"repaired","value":0} +{"input":"7πŸ˜€","fixed":"7","state":"repaired","value":7} +{"input":"1\\","fixed":"1","state":"repaired","value":1} +{"input":"{42","fixed":"{}","state":"repaired","value":{}} +{"input":"0E","fixed":"0","state":"repaired","value":0} +{"input":"0/","fixed":"0","state":"repaired","value":0} +{"input":"1\"","fixed":"1","state":"repaired","value":1} +{"input":"{1\r","fixed":"{}","state":"repaired","value":{}} +{"input":"9{","fixed":"9","state":"repaired","value":9} +{"input":"9}","fixed":"9","state":"repaired","value":9} +{"input":"1e+","fixed":"1","state":"repaired","value":1} +{"input":"{E,","fixed":"{}","state":"repaired","value":{}} +{"input":"0e-","fixed":"0","state":"repaired","value":0} +{"input":"\"E.","fixed":"\"E.\"","state":"repaired","value":"E."} +{"input":"9E:","fixed":"9","state":"repaired","value":9} +{"input":"7E[","fixed":"7","state":"repaired","value":7} +{"input":"true]","fixed":"true","state":"repaired","value":true} +{"input":"9ea","fixed":"9","state":"repaired","value":9} +{"input":"{EπŸ˜€","fixed":"{}","state":"repaired","value":{}} +{"input":"{e\\","fixed":"{}","state":"repaired","value":{}} +{"input":"\"E1","fixed":"\"E1\"","state":"repaired","value":"E1"} +{"input":"0ee","fixed":"0","state":"repaired","value":0} +{"input":"{e'","fixed":"{}","state":"repaired","value":{}} +{"input":"1E\"","fixed":"1","state":"repaired","value":1} +{"input":"{E\r","fixed":"{}","state":"repaired","value":{}} +{"input":"\"e{","fixed":"\"e{\"","state":"repaired","value":"e{"} +{"input":"\"e}","fixed":"\"e}\"","state":"repaired","value":"e}"} +{"input":"[","fixed":"[]","state":"repaired","value":[]} +{"input":"t","fixed":"true","state":"repaired","value":true} +{"input":"\"","fixed":"\"\"","state":"repaired","value":""} +{"input":"{","fixed":"{}","state":"repaired","value":{}} +{"input":"1\u0000+","fixed":"1","state":"repaired","value":1} +{"input":"1\u0000,","fixed":"1","state":"repaired","value":1} +{"input":"{/-","fixed":"{}","state":"repaired","value":{}} +{"input":"\"'.","fixed":"\"'.\"","state":"repaired","value":"'."} +{"input":"\"Γ©:","fixed":"\"Γ©:\"","state":"repaired","value":"Γ©:"} +{"input":"\"/[","fixed":"\"/[\"","state":"repaired","value":"/["} +{"input":"\"/]","fixed":"\"/]\"","state":"repaired","value":"/]"} +{"input":"\"'x","fixed":"\"'x\"","state":"repaired","value":"'x"} +{"input":"\"/π„ž","fixed":"\"/π„ž\"","state":"repaired","value":"/π„ž"} +{"input":"0'\\","fixed":"0","state":"repaired","value":0} +{"input":"0/9","fixed":"0","state":"repaired","value":0} +{"input":"\"/E","fixed":"\"/E\"","state":"repaired","value":"/E"} +{"input":"\"__","fixed":"\"__\"","state":"repaired","value":"__"} +{"input":"7Γ©\"","fixed":"7","state":"repaired","value":7} +{"input":"{''\r","fixed":"{}","state":"repaired","value":{}} +{"input":"4/{","fixed":"4","state":"repaired","value":4} +{"input":"\"\\/}","fixed":"\"\\/}\"","state":"repaired","value":"/}"} +{"input":"\"+","fixed":"\"+\"","state":"repaired","value":"+"} +{"input":"\",","fixed":"\",\"","state":"repaired","value":","} +{"input":"\"-","fixed":"\"-\"","state":"repaired","value":"-"} +{"input":"\".","fixed":"\".\"","state":"repaired","value":"."} +{"input":"\":","fixed":"\":\"","state":"repaired","value":":"} +{"input":"\"[","fixed":"\"[\"","state":"repaired","value":"["} +{"input":"\"]","fixed":"\"]\"","state":"repaired","value":"]"} +{"input":"\"a","fixed":"\"a\"","state":"repaired","value":"a"} +{"input":"\"\\","fixed":"\"\"","state":"repaired","value":""} +{"input":"\"1","fixed":"\"1\"","state":"repaired","value":"1"} +{"input":"\"e","fixed":"\"e\"","state":"repaired","value":"e"} +{"input":"\"_","fixed":"\"_\"","state":"repaired","value":"_"} +{"input":"\"\"\"","fixed":"\"\"","state":"repaired","value":""} +{"input":"\" ","fixed":"\" \"","state":"repaired","value":" "} +{"input":"\"{","fixed":"\"{\"","state":"repaired","value":"{"} +{"input":"\"}","fixed":"\"}\"","state":"repaired","value":"}"} +{"input":"1\n+","fixed":"1","state":"repaired","value":1} +{"input":"1\t,","fixed":"1","state":"repaired","value":1} +{"input":"0\t-","fixed":"0","state":"repaired","value":0} +{"input":"7 .","fixed":"7","state":"repaired","value":7} +{"input":"\" :","fixed":"\" :\"","state":"repaired","value":" :"} +{"input":" [","fixed":" []","state":"repaired","value":[]} +{"input":"\" ]","fixed":"\" ]\"","state":"repaired","value":" ]"} +{"input":"\nt","fixed":"\ntrue","state":"repaired","value":true} +{"input":"{\nπŸ˜€","fixed":"{}","state":"repaired","value":{}} +{"input":"\" \\","fixed":"\" \"","state":"repaired","value":" "} +{"input":"[\n7","fixed":"[\n7]","state":"repaired","value":[7]} +{"input":"\" E","fixed":"\" E\"","state":"repaired","value":" E"} +{"input":"\" _","fixed":"\" _\"","state":"repaired","value":" _"} +{"input":"\t\"","fixed":"\t\"\"","state":"repaired","value":""} +{"input":"{\t ","fixed":"{}","state":"repaired","value":{}} +{"input":" {","fixed":" {}","state":"repaired","value":{}} +{"input":"9\r}","fixed":"9","state":"repaired","value":9} +{"input":"{+","fixed":"{}","state":"repaired","value":{}} +{"input":"{,","fixed":"{}","state":"repaired","value":{}} +{"input":"{-","fixed":"{}","state":"repaired","value":{}} +{"input":"{.","fixed":"{}","state":"repaired","value":{}} +{"input":"{:","fixed":"{}","state":"repaired","value":{}} +{"input":"{[","fixed":"{}","state":"repaired","value":{}} +{"input":"{]","fixed":"{}","state":"repaired","value":{}} +{"input":"{f","fixed":"{}","state":"repaired","value":{}} +{"input":"{π„ž","fixed":"{}","state":"repaired","value":{}} +{"input":"{\\","fixed":"{}","state":"repaired","value":{}} +{"input":"{1","fixed":"{}","state":"repaired","value":{}} +{"input":"{E","fixed":"{}","state":"repaired","value":{}} +{"input":"{'","fixed":"{}","state":"repaired","value":{}} +{"input":"{\"","fixed":"{}","state":"repaired","value":{}} +{"input":"{ ","fixed":"{}","state":"repaired","value":{}} +{"input":"{{","fixed":"{}","state":"repaired","value":{}} +{"input":"[{}","fixed":"[{}]","state":"repaired","value":[{}]} +{"input":"{}+","fixed":"{}","state":"repaired","value":{}} +{"input":"{},","fixed":"{}","state":"repaired","value":{}} +{"input":" \"}-","fixed":" \"}-\"","state":"repaired","value":"}-"} +{"input":"{}.","fixed":"{}","state":"repaired","value":{}} +{"input":"\"}:","fixed":"\"}:\"","state":"repaired","value":"}:"} +{"input":"\"}[","fixed":"\"}[\"","state":"repaired","value":"}["} +{"input":"\"}]","fixed":"\"}]\"","state":"repaired","value":"}]"} +{"input":"\"}k","fixed":"\"}k\"","state":"repaired","value":"}k"} +{"input":"\"}π„ž","fixed":"\"}π„ž\"","state":"repaired","value":"}π„ž"} +{"input":"\"}\\","fixed":"\"}\"","state":"repaired","value":"}"} +{"input":"\"}1","fixed":"\"}1\"","state":"repaired","value":"}1"} +{"input":"{}E","fixed":"{}","state":"repaired","value":{}} +{"input":"{}\u0000","fixed":"{}","state":"repaired","value":{}} +{"input":"{}\"","fixed":"{}","state":"repaired","value":{}} +{"input":"\"} ","fixed":"\"} \"","state":"repaired","value":"} "} +{"input":"{}{","fixed":"{}","state":"repaired","value":{}} +{"input":"{}}","fixed":"{}","state":"repaired","value":{}} +{"input":"[1,2","fixed":"[1,2]","state":"repaired","value":[1,2]} +{"input":"[1 2","fixed":"[1 2]","state":"failed"} +{"input":"[1,]","fixed":"[1]","state":"repaired","value":[1]} +{"input":"[\"a\" x","fixed":"[\"a\" x]","state":"failed"} +{"input":"[1,2,{\"a\":","fixed":"[1,2,{}]","state":"repaired","value":[1,2,{}]} +{"input":"[true,fal","fixed":"[true,false]","state":"repaired","value":[true,false]} +{"input":"[[1],[2","fixed":"[[1],[2]]","state":"repaired","value":[[1],[2]]} +{"input":"[1 ,","fixed":"[1]","state":"repaired","value":[1]} +{"input":"[\"a\",\"b","fixed":"[\"a\",\"b\"]","state":"repaired","value":["a","b"]} diff --git a/aimux-core/tests/json_repair_cases_test.rs b/aimux-core/tests/json_repair_cases_test.rs new file mode 100644 index 000000000..603a0f495 --- /dev/null +++ b/aimux-core/tests/json_repair_cases_test.rs @@ -0,0 +1,51 @@ +//! Regression cases for `json_repair`. +//! +//! `fixtures/json_repair_cases.jsonl` holds 740 inputs with the result the +//! line-for-line port of `fix-json.ts` / `parse-partial-json.ts` gave for +//! them: one shortest input per (outcome, last two characters, unchanged by +//! `fix_json`) bucket out of 300 000 generated ones, plus hand-picked edges +//! (truncated `\u` escapes, bare `-`, astral characters, partial literals, +//! array separators). +//! The upstream test cases themselves are in `util_test.rs`. + +use aimux_core::json_repair::{PartialJson, fix_json}; +use serde_json::Value; + +#[test] +fn matches_the_recorded_cases() { + let cases = include_str!("fixtures/json_repair_cases.jsonl"); + let mut checked = 0; + let mut differences = Vec::new(); + for line in cases.lines() { + let case: Value = serde_json::from_str(line).unwrap(); + let input = case["input"].as_str().unwrap(); + + let fixed = fix_json(input); + if fixed != case["fixed"] { + differences.push(format!( + "fix_json({input:?}) = {fixed:?}, want {}", + case["fixed"] + )); + } + + let want = match case["state"].as_str().unwrap() { + "parsed" => PartialJson::Parsed(case["value"].clone()), + "repaired" => PartialJson::Repaired(case["value"].clone()), + _ => PartialJson::Failed, + }; + let got = PartialJson::parse(input); + if got != want { + differences.push(format!( + "PartialJson::parse({input:?}) = {got:?}, want {want:?}" + )); + } + checked += 1; + } + assert_eq!(checked, 740); + assert!( + differences.is_empty(), + "{} differences:\n{}", + differences.len(), + differences[..differences.len().min(10)].join("\n") + ); +} diff --git a/aimux-core/tests/util_test.rs b/aimux-core/tests/util_test.rs index 0f2e9f9fd..431f33064 100644 --- a/aimux-core/tests/util_test.rs +++ b/aimux-core/tests/util_test.rs @@ -11,9 +11,7 @@ //! Rust equivalent because the port uses real `serde_json` parsing instead of //! the mocked `safeParseJSON`. Such omissions are called out in comments. -use aimux_core::util::{ - ParsePartialJsonState, UtilError, cosine_similarity, fix_json, parse_partial_json, -}; +use aimux_core::util::{PartialJson, UtilError, cosine_similarity, fix_json}; use serde_json::json; // =========================================================================== @@ -382,17 +380,16 @@ mod parse_partial_json { #[test] fn should_handle_nullish_input() { - let result = parse_partial_json(None); - assert_eq!(result.value, None); - assert_eq!(result.state, ParsePartialJsonState::UndefinedInput); + // Upstream's `undefined-input` state is the caller's `Option`. + let text: Option<&str> = None; + assert_eq!(text.map(PartialJson::parse), None); } #[test] fn should_parse_valid_json() { let valid_json = r#"{"key": "value"}"#; - let result = parse_partial_json(Some(valid_json)); - assert_eq!(result.value, Some(json!({"key": "value"}))); - assert_eq!(result.state, ParsePartialJsonState::SuccessfulParse); + let result = PartialJson::parse(valid_json); + assert_eq!(result, PartialJson::Parsed(json!({"key": "value"}))); // TS also asserts `safeParseJSON` was called with `{ text: validJson }`; // skipped: no mock in the Rust port (real serde_json parse is used). } @@ -400,9 +397,8 @@ mod parse_partial_json { #[test] fn should_repair_and_parse_partial_json() { let partial_json = r#"{"key": "value""#; - let result = parse_partial_json(Some(partial_json)); - assert_eq!(result.value, Some(json!({"key": "value"}))); - assert_eq!(result.state, ParsePartialJsonState::RepairedParse); + let result = PartialJson::parse(partial_json); + assert_eq!(result, PartialJson::Repaired(json!({"key": "value"}))); // TS also asserts fixJson was called with the partial input and the // second parse used the fixed text; skipped: no mock in the Rust port. } @@ -410,12 +406,12 @@ mod parse_partial_json { #[test] fn should_handle_invalid_json_that_cannot_be_repaired() { let invalid_json = "not json at all"; - let result = parse_partial_json(Some(invalid_json)); - assert_eq!(result.value, None); - assert_eq!(result.state, ParsePartialJsonState::FailedParse); + let result = PartialJson::parse(invalid_json); + assert_eq!(result, PartialJson::Failed); + assert_eq!(result.value(), None); // TS mocks fixJson to return the input unchanged; the real fixJson // reduces this input to "n", which still fails to parse, so the - // observable outcome (FailedParse) is identical. + // observable outcome (`failed-parse`) is identical. } }