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