1// Package jsonrepair provides utilities to repair malformed JSON.
2package jsonrepair
3
4import (
5 "bytes"
6 "encoding/json"
7 "errors"
8 "reflect"
9 "slices"
10 "strconv"
11 "strings"
12 "unicode"
13 "unicode/utf16"
14)
15
16// Option is a function that configures the JSON repairer.
17type Option func(*options)
18
19type options struct {
20 ensureASCII *bool
21 skipJSONLoads bool
22 streamStable bool
23 strict bool
24}
25
26// LogEntry represents a log entry with context and text.
27type LogEntry struct {
28 Context string `json:"context"`
29 Text string `json:"text"`
30}
31
32type numberValue struct {
33 raw string
34}
35
36type objectEntry struct {
37 key string
38 value any
39}
40
41type orderedObject struct {
42 entries []objectEntry
43 index map[string]int
44}
45
46func newOrderedObject() *orderedObject {
47 return &orderedObject{index: map[string]int{}}
48}
49
50func (o *orderedObject) set(key string, value any) {
51 if idx, ok := o.index[key]; ok {
52 o.entries[idx].value = value
53 return
54 }
55 o.index[key] = len(o.entries)
56 o.entries = append(o.entries, objectEntry{key: key, value: value})
57}
58
59func (o *orderedObject) get(key string) (any, bool) {
60 idx, ok := o.index[key]
61 if !ok {
62 return nil, false
63 }
64 return o.entries[idx].value, true
65}
66
67func (o *orderedObject) lastKey() (string, bool) {
68 if len(o.entries) == 0 {
69 return "", false
70 }
71 return o.entries[len(o.entries)-1].key, true
72}
73
74func (o *orderedObject) hasKey(key string) bool {
75 _, ok := o.index[key]
76 return ok
77}
78
79func (o *orderedObject) merge(other *orderedObject) {
80 for _, entry := range other.entries {
81 o.set(entry.key, entry.value)
82 }
83}
84
85type contextValue int
86
87const (
88 contextObjectKey contextValue = iota
89 contextObjectValue
90 contextArray
91)
92
93type jsonContext struct {
94 context []contextValue
95 current *contextValue
96 empty bool
97}
98
99func newJSONContext() *jsonContext {
100 return &jsonContext{empty: true}
101}
102
103func (c *jsonContext) set(value contextValue) {
104 c.context = append(c.context, value)
105 c.current = &c.context[len(c.context)-1]
106 c.empty = false
107}
108
109func (c *jsonContext) reset() {
110 if len(c.context) > 0 {
111 c.context = c.context[:len(c.context)-1]
112 }
113 if len(c.context) == 0 {
114 c.current = nil
115 c.empty = true
116 return
117 }
118 c.current = &c.context[len(c.context)-1]
119}
120
121func (c *jsonContext) contains(value contextValue) bool {
122 return slices.Contains(c.context, value)
123}
124
125type parser struct {
126 jsonStr []rune
127 index int
128 context *jsonContext
129 logging bool
130 logger []LogEntry
131 streamStable bool
132 strict bool
133 log func(string)
134}
135
136func newParser(input string, logging bool, streamStable bool, strict bool) *parser {
137 p := &parser{
138 jsonStr: []rune(input),
139 context: newJSONContext(),
140 logging: logging,
141 streamStable: streamStable,
142 strict: strict,
143 }
144 if logging {
145 p.log = p.addLog
146 } else {
147 p.log = func(string) {}
148 }
149 return p
150}
151
152func (p *parser) addLog(text string) {
153 window := 10
154 start := max(p.index-window, 0)
155 end := min(p.index+window, len(p.jsonStr))
156 context := string(p.jsonStr[start:end])
157 p.logger = append(p.logger, LogEntry{Text: text, Context: context})
158}
159
160func (p *parser) parse() (any, []LogEntry, error) {
161 jsonValue, err := p.parseJSON()
162 if err != nil {
163 return nil, nil, err
164 }
165 if p.index < len(p.jsonStr) {
166 p.log("The parser returned early, checking if there's more json elements")
167 values := []any{jsonValue}
168 for p.index < len(p.jsonStr) {
169 p.context.reset()
170 j, parseErr := p.parseJSON()
171 if parseErr != nil {
172 return nil, nil, parseErr
173 }
174 if isTruthy(j) {
175 if len(values) > 0 && isSameObject(values[len(values)-1], j) {
176 values = values[:len(values)-1]
177 } else if len(values) > 0 && !isTruthy(values[len(values)-1]) {
178 values = values[:len(values)-1]
179 }
180 values = append(values, j)
181 } else {
182 if len(values) > 1 {
183 _, ok := p.getCharAt(0)
184 if !ok {
185 break
186 }
187 if len(values) > 1 {
188 values = values[:len(values)-1]
189 }
190 p.index = len(p.jsonStr)
191 break
192 }
193 p.index++
194 }
195 }
196 if len(values) == 1 {
197 p.log("There were no more elements, returning the element without the array")
198 jsonValue = values[0]
199 } else if p.strict {
200 p.log("Multiple top-level JSON elements found in strict mode, raising an error")
201 return nil, nil, errors.New("multiple top-level JSON elements found in strict mode")
202 } else {
203 jsonValue = values
204 }
205 }
206 return jsonValue, p.logger, nil
207}
208
209func (p *parser) parseJSON() (any, error) {
210 for {
211 char, ok := p.getCharAt(0)
212 if !ok {
213 return "", nil
214 }
215 if char == '{' {
216 p.index++
217 return p.parseObject()
218 }
219 if char == '[' {
220 p.index++
221 return p.parseArray()
222 }
223 if !p.context.empty && (isStringDelimiter(char) || unicode.IsLetter(char)) {
224 return p.parseString()
225 }
226 if !p.context.empty && (unicode.IsDigit(char) || char == '-' || char == '.') {
227 return p.parseNumber()
228 }
229 if p.context.empty && (unicode.IsDigit(char) || char == '-' || char == '.') {
230 if onlyWhitespaceBefore(p) {
231 return p.parseNumber()
232 }
233 }
234 if char == '#' || char == '/' {
235 return p.parseComment()
236 }
237 if !p.context.empty && (char == 't' || char == 'f' || char == 'n') {
238 value := p.parseBooleanOrNull()
239 if value != "" {
240 return value, nil
241 }
242 return p.parseString()
243 }
244 if p.context.empty && (char == 't' || char == 'f' || char == 'n') {
245 if onlyWhitespaceBefore(p) {
246 value := p.parseBooleanOrNull()
247 if value != "" {
248 return value, nil
249 }
250 }
251 }
252 if p.context.empty && char == ':' {
253 return "", nil
254 }
255 p.index++
256 }
257}
258
259func (p *parser) getCharAt(offset int) (rune, bool) {
260 idx := p.index + offset
261 if idx < 0 || idx >= len(p.jsonStr) {
262 return 0, false
263 }
264 return p.jsonStr[idx], true
265}
266
267func (p *parser) skipWhitespaces() {
268 for {
269 char, ok := p.getCharAt(0)
270 if !ok || !unicode.IsSpace(char) {
271 return
272 }
273 p.index++
274 }
275}
276
277func (p *parser) scrollWhitespaces(idx int) int {
278 for {
279 char, ok := p.getCharAt(idx)
280 if !ok || !unicode.IsSpace(char) {
281 return idx
282 }
283 idx++
284 }
285}
286
287func (p *parser) skipToCharacter(character rune, idx int) int {
288 targets := map[rune]struct{}{character: {}}
289 return p.skipToCharacters(targets, idx)
290}
291
292func (p *parser) skipToCharacters(targets map[rune]struct{}, idx int) int {
293 i := p.index + idx
294 backslashes := 0
295 for i < len(p.jsonStr) {
296 ch := p.jsonStr[i]
297 if ch == '\\' {
298 backslashes++
299 i++
300 continue
301 }
302 if _, ok := targets[ch]; ok && backslashes%2 == 0 {
303 return i - p.index
304 }
305 backslashes = 0
306 i++
307 }
308 return len(p.jsonStr) - p.index
309}
310
311func (p *parser) parseArray() (any, error) {
312 arr := []any{}
313 p.context.set(contextArray)
314 char, ok := p.getCharAt(0)
315 for ok && char != ']' && char != '}' {
316 p.skipWhitespaces()
317 var value any
318 if isStringDelimiter(char) {
319 i := 1
320 i = p.skipToCharacter(char, i)
321 i = p.scrollWhitespaces(i + 1)
322 if nextChar, ok := p.getCharAt(i); ok && nextChar == ':' {
323 value, _ = p.parseObject()
324 } else {
325 value, _ = p.parseString()
326 }
327 } else {
328 var err error
329 value, err = p.parseJSON()
330 if err != nil {
331 return nil, err
332 }
333 }
334
335 if isStrictlyEmpty(value) {
336 if nextChar, ok := p.getCharAt(0); !ok || (nextChar != ']' && nextChar != ',') {
337 p.index++
338 } else {
339 arr = append(arr, value)
340 }
341 } else if strVal, ok := value.(string); ok && strVal == "..." {
342 if prev, ok := p.getCharAt(-1); ok && prev == '.' {
343 p.log("While parsing an array, found a stray '...'; ignoring it")
344 } else {
345 arr = append(arr, value)
346 }
347 } else {
348 arr = append(arr, value)
349 }
350
351 char, ok = p.getCharAt(0)
352 for ok && char != ']' && (unicode.IsSpace(char) || char == ',') {
353 p.index++
354 char, ok = p.getCharAt(0)
355 }
356 }
357
358 if char != ']' {
359 p.log("While parsing an array we missed the closing ], ignoring it")
360 }
361
362 p.index++
363 p.context.reset()
364 return arr, nil
365}
366
367func (p *parser) parseComment() (any, error) {
368 char, ok := p.getCharAt(0)
369 if !ok {
370 return "", nil
371 }
372 termination := map[rune]struct{}{'\n': {}, '\r': {}}
373 if p.context.contains(contextArray) {
374 termination[']'] = struct{}{}
375 }
376 if p.context.contains(contextObjectValue) {
377 termination['}'] = struct{}{}
378 }
379 if p.context.contains(contextObjectKey) {
380 termination[':'] = struct{}{}
381 }
382 if char == '#' {
383 comment := []rune{}
384 for ok {
385 if _, hit := termination[char]; hit {
386 break
387 }
388 comment = append(comment, char)
389 p.index++
390 char, ok = p.getCharAt(0)
391 }
392 p.log("Found line comment: " + string(comment) + ", ignoring")
393 } else if char == '/' {
394 nextChar, ok := p.getCharAt(1)
395 if ok && nextChar == '/' {
396 comment := []rune{'/', '/'}
397 p.index += 2
398 char, ok = p.getCharAt(0)
399 for ok {
400 if _, hit := termination[char]; hit {
401 break
402 }
403 comment = append(comment, char)
404 p.index++
405 char, ok = p.getCharAt(0)
406 }
407 p.log("Found line comment: " + string(comment) + ", ignoring")
408 } else if ok && nextChar == '*' {
409 comment := []rune{'/', '*'}
410 p.index += 2
411 for {
412 char, ok = p.getCharAt(0)
413 if !ok {
414 p.log("Reached end-of-string while parsing block comment; unclosed block comment.")
415 break
416 }
417 comment = append(comment, char)
418 p.index++
419 if len(comment) >= 2 && comment[len(comment)-2] == '*' && comment[len(comment)-1] == '/' {
420 break
421 }
422 }
423 p.log("Found block comment: " + string(comment) + ", ignoring")
424 } else {
425 p.index++
426 }
427 }
428 if p.context.empty {
429 return p.parseJSON()
430 }
431 return "", nil
432}
433
434func (p *parser) parseNumber() (any, error) {
435 numberChars := "0123456789-.eE/,_"
436 numberStr := ""
437 char, ok := p.getCharAt(0)
438 isArray := p.context.current != nil && *p.context.current == contextArray
439 for ok && strings.ContainsRune(numberChars, char) && (!isArray || char != ',' || strings.Contains(numberStr, "/")) {
440 if char != '_' {
441 numberStr += string(char)
442 }
443 p.index++
444 char, ok = p.getCharAt(0)
445 }
446 if nextChar, ok := p.getCharAt(0); ok && unicode.IsLetter(nextChar) {
447 p.index -= len([]rune(numberStr))
448 return p.parseString()
449 }
450 if len(numberStr) > 0 {
451 last := numberStr[len(numberStr)-1]
452 if last == '-' || last == 'e' || last == 'E' || last == '/' || last == ',' {
453 numberStr = numberStr[:len(numberStr)-1]
454 p.index--
455 }
456 }
457 if strings.Contains(numberStr, "/") || strings.Contains(numberStr, "-") || strings.Contains(numberStr, ",") {
458 if numberStr == "-" {
459 return "", nil
460 }
461 if strings.ContainsAny(numberStr, "eE") {
462 floatVal, err := strconv.ParseFloat(numberStr, 64)
463 if err == nil {
464 formatted := formatFloat(floatVal)
465 return numberValue{raw: formatted}, nil
466 }
467 return numberStr, nil
468 }
469 return numberStr, nil
470 }
471 if strings.ContainsAny(numberStr, ".eE") {
472 floatVal, err := strconv.ParseFloat(numberStr, 64)
473 if err == nil {
474 formatted := formatFloat(floatVal)
475 return numberValue{raw: formatted}, nil
476 }
477 return numberStr, nil
478 }
479 if numberStr == "" {
480 return "", nil
481 }
482 return numberValue{raw: numberStr}, nil
483}
484
485func (p *parser) parseObject() (any, error) {
486 obj := newOrderedObject()
487 startIndex := p.index
488 for {
489 p.skipWhitespaces()
490 char, ok := p.getCharAt(0)
491 if !ok || char == '}' {
492 break
493 }
494 if current, ok := p.getCharAt(0); ok && current == ':' {
495 p.log("While parsing an object we found a : before a key, ignoring")
496 p.index++
497 }
498 p.context.set(contextObjectKey)
499 rollbackIndex := p.index
500 key := ""
501 for {
502 current, ok := p.getCharAt(0)
503 if !ok {
504 break
505 }
506 rollbackIndex = p.index
507 if current == '[' && key == "" {
508 prevKey, ok := obj.lastKey()
509 if ok {
510 prevValue, _ := obj.get(prevKey)
511 if prevArray, ok := prevValue.([]any); ok && !p.strict {
512 p.index++
513 newArrayValue, err := p.parseArray()
514 if err != nil {
515 return nil, err
516 }
517 if newArray, ok := newArrayValue.([]any); ok {
518 listLengths := []int{}
519 for _, item := range prevArray {
520 if nested, ok := item.([]any); ok {
521 listLengths = append(listLengths, len(nested))
522 }
523 }
524 expectedLen := 0
525 if len(listLengths) > 0 {
526 same := true
527 for _, length := range listLengths {
528 if length != listLengths[0] {
529 same = false
530 break
531 }
532 }
533 if same {
534 expectedLen = listLengths[0]
535 }
536 }
537 if expectedLen > 0 {
538 tail := []any{}
539 for len(prevArray) > 0 {
540 if _, ok := prevArray[len(prevArray)-1].([]any); ok {
541 break
542 }
543 tail = append(tail, prevArray[len(prevArray)-1])
544 prevArray = prevArray[:len(prevArray)-1]
545 }
546 if len(tail) > 0 {
547 reverseAny(tail)
548 if len(tail)%expectedLen == 0 {
549 p.log("While parsing an object we found row values without an inner array, grouping them into rows")
550 for i := 0; i < len(tail); i += expectedLen {
551 prevArray = append(prevArray, tail[i:i+expectedLen])
552 }
553 } else {
554 prevArray = append(prevArray, tail...)
555 }
556 }
557 if len(newArray) > 0 {
558 allLists := true
559 for _, item := range newArray {
560 if _, ok := item.([]any); !ok {
561 allLists = false
562 break
563 }
564 }
565 if allLists {
566 p.log("While parsing an object we found additional rows, appending them without flattening")
567 prevArray = append(prevArray, newArray...)
568 } else {
569 prevArray = append(prevArray, newArray)
570 }
571 }
572 } else {
573 if len(newArray) == 1 {
574 if nested, ok := newArray[0].([]any); ok {
575 prevArray = append(prevArray, nested...)
576 } else {
577 prevArray = append(prevArray, newArray...)
578 }
579 } else {
580 prevArray = append(prevArray, newArray...)
581 }
582 }
583 obj.set(prevKey, prevArray)
584 }
585 p.skipWhitespaces()
586 if nextChar, ok := p.getCharAt(0); ok && nextChar == ',' {
587 p.index++
588 }
589 p.skipWhitespaces()
590 continue
591 }
592 }
593 }
594 rawKeyValue, err := p.parseString()
595 if err != nil {
596 return nil, err
597 }
598 rawKey, _ := rawKeyValue.(string)
599 key = rawKey
600 if key == "" {
601 p.skipWhitespaces()
602 }
603 if key != "" || (key == "" && func() bool { ch, ok := p.getCharAt(0); return ok && (ch == ':' || ch == '}') }()) {
604 if key == "" && p.strict {
605 p.log("Empty key found in strict mode while parsing object, raising an error")
606 return nil, errors.New("empty key found in strict mode while parsing object")
607 }
608 break
609 }
610 }
611 if p.context.contains(contextArray) && obj.hasKey(key) {
612 if p.strict {
613 p.log("Duplicate key found in strict mode while parsing object, raising an error")
614 return nil, errors.New("duplicate key found in strict mode while parsing object")
615 }
616 p.log("While parsing an object we found a duplicate key, closing the object here and rolling back the index")
617 p.index = rollbackIndex - 1
618 p.insertRune(p.index+1, '{')
619 break
620 }
621 p.skipWhitespaces()
622 if current, ok := p.getCharAt(0); !ok || current == '}' {
623 continue
624 }
625 p.skipWhitespaces()
626 if current, ok := p.getCharAt(0); ok && current != ':' {
627 if p.strict {
628 p.log("Missing ':' after key in strict mode while parsing object, raising an error")
629 return nil, errors.New("missing ':' after key in strict mode while parsing object")
630 }
631 p.log("While parsing an object we missed a : after a key")
632 }
633 p.index++
634 p.context.reset()
635 p.context.set(contextObjectValue)
636 p.skipWhitespaces()
637 value := any("")
638 if current, ok := p.getCharAt(0); ok && (current == ',' || current == '}') {
639 p.log("While parsing an object value we found a stray " + string(current) + ", ignoring it")
640 } else {
641 var err error
642 value, err = p.parseJSON()
643 if err != nil {
644 return nil, err
645 }
646 }
647 if value == "" && p.strict {
648 if prev, ok := p.getCharAt(-1); !ok || !isStringDelimiter(prev) {
649 p.log("Parsed value is empty in strict mode while parsing object, raising an error")
650 return nil, errors.New("parsed value is empty in strict mode while parsing object")
651 }
652 }
653 p.context.reset()
654 obj.set(key, value)
655 if current, ok := p.getCharAt(0); ok && (current == ',' || current == '\'' || current == '"') {
656 p.index++
657 }
658 if current, ok := p.getCharAt(0); ok && current == ']' && p.context.contains(contextArray) {
659 p.log("While parsing an object we found a closing array bracket, closing the object here and rolling back the index")
660 p.index--
661 break
662 }
663 p.skipWhitespaces()
664 }
665 p.index++
666 if len(obj.entries) == 0 && p.index-startIndex > 2 {
667 if p.strict {
668 p.log("Parsed object is empty but contains extra characters in strict mode, raising an error")
669 return nil, errors.New("parsed object is empty but contains extra characters in strict mode")
670 }
671 if p.context.empty && p.index-startIndex <= 3 {
672 return obj, nil
673 }
674 if p.context.empty {
675 prefix := string(p.jsonStr[:startIndex-1])
676 if strings.TrimSpace(prefix) == "" {
677 return obj, nil
678 }
679 }
680 p.log("Parsed object is empty, we will try to parse this as an array instead")
681 p.index = startIndex
682 return p.parseArray()
683 }
684 if len(obj.entries) == 0 && p.index-startIndex <= 2 {
685 return obj, nil
686 }
687 if !p.context.empty {
688 if current, ok := p.getCharAt(0); ok && current == '}' {
689 if p.context.current == nil || (*p.context.current != contextObjectKey && *p.context.current != contextObjectValue) {
690 p.log("Found an extra closing brace that shouldn't be there, skipping it")
691 p.index++
692 }
693 }
694 return obj, nil
695 }
696 p.skipWhitespaces()
697 if current, ok := p.getCharAt(0); !ok || current != ',' {
698 return obj, nil
699 }
700 p.index++
701 p.skipWhitespaces()
702 if current, ok := p.getCharAt(0); !ok || !isStringDelimiter(current) {
703 return obj, nil
704 }
705 if !p.strict {
706 p.log("Found a comma and string delimiter after object closing brace, checking for additional key-value pairs")
707 additionalValue, err := p.parseObject()
708 if err != nil {
709 return nil, err
710 }
711 if additionalObj, ok := additionalValue.(*orderedObject); ok {
712 obj.merge(additionalObj)
713 }
714 }
715 return obj, nil
716}
717
718func (p *parser) parseString() (any, error) {
719 missingQuotes := false
720 doubledQuotes := false
721 ldelim := '"'
722 rdelim := '"'
723
724 char, ok := p.getCharAt(0)
725 if ok && (char == '#' || char == '/') {
726 return p.parseComment()
727 }
728 for ok && !isStringDelimiter(char) && !isAlphaNum(char) {
729 p.index++
730 char, ok = p.getCharAt(0)
731 }
732 if !ok {
733 return "", nil
734 }
735 if char == '\'' {
736 ldelim = '\''
737 rdelim = '\''
738 } else if char == '“' {
739 ldelim = '“'
740 rdelim = '”'
741 } else if isAlphaNum(char) {
742 if (char == 't' || char == 'f' || char == 'n') && (p.context.current == nil || *p.context.current != contextObjectKey) {
743 value := p.parseBooleanOrNull()
744 if value != "" {
745 return value, nil
746 }
747 }
748 if (char == 'T' || char == 'F' || char == 'N') && (p.context.current == nil || *p.context.current != contextObjectKey) {
749 value := p.parseBooleanOrNull()
750 if value != "" {
751 return value, nil
752 }
753 }
754 p.log("While parsing a string, we found a literal instead of a quote")
755 missingQuotes = true
756 }
757
758 if !missingQuotes {
759 p.index++
760 }
761 if next, ok := p.getCharAt(0); ok && next == '`' {
762 if value, ok := p.parseJSONLLMBlock(); ok {
763 return value, nil
764 }
765 if p.context.empty {
766 return "", nil
767 }
768 p.log("While parsing a string, we found code fences but they did not enclose valid JSON, continuing parsing the string")
769 }
770
771 if next, ok := p.getCharAt(0); ok && next == ldelim {
772 if (p.context.current != nil && *p.context.current == contextObjectKey && func() bool { ch, ok := p.getCharAt(1); return ok && ch == ':' }()) ||
773 (p.context.current != nil && *p.context.current == contextObjectValue && func() bool { ch, ok := p.getCharAt(1); return ok && (ch == ',' || ch == '}') }()) ||
774 (p.context.current != nil && *p.context.current == contextArray && func() bool { ch, ok := p.getCharAt(1); return ok && (ch == ',' || ch == ']') }()) {
775 p.index++
776 return "", nil
777 }
778 if p.context.current != nil && *p.context.current == contextObjectKey {
779 i := p.scrollWhitespaces(1)
780 if ch, ok := p.getCharAt(i); ok && ch == ':' {
781 p.index++
782 return "", nil
783 }
784 }
785 if next2, ok := p.getCharAt(1); ok && next2 == ldelim {
786 p.log("While parsing a string, we found a doubled quote and then a quote again, ignoring it")
787 if p.strict {
788 return nil, errors.New("found doubled quotes followed by another quote")
789 }
790 return "", nil
791 }
792 i := p.skipToCharacter(rdelim, 1)
793 if nextChar, ok := p.getCharAt(i + 1); ok && nextChar == rdelim {
794 p.log("While parsing a string, we found a valid starting doubled quote")
795 doubledQuotes = true
796 p.index++
797 } else {
798 i = p.scrollWhitespaces(1)
799 nextChar, ok := p.getCharAt(i)
800 if ok && (isStringDelimiter(nextChar) || nextChar == '{' || nextChar == '[') {
801 p.log("While parsing a string, we found a doubled quote but also another quote afterwards, ignoring it")
802 if p.strict {
803 return nil, errors.New("found doubled quotes followed by another quote while parsing a string")
804 }
805 p.index++
806 return "", nil
807 }
808 if !ok || (nextChar != ',' && nextChar != ']' && nextChar != '}') {
809 p.log("While parsing a string, we found a doubled quote but it was a mistake, removing one quote")
810 p.index++
811 }
812 }
813 }
814
815 stringAcc := []rune{}
816 char, ok = p.getCharAt(0)
817 unmatchedDelimiter := false
818 for ok && char != rdelim {
819 if missingQuotes {
820 if p.context.current != nil && *p.context.current == contextObjectKey {
821 if char == ':' || unicode.IsSpace(char) {
822 p.log("While parsing a string missing the left delimiter in object key context, we found a :, stopping here")
823 break
824 }
825 }
826 if p.context.current != nil && *p.context.current == contextArray {
827 if char == ']' || char == ',' {
828 p.log("While parsing a string missing the left delimiter in array context, we found a ] or ,, stopping here")
829 break
830 }
831 }
832 }
833 if !p.streamStable && p.context.current != nil && *p.context.current == contextObjectValue {
834 if (char == ',' || char == '}') && (len(stringAcc) == 0 || stringAcc[len(stringAcc)-1] != rdelim) {
835 rstringDelimiterMissing := true
836 next := rune(0)
837 p.skipWhitespaces()
838 if next, ok := p.getCharAt(1); ok && next == '\\' {
839 rstringDelimiterMissing = false
840 }
841 i := p.skipToCharacter(rdelim, 1)
842 if _, ok := p.getCharAt(i); ok {
843 i++
844 i = p.scrollWhitespaces(i)
845 next, _ = p.getCharAt(i)
846 if next == ',' || next == '}' {
847 rstringDelimiterMissing = false
848 } else {
849 i = p.skipToCharacter(ldelim, i)
850 if _, ok := p.getCharAt(i); !ok {
851 rstringDelimiterMissing = false
852 } else {
853 i = p.scrollWhitespaces(i + 1)
854 next, _ = p.getCharAt(i)
855 if next != ':' {
856 rstringDelimiterMissing = false
857 }
858 }
859 }
860 } else {
861 i = p.skipToCharacter(':', 1)
862 if _, ok := p.getCharAt(i); ok {
863 break
864 }
865 i = p.scrollWhitespaces(1)
866 j := p.skipToCharacter('}', i)
867 if j-i > 1 {
868 rstringDelimiterMissing = false
869 } else if _, ok := p.getCharAt(j); ok {
870 for k := len(stringAcc) - 1; k >= 0; k-- {
871 if stringAcc[k] == '{' {
872 rstringDelimiterMissing = false
873 break
874 }
875 }
876 }
877 }
878 if rstringDelimiterMissing {
879 p.log("While parsing a string missing the left delimiter in object value context, we found a , or } and we couldn't determine that a right delimiter was present. Stopping here")
880 break
881 }
882 }
883 }
884 if !p.streamStable && p.context.contains(contextArray) && char == ']' {
885 i := p.skipToCharacter(rdelim, 0)
886 if _, ok := p.getCharAt(i); !ok {
887 break
888 }
889 }
890 if p.context.current != nil && *p.context.current == contextObjectValue && char == '}' {
891 i := p.scrollWhitespaces(1)
892 nextChar, ok := p.getCharAt(i)
893 if ok && nextChar == '`' {
894 if c1, ok := p.getCharAt(i + 1); ok && c1 == '`' {
895 if c2, ok := p.getCharAt(i + 2); ok && c2 == '`' {
896 p.log("While parsing a string in object value context, we found a } that closes the object before code fences, stopping here")
897 break
898 }
899 }
900 }
901 if !ok {
902 p.log("While parsing a string in object value context, we found a } that closes the object, stopping here")
903 break
904 }
905 }
906 stringAcc = append(stringAcc, char)
907 p.index++
908 char, ok = p.getCharAt(0)
909 if !ok {
910 if p.streamStable && len(stringAcc) > 0 && stringAcc[len(stringAcc)-1] == '\\' {
911 stringAcc = stringAcc[:len(stringAcc)-1]
912 }
913 break
914 }
915 if len(stringAcc) > 0 && stringAcc[len(stringAcc)-1] == '\\' {
916 p.log("Found a stray escape sequence, normalizing it")
917 if char == rdelim || char == 't' || char == 'n' || char == 'r' || char == 'b' || char == '\\' {
918 stringAcc = stringAcc[:len(stringAcc)-1]
919 escapeSeqs := map[rune]rune{'t': '\t', 'n': '\n', 'r': '\r', 'b': '\b'}
920 if replacement, ok := escapeSeqs[char]; ok {
921 stringAcc = append(stringAcc, replacement)
922 } else {
923 stringAcc = append(stringAcc, char)
924 }
925 p.index++
926 char, ok = p.getCharAt(0)
927 for ok && len(stringAcc) > 0 && stringAcc[len(stringAcc)-1] == '\\' && (char == rdelim || char == '\\') {
928 stringAcc = append(stringAcc[:len(stringAcc)-1], char)
929 p.index++
930 char, ok = p.getCharAt(0)
931 }
932 continue
933 }
934 if char == 'u' || char == 'x' {
935 numChars := 4
936 if char == 'x' {
937 numChars = 2
938 }
939 nextChars := p.sliceRunes(p.index+1, p.index+1+numChars)
940 if len(nextChars) == numChars && isHexString(string(nextChars)) {
941 p.log("Found a unicode escape sequence, normalizing it")
942 parsed, _ := strconv.ParseInt(string(nextChars), 16, 32)
943 stringAcc = append(stringAcc[:len(stringAcc)-1], rune(parsed))
944 p.index += 1 + numChars
945 char, ok = p.getCharAt(0)
946 continue
947 }
948 } else if isStringDelimiter(char) && char != rdelim {
949 p.log("Found a delimiter that was escaped but shouldn't be escaped, removing the escape")
950 stringAcc = append(stringAcc[:len(stringAcc)-1], char)
951 p.index++
952 char, ok = p.getCharAt(0)
953 continue
954 }
955 }
956 if char == ':' && !missingQuotes && p.context.current != nil && *p.context.current == contextObjectKey {
957 i := p.skipToCharacter(ldelim, 1)
958 if _, ok := p.getCharAt(i); ok {
959 i++
960 i = p.skipToCharacter(rdelim, i)
961 if _, ok := p.getCharAt(i); ok {
962 i++
963 i = p.scrollWhitespaces(i)
964 ch, ok := p.getCharAt(i)
965 if ok && (ch == ',' || ch == '}') {
966 p.log("While parsing a string missing the right delimiter in object key context, we found a " + string(ch) + " stopping here")
967 break
968 }
969 }
970 } else {
971 p.log("While parsing a string missing the right delimiter in object key context, we found a :, stopping here")
972 break
973 }
974 }
975 if char == rdelim && (len(stringAcc) == 0 || stringAcc[len(stringAcc)-1] != '\\') {
976 if doubledQuotes {
977 if next, ok := p.getCharAt(1); ok && next == rdelim {
978 p.log("While parsing a string, we found a doubled quote, ignoring it")
979 p.index++
980 }
981 } else if missingQuotes && p.context.current != nil && *p.context.current == contextObjectValue {
982 i := 1
983 nextChar, ok := p.getCharAt(i)
984 for ok && nextChar != rdelim && nextChar != ldelim {
985 i++
986 nextChar, ok = p.getCharAt(i)
987 }
988 if ok {
989 i++
990 i = p.scrollWhitespaces(i)
991 if ch, ok := p.getCharAt(i); ok && ch == ':' {
992 p.index--
993 char, _ = p.getCharAt(0)
994 p.log("In a string with missing quotes and object value context, I found a delimeter but it turns out it was the beginning on the next key. Stopping here.")
995 break
996 }
997 }
998 } else if unmatchedDelimiter {
999 unmatchedDelimiter = false
1000 stringAcc = append(stringAcc, char)
1001 p.index++
1002 char, ok = p.getCharAt(0)
1003 } else {
1004 i := 1
1005 nextChar, ok := p.getCharAt(i)
1006 checkCommaInObjectValue := true
1007 for ok && nextChar != rdelim && nextChar != ldelim {
1008 if checkCommaInObjectValue && unicode.IsLetter(nextChar) {
1009 checkCommaInObjectValue = false
1010 }
1011 if (p.context.contains(contextObjectKey) && (nextChar == ':' || nextChar == '}')) ||
1012 (p.context.contains(contextObjectValue) && nextChar == '}') ||
1013 (p.context.contains(contextArray) && (nextChar == ']' || nextChar == ',')) ||
1014 (checkCommaInObjectValue && p.context.current != nil && *p.context.current == contextObjectValue && nextChar == ',') {
1015 break
1016 }
1017 i++
1018 nextChar, ok = p.getCharAt(i)
1019 }
1020 if nextChar == ',' && p.context.current != nil && *p.context.current == contextObjectValue {
1021 i++
1022 i = p.skipToCharacter(rdelim, i)
1023 i++
1024 i = p.scrollWhitespaces(i)
1025 nextChar, _ = p.getCharAt(i)
1026 if nextChar == '}' || nextChar == ',' {
1027 p.log("While parsing a string, we found a misplaced quote that would have closed the string but has a different meaning here, ignoring it")
1028 stringAcc = append(stringAcc, char)
1029 p.index++
1030 char, ok = p.getCharAt(0)
1031 if !ok {
1032 break
1033 }
1034 continue
1035 }
1036 } else if nextChar == rdelim && func() bool { prev, ok := p.getCharAt(i - 1); return ok && prev != '\\' }() {
1037 if onlyWhitespaceUntil(p, i) {
1038 break
1039 }
1040 if p.context.current != nil && *p.context.current == contextObjectValue {
1041 i = p.scrollWhitespaces(i + 1)
1042 if ch, ok := p.getCharAt(i); ok && ch == ',' {
1043 i = p.skipToCharacter(ldelim, i+1)
1044 i++
1045 i = p.skipToCharacter(rdelim, i+1)
1046 i++
1047 i = p.scrollWhitespaces(i)
1048 if ch, ok := p.getCharAt(i); ok && ch == ':' {
1049 p.log("While parsing a string, we found a misplaced quote that would have closed the string but has a different meaning here, ignoring it")
1050 stringAcc = append(stringAcc, char)
1051 p.index++
1052 char, ok = p.getCharAt(0)
1053 if !ok {
1054 break
1055 }
1056 continue
1057 }
1058 }
1059 i = p.skipToCharacter(rdelim, i+1)
1060 i++
1061 nextChar, ok = p.getCharAt(i)
1062 for ok && nextChar != ':' {
1063 if nextChar == ',' || nextChar == ']' || nextChar == '}' || (nextChar == rdelim && func() bool { prev, ok := p.getCharAt(i - 1); return ok && prev != '\\' }()) {
1064 break
1065 }
1066 i++
1067 nextChar, ok = p.getCharAt(i)
1068 }
1069 if nextChar != ':' {
1070 p.log("While parsing a string, we found a misplaced quote that would have closed the string but has a different meaning here, ignoring it")
1071 unmatchedDelimiter = !unmatchedDelimiter
1072 stringAcc = append(stringAcc, char)
1073 p.index++
1074 char, ok = p.getCharAt(0)
1075 if !ok {
1076 break
1077 }
1078 }
1079 } else if p.context.current != nil && *p.context.current == contextArray {
1080 evenDelimiters := nextChar == rdelim
1081 for nextChar == rdelim {
1082 i = p.skipToCharacters(map[rune]struct{}{rdelim: {}, ']': {}}, i+1)
1083 nextChar, ok = p.getCharAt(i)
1084 if !ok || nextChar != rdelim {
1085 evenDelimiters = false
1086 break
1087 }
1088 i = p.skipToCharacters(map[rune]struct{}{rdelim: {}, ']': {}}, i+1)
1089 nextChar, _ = p.getCharAt(i)
1090 }
1091 if evenDelimiters {
1092 p.log("While parsing a string in Array context, we detected a quoted section that would have closed the string but has a different meaning here, ignoring it")
1093 unmatchedDelimiter = !unmatchedDelimiter
1094 stringAcc = append(stringAcc, char)
1095 p.index++
1096 char, ok = p.getCharAt(0)
1097 if !ok {
1098 break
1099 }
1100 } else {
1101 break
1102 }
1103 } else if p.context.current != nil && *p.context.current == contextObjectKey {
1104 p.log("While parsing a string in Object Key context, we detected a quoted section that would have closed the string but has a different meaning here, ignoring it")
1105 stringAcc = append(stringAcc, char)
1106 p.index++
1107 char, ok = p.getCharAt(0)
1108 if !ok {
1109 break
1110 }
1111 }
1112 }
1113 }
1114 }
1115 }
1116 if ok && missingQuotes && p.context.current != nil && *p.context.current == contextObjectKey && unicode.IsSpace(char) {
1117 p.log("While parsing a string, handling an extreme corner case in which the LLM added a comment instead of valid string, invalidate the string and return an empty value")
1118 p.skipWhitespaces()
1119 if ch, ok := p.getCharAt(0); ok {
1120 if ch != ':' && ch != ',' {
1121 p.index--
1122 return "", nil
1123 }
1124 if ch == ',' {
1125 p.index--
1126 return "", nil
1127 }
1128 }
1129 }
1130 if missingQuotes && p.context.current != nil && *p.context.current == contextObjectKey {
1131 if !onlyWhitespaceUntil(p, p.scrollWhitespaces(0)) {
1132 stringAcc = trimRightWhitespace(stringAcc)
1133 if len(stringAcc) == 0 {
1134 return "", nil
1135 }
1136 }
1137 }
1138 if !ok || char != rdelim {
1139 if !p.streamStable {
1140 p.log("While parsing a string, we missed the closing quote, ignoring")
1141 stringAcc = trimRightWhitespace(stringAcc)
1142 }
1143 } else {
1144 p.index++
1145 }
1146 if !p.streamStable && (missingQuotes || (len(stringAcc) > 0 && stringAcc[len(stringAcc)-1] == '\n')) {
1147 stringAcc = trimRightWhitespace(stringAcc)
1148 }
1149 if missingQuotes && p.context.empty {
1150 next := p.scrollWhitespaces(0)
1151 if ch, ok := p.getCharAt(next); ok && (ch == '{' || ch == '[' || ch == '`') {
1152 return "", nil
1153 }
1154 if !p.streamStable {
1155 stringAcc = trimRightWhitespace(stringAcc)
1156 }
1157 if len(stringAcc) == 0 {
1158 return "", nil
1159 }
1160 }
1161 if p.context.empty {
1162 next := p.scrollWhitespaces(0)
1163 if ch, ok := p.getCharAt(next); ok && (ch == '{' || ch == '[' || ch == '`') {
1164 return "", nil
1165 }
1166 }
1167 if len(stringAcc) == 1 && stringAcc[0] == rdelim {
1168 return "", nil
1169 }
1170 if p.context.empty && missingQuotes {
1171 if len(stringAcc) == 1 && stringAcc[0] == '"' {
1172 return "", nil
1173 }
1174 }
1175 return string(stringAcc), nil
1176}
1177
1178func (p *parser) parseBooleanOrNull() any {
1179 char, ok := p.getCharAt(0)
1180 if !ok {
1181 return ""
1182 }
1183 valueMap := map[rune]struct {
1184 token string
1185 value any
1186 }{
1187 't': {"true", true},
1188 'f': {"false", false},
1189 'n': {"null", nil},
1190 }
1191 lower := unicode.ToLower(char)
1192 value, ok := valueMap[lower]
1193 if !ok {
1194 return ""
1195 }
1196 matchUpper := unicode.IsUpper(char)
1197 i := 0
1198 startingIndex := p.index
1199 current := lower
1200 for ok && i < len(value.token) && current == rune(value.token[i]) {
1201 i++
1202 p.index++
1203 char, ok = p.getCharAt(0)
1204 if ok {
1205 if unicode.IsUpper(char) {
1206 matchUpper = true
1207 }
1208 current = unicode.ToLower(char)
1209 }
1210 }
1211 if i == len(value.token) {
1212 if matchUpper && p.context.empty {
1213 p.index = startingIndex
1214 return ""
1215 }
1216 return value.value
1217 }
1218 p.index = startingIndex
1219 return ""
1220}
1221
1222func (p *parser) parseJSONLLMBlock() (any, bool) {
1223 if p.sliceString(p.index, p.index+7) == "```json" {
1224 i := p.skipToCharacter('`', 7)
1225 if p.sliceString(p.index+i, p.index+i+3) == "```" {
1226 p.index += 7
1227 value, err := p.parseJSON()
1228 if err != nil {
1229 return nil, false
1230 }
1231 return value, true
1232 }
1233 }
1234 return nil, false
1235}
1236
1237func (p *parser) sliceRunes(start int, end int) []rune {
1238 if start < 0 {
1239 start = 0
1240 }
1241 if end > len(p.jsonStr) {
1242 end = len(p.jsonStr)
1243 }
1244 if start > end {
1245 return []rune{}
1246 }
1247 return p.jsonStr[start:end]
1248}
1249
1250func (p *parser) sliceString(start int, end int) string {
1251 return string(p.sliceRunes(start, end))
1252}
1253
1254func (p *parser) insertRune(pos int, r rune) {
1255 if pos < 0 {
1256 pos = 0
1257 }
1258 if pos > len(p.jsonStr) {
1259 pos = len(p.jsonStr)
1260 }
1261 p.jsonStr = append(p.jsonStr[:pos], append([]rune{r}, p.jsonStr[pos:]...)...)
1262}
1263
1264func onlyWhitespaceUntil(p *parser, end int) bool {
1265 for j := 1; j < end; j++ {
1266 c, ok := p.getCharAt(j)
1267 if ok && !unicode.IsSpace(c) {
1268 return false
1269 }
1270 }
1271 return true
1272}
1273
1274func onlyWhitespaceBefore(p *parser) bool {
1275 for i := p.index - 1; i >= 0; i-- {
1276 c := p.jsonStr[i]
1277 if !unicode.IsSpace(c) {
1278 return false
1279 }
1280 }
1281 return true
1282}
1283
1284func reverseAny(values []any) {
1285 for i, j := 0, len(values)-1; i < j; i, j = i+1, j-1 {
1286 values[i], values[j] = values[j], values[i]
1287 }
1288}
1289
1290func isStrictlyEmpty(value any) bool {
1291 switch v := value.(type) {
1292 case string:
1293 return len(v) == 0
1294 case []any:
1295 return len(v) == 0
1296 case *orderedObject:
1297 return len(v.entries) == 0
1298 default:
1299 return false
1300 }
1301}
1302
1303func isSameObject(obj1 any, obj2 any) bool {
1304 switch v1 := obj1.(type) {
1305 case *orderedObject:
1306 v2, ok := obj2.(*orderedObject)
1307 if !ok {
1308 return false
1309 }
1310 if len(v1.entries) != len(v2.entries) {
1311 return false
1312 }
1313 for _, entry := range v1.entries {
1314 val2, ok := v2.get(entry.key)
1315 if !ok {
1316 return false
1317 }
1318 if !isSameObject(entry.value, val2) {
1319 return false
1320 }
1321 }
1322 return true
1323 case []any:
1324 v2, ok := obj2.([]any)
1325 if !ok {
1326 return false
1327 }
1328 if len(v1) != len(v2) {
1329 return false
1330 }
1331 for i := range v1 {
1332 if !isSameObject(v1[i], v2[i]) {
1333 return false
1334 }
1335 }
1336 return true
1337 default:
1338 if obj1 == nil || obj2 == nil {
1339 return obj1 == obj2
1340 }
1341 return reflect.TypeOf(obj1) == reflect.TypeOf(obj2)
1342 }
1343}
1344
1345func isTruthy(value any) bool {
1346 switch v := value.(type) {
1347 case string:
1348 return v != ""
1349 case []any:
1350 return len(v) > 0
1351 case *orderedObject:
1352 return len(v.entries) > 0
1353 case bool:
1354 return v
1355 case numberValue:
1356 return v.raw != ""
1357 case nil:
1358 return false
1359 default:
1360 return true
1361 }
1362}
1363
1364func isStringDelimiter(char rune) bool {
1365 switch char {
1366 case '"', '\'', '“', '”':
1367 return true
1368 default:
1369 return false
1370 }
1371}
1372
1373func isAlphaNum(char rune) bool {
1374 return unicode.IsLetter(char) || unicode.IsDigit(char)
1375}
1376
1377func trimRightWhitespace(values []rune) []rune {
1378 for len(values) > 0 {
1379 if !unicode.IsSpace(values[len(values)-1]) {
1380 break
1381 }
1382 values = values[:len(values)-1]
1383 }
1384 return values
1385}
1386
1387func isHexString(value string) bool {
1388 if value == "" {
1389 return false
1390 }
1391 for _, c := range value {
1392 if (c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F') {
1393 return false
1394 }
1395 }
1396 return true
1397}
1398
1399func formatFloat(value float64) string {
1400 formatted := strconv.FormatFloat(value, 'f', -1, 64)
1401 if !strings.Contains(formatted, ".") {
1402 formatted += ".0"
1403 }
1404 return formatted
1405}
1406
1407func applyOptions(opts []Option) options {
1408 cfg := options{}
1409 for _, opt := range opts {
1410 if opt != nil {
1411 opt(&cfg)
1412 }
1413 }
1414 return cfg
1415}
1416
1417func ensureASCIIValue(cfg options) bool {
1418 if cfg.ensureASCII == nil {
1419 return true
1420 }
1421 return *cfg.ensureASCII
1422}
1423
1424// WithEnsureASCII sets whether to escape non-ASCII characters.
1425func WithEnsureASCII(value bool) Option {
1426 return func(o *options) {
1427 o.ensureASCII = &value
1428 }
1429}
1430
1431// WithSkipJSONLoads skips JSON parsing during load.
1432func WithSkipJSONLoads() Option {
1433 return func(o *options) {
1434 o.skipJSONLoads = true
1435 }
1436}
1437
1438// WithStreamStable enables streaming-stable parsing.
1439func WithStreamStable() Option {
1440 return func(o *options) {
1441 o.streamStable = true
1442 }
1443}
1444
1445// WithStrict enables strict parsing mode.
1446func WithStrict() Option {
1447 return func(o *options) {
1448 o.strict = true
1449 }
1450}
1451
1452// RepairJSON takes a potentially malformed JSON string output from LLMs and
1453// attempts to repair it into a valid JSON string. It returns the repaired JSON
1454// string or an error if the input cannot be repaired.
1455func RepairJSON(input string, opts ...Option) (string, error) {
1456 cfg := applyOptions(opts)
1457 p := newParser(input, false, cfg.streamStable, cfg.strict)
1458 value, _, err := p.parse()
1459 if err != nil {
1460 return "", err
1461 }
1462 if str, ok := value.(string); ok {
1463 trimmed := strings.TrimSpace(str)
1464 if str == "" || trimmed == "" {
1465 return "", nil
1466 }
1467 return "", nil
1468 }
1469 if value == "" {
1470 return "", nil
1471 }
1472 return serialize(value, ensureASCIIValue(cfg)), nil
1473}
1474
1475// Loads takes a potentially malformed JSON string output from LLMs and attempts
1476// to repair it and parse it into a Go value.
1477func Loads(input string, opts ...Option) (any, error) {
1478 cfg := applyOptions(opts)
1479 p := newParser(input, false, cfg.streamStable, cfg.strict)
1480 value, _, err := p.parse()
1481 if err != nil {
1482 return nil, err
1483 }
1484 if value == "" {
1485 return "", nil
1486 }
1487 return normalizeValue(value), nil
1488}
1489
1490// RepairJSONWithLog takes a potentially malformed JSON string output from LLMs
1491// and attempts to repair it into a valid JSON string, while also returning logs
1492// of the repair process.
1493func RepairJSONWithLog(input string, opts ...Option) (any, []LogEntry, error) {
1494 cfg := applyOptions(opts)
1495 p := newParser(input, true, cfg.streamStable, cfg.strict)
1496 value, logs, err := p.parse()
1497 if err != nil {
1498 return nil, nil, err
1499 }
1500 if logs == nil {
1501 logs = []LogEntry{}
1502 }
1503 if value == "" {
1504 return "", logs, nil
1505 }
1506 return normalizeValue(value), logs, nil
1507}
1508
1509func normalizeValue(value any) any {
1510 switch v := value.(type) {
1511 case *orderedObject:
1512 result := map[string]any{}
1513 for _, entry := range v.entries {
1514 result[entry.key] = normalizeValue(entry.value)
1515 }
1516 return result
1517 case []any:
1518 items := make([]any, 0, len(v))
1519 for _, item := range v {
1520 items = append(items, normalizeValue(item))
1521 }
1522 return items
1523 case numberValue:
1524 return json.Number(v.raw)
1525 default:
1526 return v
1527 }
1528}
1529
1530func serialize(value any, ensureASCII bool) string {
1531 var buf bytes.Buffer
1532 writeValue(&buf, value, ensureASCII)
1533 return buf.String()
1534}
1535
1536func writeValue(buf *bytes.Buffer, value any, ensureASCII bool) {
1537 switch v := value.(type) {
1538 case string:
1539 buf.WriteByte('"')
1540 writeEscapedString(buf, v, ensureASCII)
1541 buf.WriteByte('"')
1542 case numberValue:
1543 buf.WriteString(v.raw)
1544 case json.Number:
1545 buf.WriteString(v.String())
1546 case bool:
1547 if v {
1548 buf.WriteString("true")
1549 } else {
1550 buf.WriteString("false")
1551 }
1552 case nil:
1553 buf.WriteString("null")
1554 case []any:
1555 buf.WriteByte('[')
1556 for i, item := range v {
1557 if i > 0 {
1558 buf.WriteString(", ")
1559 }
1560 writeValue(buf, item, ensureASCII)
1561 }
1562 buf.WriteByte(']')
1563 case *orderedObject:
1564 buf.WriteByte('{')
1565 for i, entry := range v.entries {
1566 if i > 0 {
1567 buf.WriteString(", ")
1568 }
1569 buf.WriteByte('"')
1570 writeEscapedString(buf, entry.key, ensureASCII)
1571 buf.WriteByte('"')
1572 buf.WriteString(": ")
1573 writeValue(buf, entry.value, ensureASCII)
1574 }
1575 buf.WriteByte('}')
1576 case float64:
1577 buf.WriteString(formatFloat(v))
1578 case int:
1579 buf.WriteString(strconv.Itoa(v))
1580 case int64:
1581 buf.WriteString(strconv.FormatInt(v, 10))
1582 case uint64:
1583 buf.WriteString(strconv.FormatUint(v, 10))
1584 case map[string]any:
1585 buf.WriteByte('{')
1586 idx := 0
1587 for key, item := range v {
1588 if idx > 0 {
1589 buf.WriteString(", ")
1590 }
1591 buf.WriteByte('"')
1592 writeEscapedString(buf, key, ensureASCII)
1593 buf.WriteByte('"')
1594 buf.WriteString(": ")
1595 writeValue(buf, item, ensureASCII)
1596 idx++
1597 }
1598 buf.WriteByte('}')
1599 default:
1600 buf.WriteString("null")
1601 }
1602}
1603
1604func writeEscapedString(buf *bytes.Buffer, value string, ensureASCII bool) {
1605 for _, r := range value {
1606 switch r {
1607 case '\\':
1608 buf.WriteString("\\\\")
1609 case '"':
1610 buf.WriteString("\\\"")
1611 case '\b':
1612 buf.WriteString("\\b")
1613 case '\f':
1614 buf.WriteString("\\f")
1615 case '\n':
1616 buf.WriteString("\\n")
1617 case '\r':
1618 buf.WriteString("\\r")
1619 case '\t':
1620 buf.WriteString("\\t")
1621 default:
1622 if r < 0x20 {
1623 buf.WriteString("\\u")
1624 buf.WriteString(hex4(r))
1625 continue
1626 }
1627 if ensureASCII && r > 0x7f {
1628 if r > 0xFFFF {
1629 for _, rr := range utf16.Encode([]rune{r}) {
1630 buf.WriteString("\\u")
1631 buf.WriteString(hex4(rune(rr)))
1632 }
1633 continue
1634 }
1635 buf.WriteString("\\u")
1636 buf.WriteString(hex4(r))
1637 continue
1638 }
1639 buf.WriteRune(r)
1640 }
1641 }
1642}
1643
1644func hex4(r rune) string {
1645 value := int(r)
1646 result := strconv.FormatInt(int64(value), 16)
1647 return strings.Repeat("0", 4-len(result)) + strings.ToLower(result)
1648}