1use crate::document::{DocumentError, DocumentResult, Value};
4
5#[derive(Debug)]
9pub struct JsonDocument<'a> {
10 source: &'a str,
11 root: Node,
12}
13
14impl<'a> JsonDocument<'a> {
15 pub fn parse(source: &'a str) -> DocumentResult<Self> {
16 let mut parser = Parser::new(source)?;
17 let root = parser.parse_value(0)?;
18 if parser.skip_ws(root.end) != source.len() {
19 return Err(DocumentError::ParseError {
20 format: "JSON".to_string(),
21 detail: "trailing bytes after root value".to_string(),
22 });
23 }
24 Ok(Self { source, root })
25 }
26
27 #[must_use]
28 pub fn source(&self) -> &'a str {
29 self.source
30 }
31
32 #[must_use]
33 pub(crate) fn root(&self) -> &Node {
34 &self.root
35 }
36}
37
38pub fn set_preserving(content: &str, path: &str, value: &Value) -> DocumentResult<String> {
47 let segments = crate::document::parse_path(path)?;
48 let document = JsonDocument::parse(content)?;
49 let root = document.root();
50 let mut output = content.to_string();
51 match resolve(root, &segments, 0) {
52 Some(target) => {
53 let replacement = compact_json(value)?;
57 output.replace_range(target.start..target.end, &replacement);
58 }
59 None => {
60 let (insert_at, tail) = deepest_existing_object(root, &segments).ok_or_else(|| {
65 DocumentError::UnsupportedOperation {
66 format: "JSON".to_string(),
67 operation: "set".to_string(),
68 detail: "cannot insert a new key into a non-object JSON value".to_string(),
69 }
70 })?;
71 let NodeKind::Object(entries) = &insert_at.kind else {
72 unreachable!("deepest_existing_object only returns object nodes");
73 };
74 let (member_key, rest) = tail.split_first().ok_or(DocumentError::EmptyPath)?;
75 let new_key =
76 serde_json::to_string(member_key).map_err(|error| DocumentError::ParseError {
77 format: "JSON".to_string(),
78 detail: error.to_string(),
79 })?;
80 let (position, fragment) = match entries.last() {
81 Some((_, last_node)) => {
82 let anchor = last_node.member_start.unwrap_or(last_node.start);
83 let (indent, multiline) = member_indent(content.as_bytes(), anchor);
84 let member_value = nested_member_value(rest, value, multiline, &indent)?;
85 let fragment = if multiline {
86 format!(",\n{indent}{new_key}: {member_value}")
87 } else {
88 format!(", {new_key}: {member_value}")
89 };
90 (last_node.end, fragment)
91 }
92 None => {
93 let member_value = nested_member_value(rest, value, false, "")?;
95 (insert_at.start + 1, format!("{new_key}: {member_value}"))
96 }
97 };
98 output.insert_str(position, &fragment);
99 }
100 }
101 serde_json::from_str::<serde_json::Value>(&output).map_err(|error| {
102 DocumentError::ParseError {
103 format: "JSON".to_string(),
104 detail: error.to_string(),
105 }
106 })?;
107 Ok(output)
108}
109
110fn compact_json(value: &Value) -> DocumentResult<String> {
112 let json_value = serde_json::Value::try_from(value)?;
113 serde_json::to_string(&json_value).map_err(|error| DocumentError::UnsupportedOperation {
114 format: "JSON".to_string(),
115 operation: "set".to_string(),
116 detail: error.to_string(),
117 })
118}
119
120fn deepest_existing_object<'a>(
124 root: &'a Node,
125 segments: &'a [String],
126) -> Option<(&'a Node, &'a [String])> {
127 for split in (0..segments.len()).rev() {
130 if let Some(node) = resolve(root, &segments[..split], 0) {
131 return match node.kind {
132 NodeKind::Object(_) => Some((node, &segments[split..])),
133 _ => None,
134 };
135 }
136 }
137 None
138}
139
140fn nested_member_value(
144 tail: &[String],
145 value: &Value,
146 multiline: bool,
147 base_indent: &str,
148) -> DocumentResult<String> {
149 let mut nested = serde_json::Value::try_from(value)?;
150 for segment in tail.iter().rev() {
151 let mut object = serde_json::Map::new();
152 object.insert(segment.clone(), nested);
153 nested = serde_json::Value::Object(object);
154 }
155 if multiline {
156 let pretty =
157 serde_json::to_string_pretty(&nested).map_err(|error| DocumentError::ParseError {
158 format: "JSON".to_string(),
159 detail: error.to_string(),
160 })?;
161 Ok(pretty.replace('\n', &format!("\n{base_indent}")))
162 } else {
163 serde_json::to_string(&nested).map_err(|error| DocumentError::ParseError {
164 format: "JSON".to_string(),
165 detail: error.to_string(),
166 })
167 }
168}
169
170fn member_indent(bytes: &[u8], anchor: usize) -> (String, bool) {
173 match bytes[..anchor].iter().rposition(|byte| *byte == b'\n') {
174 Some(newline) => {
175 let indent = bytes[newline + 1..anchor]
176 .iter()
177 .take_while(|byte| matches!(byte, b' ' | b'\t'))
178 .map(|byte| *byte as char)
179 .collect();
180 (indent, true)
181 }
182 None => (String::new(), false),
183 }
184}
185
186pub fn unset_preserving(content: &str, path: &str) -> DocumentResult<String> {
189 let segments = crate::document::parse_path(path)?;
190 let document = JsonDocument::parse(content)?;
191 let root = document.root();
192 let target = resolve(root, &segments, 0).ok_or_else(|| DocumentError::PathNotFound {
193 path: path.to_string(),
194 })?;
195 if target.member_start.is_none()
196 && matches!(target.kind, NodeKind::Scalar)
197 && segments.is_empty()
198 {
199 return Err(DocumentError::UnsupportedOperation {
200 format: "JSON".to_string(),
201 operation: "unset".to_string(),
202 detail: "cannot remove the JSON root value".to_string(),
203 });
204 }
205 let mut start = target.member_start.unwrap_or(target.start);
206 if target.member_start.is_some()
207 && let Some(newline) = content.as_bytes()[..start]
208 .iter()
209 .rposition(|byte| *byte == b'\n')
210 {
211 let candidate = newline + 1;
212 if content.as_bytes()[candidate..start]
213 .iter()
214 .all(|byte| byte.is_ascii_whitespace())
215 {
216 start = candidate;
217 }
218 }
219 let end = target.end;
220 let after = skip_ws_bytes(content.as_bytes(), end);
221 let parent_body_start =
226 resolve(root, &segments[..segments.len() - 1], 0).map_or(0, |parent| parent.start + 1);
227 let (remove_start, mut remove_end, remove_following_line) =
228 if content.as_bytes().get(after) == Some(&b',') {
229 (start, after + 1, true)
230 } else if let Some(offset) = content.as_bytes()[parent_body_start..start]
231 .iter()
232 .rposition(|byte| *byte == b',')
233 {
234 (parent_body_start + offset, end, false)
235 } else {
236 (start, end, false)
237 };
238 if remove_following_line {
239 if content.as_bytes().get(remove_end) == Some(&b'\r') {
240 remove_end += 1;
241 }
242 if content.as_bytes().get(remove_end) == Some(&b'\n') {
243 remove_end += 1;
244 }
245 }
246 let mut output = content.to_string();
247 output.replace_range(remove_start..remove_end, "");
248 serde_json::from_str::<serde_json::Value>(&output).map_err(|error| {
249 DocumentError::ParseError {
250 format: "JSON".to_string(),
251 detail: error.to_string(),
252 }
253 })?;
254 Ok(output)
255}
256
257pub fn append_array_item_preserving(
260 content: &str,
261 path: &str,
262 item: &Value,
263) -> DocumentResult<String> {
264 let segments = array_path_segments(path)?;
265 let mut parser = Parser::new(content)?;
266 let root = parser.parse_value(0)?;
267 let target = resolve(&root, &segments, 0).ok_or_else(|| DocumentError::PathNotFound {
268 path: path.to_string(),
269 })?;
270 let NodeKind::Array(items) = &target.kind else {
271 return Err(DocumentError::UnsupportedOperation {
272 format: "JSON".to_string(),
273 operation: "add".to_string(),
274 detail: "target is not an array".to_string(),
275 });
276 };
277 let json_item = serde_json::Value::try_from(item)?;
278 let fragment =
279 serde_json::to_string(&json_item).map_err(|error| DocumentError::UnsupportedOperation {
280 format: "JSON".to_string(),
281 operation: "add".to_string(),
282 detail: error.to_string(),
283 })?;
284 let close = target
285 .end
286 .checked_sub(1)
287 .ok_or_else(|| DocumentError::ParseError {
288 format: "JSON".to_string(),
289 detail: "invalid array span".to_string(),
290 })?;
291 let whitespace_start = content.as_bytes()[target.start + 1..close]
292 .iter()
293 .rposition(|byte| !byte.is_ascii_whitespace())
294 .map(|index| target.start + 2 + index)
295 .unwrap_or(target.start + 1);
296 let insertion = if items.is_empty() {
297 fragment
298 } else {
299 format!(", {fragment}")
300 };
301 let mut output = content.to_string();
302 output.insert_str(whitespace_start, &insertion);
303 serde_json::from_str::<serde_json::Value>(&output).map_err(|error| {
304 DocumentError::ParseError {
305 format: "JSON".to_string(),
306 detail: error.to_string(),
307 }
308 })?;
309 Ok(output)
310}
311
312pub fn remove_array_item_preserving(
315 content: &str,
316 path: &str,
317 index: usize,
318) -> DocumentResult<String> {
319 let segments = array_path_segments(path)?;
320 let mut parser = Parser::new(content)?;
321 let root = parser.parse_value(0)?;
322 let target = resolve(&root, &segments, 0).ok_or_else(|| DocumentError::PathNotFound {
323 path: path.to_string(),
324 })?;
325 let NodeKind::Array(items) = &target.kind else {
326 return Err(DocumentError::UnsupportedOperation {
327 format: "JSON".to_string(),
328 operation: "remove".to_string(),
329 detail: "target is not an array".to_string(),
330 });
331 };
332 let item = items
333 .get(index)
334 .ok_or_else(|| DocumentError::IndexOutOfBounds {
335 path: path.to_string(),
336 index,
337 len: items.len(),
338 })?;
339 let mut start = item.start;
340 if let Some(newline) = content.as_bytes()[..start]
341 .iter()
342 .rposition(|byte| *byte == b'\n')
343 {
344 let candidate = newline + 1;
345 if content.as_bytes()[candidate..start]
346 .iter()
347 .all(|byte| byte.is_ascii_whitespace())
348 {
349 start = candidate;
350 }
351 }
352 let after = skip_ws_bytes(content.as_bytes(), item.end);
353 let (mut remove_start, mut remove_end, remove_following_line) =
354 if content.as_bytes().get(after) == Some(&b',') {
355 (start, after + 1, true)
356 } else if let Some(comma) = content.as_bytes()[target.start..start]
357 .iter()
358 .rposition(|byte| *byte == b',')
359 .map(|offset| target.start + offset)
360 {
361 (comma, item.end, false)
368 } else {
369 (start, item.end, false)
370 };
371 if remove_following_line {
372 if content.as_bytes().get(remove_end) == Some(&b'\r') {
373 remove_end += 1;
374 }
375 if content.as_bytes().get(remove_end) == Some(&b'\n') {
376 remove_end += 1;
377 }
378 }
379 if remove_start > remove_end {
380 std::mem::swap(&mut remove_start, &mut remove_end);
381 }
382 let mut output = content.to_string();
383 output.replace_range(remove_start..remove_end, "");
384 serde_json::from_str::<serde_json::Value>(&output).map_err(|error| {
385 DocumentError::ParseError {
386 format: "JSON".to_string(),
387 detail: error.to_string(),
388 }
389 })?;
390 Ok(output)
391}
392
393fn array_path_segments(path: &str) -> DocumentResult<Vec<String>> {
397 if path.is_empty() {
398 Ok(Vec::new())
399 } else {
400 crate::document::parse_path(path)
401 }
402}
403
404fn skip_ws_bytes(source: &[u8], mut position: usize) -> usize {
405 while source
406 .get(position)
407 .is_some_and(|byte| matches!(byte, b' ' | b'\n' | b'\r' | b'\t'))
408 {
409 position += 1;
410 }
411 position
412}
413
414#[derive(Debug, Clone)]
415pub(crate) struct Node {
416 start: usize,
417 end: usize,
418 member_start: Option<usize>,
419 kind: NodeKind,
420}
421
422#[derive(Debug, Clone)]
423enum NodeKind {
424 Scalar,
425 Object(Vec<(String, Node)>),
426 Array(Vec<Node>),
427}
428
429fn resolve<'a>(node: &'a Node, segments: &[String], index: usize) -> Option<&'a Node> {
430 if index == segments.len() {
431 return Some(node);
432 }
433 match &node.kind {
434 NodeKind::Object(entries) => entries
435 .iter()
436 .rev()
437 .find(|(key, _)| key == &segments[index])
438 .and_then(|(_, child)| resolve(child, segments, index + 1)),
439 NodeKind::Array(items) => segments[index]
440 .parse::<usize>()
441 .ok()
442 .and_then(|item| items.get(item))
443 .and_then(|child| resolve(child, segments, index + 1)),
444 NodeKind::Scalar => None,
445 }
446}
447
448struct Parser<'a> {
449 source: &'a [u8],
450}
451
452impl<'a> Parser<'a> {
453 fn new(source: &'a str) -> DocumentResult<Self> {
454 serde_json::from_str::<serde_json::Value>(source).map_err(|error| {
458 DocumentError::ParseError {
459 format: "JSON".to_string(),
460 detail: error.to_string(),
461 }
462 })?;
463 Ok(Self {
464 source: source.as_bytes(),
465 })
466 }
467
468 fn parse_value(&mut self, mut position: usize) -> DocumentResult<Node> {
469 position = self.skip_ws(position);
470 let start = position;
471 let Some(byte) = self.source.get(position).copied() else {
472 return self.error(position, "expected JSON value");
473 };
474 let kind = match byte {
475 b'{' => self.parse_object(&mut position)?,
476 b'[' => self.parse_array(&mut position)?,
477 b'"' => {
478 position = self.parse_string(position)?;
479 NodeKind::Scalar
480 }
481 _ => {
482 position = self.parse_scalar(position)?;
483 NodeKind::Scalar
484 }
485 };
486 Ok(Node {
487 start,
488 end: position,
489 member_start: None,
490 kind,
491 })
492 }
493
494 fn parse_object(&mut self, position: &mut usize) -> DocumentResult<NodeKind> {
495 *position += 1;
496 let mut entries = Vec::new();
497 let mut keys = std::collections::HashSet::new();
498 loop {
499 *position = self.skip_ws(*position);
500 if self.source.get(*position) == Some(&b'}') {
501 *position += 1;
502 return Ok(NodeKind::Object(entries));
503 }
504 let key_start = *position;
505 let key_end = self.parse_string(*position)?;
506 let key = serde_json::from_slice::<String>(&self.source[key_start..key_end]).map_err(
507 |error| DocumentError::ParseError {
508 format: "JSON".to_string(),
509 detail: error.to_string(),
510 },
511 )?;
512 if !keys.insert(key.clone()) {
513 return self.error(key_start, "duplicate object key");
514 }
515 *position = self.skip_ws(key_end);
516 if self.source.get(*position) != Some(&b':') {
517 return self.error(*position, "expected `:` after object key");
518 }
519 *position += 1;
520 let child = self.parse_value(*position)?;
521 *position = child.end;
522 let mut child = child;
523 child.member_start = Some(key_start);
524 entries.push((key, child));
525 *position = self.skip_ws(*position);
526 match self.source.get(*position) {
527 Some(b',') => *position += 1,
528 Some(b'}') => {
529 *position += 1;
530 return Ok(NodeKind::Object(entries));
531 }
532 _ => return self.error(*position, "expected `,` or `}` in object"),
533 }
534 }
535 }
536
537 fn parse_array(&mut self, position: &mut usize) -> DocumentResult<NodeKind> {
538 *position += 1;
539 let mut items = Vec::new();
540 loop {
541 *position = self.skip_ws(*position);
542 if self.source.get(*position) == Some(&b']') {
543 *position += 1;
544 return Ok(NodeKind::Array(items));
545 }
546 let child = self.parse_value(*position)?;
547 *position = child.end;
548 items.push(child);
549 *position = self.skip_ws(*position);
550 match self.source.get(*position) {
551 Some(b',') => *position += 1,
552 Some(b']') => {
553 *position += 1;
554 return Ok(NodeKind::Array(items));
555 }
556 _ => return self.error(*position, "expected `,` or `]` in array"),
557 }
558 }
559 }
560
561 fn parse_string(&self, mut position: usize) -> DocumentResult<usize> {
562 if self.source.get(position) != Some(&b'"') {
563 return self.error(position, "expected JSON string");
564 }
565 position += 1;
566 let mut escaped = false;
567 while let Some(byte) = self.source.get(position).copied() {
568 position += 1;
569 if escaped {
570 escaped = false;
571 } else if byte == b'\\' {
572 escaped = true;
573 } else if byte == b'"' {
574 return Ok(position);
575 }
576 }
577 self.error(position, "unterminated JSON string")
578 }
579
580 fn parse_scalar(&self, mut position: usize) -> DocumentResult<usize> {
581 let start = position;
582 while let Some(byte) = self.source.get(position).copied() {
583 if matches!(byte, b',' | b']' | b'}' | b' ' | b'\n' | b'\r' | b'\t') {
584 break;
585 }
586 position += 1;
587 }
588 if position == start {
589 return self.error(position, "empty JSON scalar");
590 }
591 serde_json::from_slice::<serde_json::Value>(&self.source[start..position])
592 .map_err(|error| DocumentError::ParseError {
593 format: "JSON".to_string(),
594 detail: error.to_string(),
595 })
596 .map(|_| position)
597 }
598
599 fn skip_ws(&self, mut position: usize) -> usize {
600 while self
601 .source
602 .get(position)
603 .is_some_and(|byte| matches!(byte, b' ' | b'\n' | b'\r' | b'\t'))
604 {
605 position += 1;
606 }
607 position
608 }
609
610 fn error<T>(&self, position: usize, detail: &str) -> DocumentResult<T> {
611 Err(DocumentError::ParseError {
612 format: "JSON".to_string(),
613 detail: format!("at byte {position}: {detail}"),
614 })
615 }
616}
617
618pub fn load(content: &str) -> DocumentResult<Value> {
619 JsonDocument::parse(content)?;
620 serde_json::from_str::<serde_json::Value>(content)
621 .map(Value::from)
622 .map_err(|e| DocumentError::ParseError {
623 format: "JSON".to_string(),
624 detail: e.to_string(),
625 })
626}
627
628pub fn save(value: &Value) -> DocumentResult<String> {
629 let json_val = serde_json::Value::try_from(value)?;
630 serde_json::to_string_pretty(&json_val).map_err(|e| DocumentError::ParseError {
631 format: "JSON".to_string(),
632 detail: e.to_string(),
633 })
634}
635
636#[cfg(test)]
637mod tests {
638 use super::{JsonDocument, load, save};
639 use crate::document::Value;
640
641 #[test]
642 fn duplicate_keys_are_rejected_by_load_and_editor_parser() {
643 let source = r#"{"same":1,"same":2}"#;
644 assert!(load(source).is_err());
645 assert!(JsonDocument::parse(source).is_err());
646 }
647
648 #[test]
649 fn source_parser_rejects_invalid_strings_and_trailing_commas_before_editing() {
650 for source in [r#"{"key":"\q"}"#, "[1,]", r#"{"key":1,}"#] {
651 assert!(JsonDocument::parse(source).is_err(), "{source}");
652 }
653 assert!(
654 super::set_preserving(
655 r#"{"key":"\q"}"#,
656 "key",
657 &Value::String("replacement".into()),
658 )
659 .is_err()
660 );
661 }
662
663 #[test]
664 fn source_parser_bounds_nesting_before_scanning_spans() {
665 for depth in [256, 10_000] {
666 let source = format!("{}0{}", "[".repeat(depth), "]".repeat(depth));
667 assert!(JsonDocument::parse(&source).is_err());
668 }
669 }
670
671 #[test]
672 fn save_rejects_non_finite_float() {
673 let error = save(&Value::Float(f64::INFINITY)).expect_err("infinity must fail");
674 assert!(error.to_string().contains("non-finite"));
675 }
676}