Files
nostr/encoders/filter/skip.go

162 lines
3.1 KiB
Go

package filter
import (
"lol.mleku.dev/errorf"
)
// skipJSONValue skips over an arbitrary JSON value and returns the raw bytes and remainder.
// It handles: objects {}, arrays [], strings "", numbers, true, false, null.
// The input `b` should start at the first character of the value (after the colon in "key":value).
func skipJSONValue(b []byte) (val []byte, r []byte, err error) {
if len(b) == 0 {
err = errorf.E("empty input")
return
}
start := 0
end := 0
switch b[0] {
case '{':
// Object - find matching closing brace
end, err = findMatchingBrace(b, '{', '}')
case '[':
// Array - find matching closing bracket
end, err = findMatchingBrace(b, '[', ']')
case '"':
// String - find closing quote (handling escapes)
end, err = findClosingQuote(b)
case 't':
// true
if len(b) >= 4 && string(b[:4]) == "true" {
end = 4
} else {
err = errorf.E("invalid JSON value starting with 't'")
}
case 'f':
// false
if len(b) >= 5 && string(b[:5]) == "false" {
end = 5
} else {
err = errorf.E("invalid JSON value starting with 'f'")
}
case 'n':
// null
if len(b) >= 4 && string(b[:4]) == "null" {
end = 4
} else {
err = errorf.E("invalid JSON value starting with 'n'")
}
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
// Number - scan until we hit a non-number character
end = scanNumber(b)
default:
err = errorf.E("invalid JSON value starting with '%c'", b[0])
}
if err != nil {
return
}
val = b[start:end]
r = b[end:]
return
}
// findMatchingBrace finds the index after the closing brace/bracket that matches the opening one.
// It handles nested structures and strings.
func findMatchingBrace(b []byte, open, close byte) (end int, err error) {
if len(b) == 0 || b[0] != open {
err = errorf.E("expected '%c'", open)
return
}
depth := 0
inString := false
escaped := false
for i := 0; i < len(b); i++ {
c := b[i]
if escaped {
escaped = false
continue
}
if c == '\\' && inString {
escaped = true
continue
}
if c == '"' {
inString = !inString
continue
}
if inString {
continue
}
if c == open {
depth++
} else if c == close {
depth--
if depth == 0 {
end = i + 1
return
}
}
}
err = errorf.E("unmatched '%c'", open)
return
}
// findClosingQuote finds the index after the closing quote of a JSON string.
// Handles escape sequences.
func findClosingQuote(b []byte) (end int, err error) {
if len(b) == 0 || b[0] != '"' {
err = errorf.E("expected '\"'")
return
}
escaped := false
for i := 1; i < len(b); i++ {
c := b[i]
if escaped {
escaped = false
continue
}
if c == '\\' {
escaped = true
continue
}
if c == '"' {
end = i + 1
return
}
}
err = errorf.E("unclosed string")
return
}
// scanNumber scans a JSON number and returns the index after it.
// Handles integers, decimals, and scientific notation.
func scanNumber(b []byte) (end int) {
for i := 0; i < len(b); i++ {
c := b[i]
// Number characters: digits, minus, plus, dot, e, E
if (c >= '0' && c <= '9') || c == '-' || c == '+' || c == '.' || c == 'e' || c == 'E' {
continue
}
end = i
return
}
end = len(b)
return
}