skip.go raw
1 package filter
2
3 import (
4 "next.orly.dev/pkg/lol/errorf"
5 )
6
7 // skipJSONValue skips over an arbitrary JSON value and returns the raw bytes and remainder.
8 // It handles: objects {}, arrays [], strings "", numbers, true, false, null.
9 // The input `b` should start at the first character of the value (after the colon in "key":value).
10 func skipJSONValue(b []byte) (val []byte, r []byte, err error) {
11 if len(b) == 0 {
12 err = errorf.E("empty input")
13 return
14 }
15
16 start := 0
17 end := 0
18
19 switch b[0] {
20 case '{':
21 // Object - find matching closing brace
22 end, err = findMatchingBrace(b, '{', '}')
23 case '[':
24 // Array - find matching closing bracket
25 end, err = findMatchingBrace(b, '[', ']')
26 case '"':
27 // String - find closing quote (handling escapes)
28 end, err = findClosingQuote(b)
29 case 't':
30 // true
31 if len(b) >= 4 && string(b[:4]) == "true" {
32 end = 4
33 } else {
34 err = errorf.E("invalid JSON value starting with 't'")
35 }
36 case 'f':
37 // false
38 if len(b) >= 5 && string(b[:5]) == "false" {
39 end = 5
40 } else {
41 err = errorf.E("invalid JSON value starting with 'f'")
42 }
43 case 'n':
44 // null
45 if len(b) >= 4 && string(b[:4]) == "null" {
46 end = 4
47 } else {
48 err = errorf.E("invalid JSON value starting with 'n'")
49 }
50 case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
51 // Number - scan until we hit a non-number character
52 end = scanNumber(b)
53 default:
54 err = errorf.E("invalid JSON value starting with '%c'", b[0])
55 }
56
57 if err != nil {
58 return
59 }
60
61 val = b[start:end]
62 r = b[end:]
63 return
64 }
65
66 // findMatchingBrace finds the index after the closing brace/bracket that matches the opening one.
67 // It handles nested structures and strings.
68 func findMatchingBrace(b []byte, open, close byte) (end int, err error) {
69 if len(b) == 0 || b[0] != open {
70 err = errorf.E("expected '%c'", open)
71 return
72 }
73
74 depth := 0
75 inString := false
76 escaped := false
77
78 for i := 0; i < len(b); i++ {
79 c := b[i]
80
81 if escaped {
82 escaped = false
83 continue
84 }
85
86 if c == '\\' && inString {
87 escaped = true
88 continue
89 }
90
91 if c == '"' {
92 inString = !inString
93 continue
94 }
95
96 if inString {
97 continue
98 }
99
100 if c == open {
101 depth++
102 } else if c == close {
103 depth--
104 if depth == 0 {
105 end = i + 1
106 return
107 }
108 }
109 }
110
111 err = errorf.E("unmatched '%c'", open)
112 return
113 }
114
115 // findClosingQuote finds the index after the closing quote of a JSON string.
116 // Handles escape sequences.
117 func findClosingQuote(b []byte) (end int, err error) {
118 if len(b) == 0 || b[0] != '"' {
119 err = errorf.E("expected '\"'")
120 return
121 }
122
123 escaped := false
124 for i := 1; i < len(b); i++ {
125 c := b[i]
126
127 if escaped {
128 escaped = false
129 continue
130 }
131
132 if c == '\\' {
133 escaped = true
134 continue
135 }
136
137 if c == '"' {
138 end = i + 1
139 return
140 }
141 }
142
143 err = errorf.E("unclosed string")
144 return
145 }
146
147 // scanNumber scans a JSON number and returns the index after it.
148 // Handles integers, decimals, and scientific notation.
149 func scanNumber(b []byte) (end int) {
150 for i := 0; i < len(b); i++ {
151 c := b[i]
152 // Number characters: digits, minus, plus, dot, e, E
153 if (c >= '0' && c <= '9') || c == '-' || c == '+' || c == '.' || c == 'e' || c == 'E' {
154 continue
155 }
156 end = i
157 return
158 }
159 end = len(b)
160 return
161 }
162