romshark / jscan

High performance JSON iterator & validator for Go
BSD 3-Clause "New" or "Revised" License
88 stars 7 forks source link

Add ScanOne #11

Closed romshark closed 1 year ago

romshark commented 1 year ago

Sometimes it's necessary to read multiple JSON values from a stream, but the following program will produce: ERR: error at index 7 ('{'): unexpected token

package main

import (
    "fmt"
    "github.com/romshark/jscan"
)

func main() {
    j := `{"x":1}{"x":2}`
    err := jscan.Scan(jscan.Options{}, j, func(i *jscan.Iterator) (err bool) {
        return false // No-op.
    })
    fmt.Printf("ERR: %s\n", err)
}

Therefore I propose to extend the API by adding a new function:

// ScanOne is similar to Scan but will scan 1 valid JSON value from s and return s[len(value):]
func ScanOne(o Options, s string, fn func(*Iterator) (err bool)) (string, Error)

// ScanBytesOne is similar to ScanBytes but will scan 1 valid JSON value from s and return s[len(value):]
func ScanBytesOne(o Options, s []byte, fn func(*IteratorBytes) (err bool)) ([]byte, Error)
raff commented 1 year ago

A different way to approach this, that would cover more use cases, would be to make all the iterator callbacks return an error (now some return a "error" boolean, some don't have a return value) . If error then the scan would stop. And you could have some specific errors (Found or Stop) that would just indicate that the user found the value they were looking for and wanted to stop.

romshark commented 1 year ago

@raff if I'm correct you propose allowing callbacks to return more sophisticated error values. The callback function was supposed to manage any external error reporting on its own:

depthLimitExceeded := false
err := jscan.Scan(jscan.Options{
    CachePath:  true,
    EscapePath: true,
}, j, func(i *jscan.Iterator) (err bool) {
    if i.Level > MyDepthLimit {
        depthLimitExceeded = true
        return true
    }
    return false
})
if err.IsErr() {
    // There was a syntax error
}
if depthLimitExceeded {
    // There was a user-defined error
}
romshark commented 1 year ago

jscan v2 will provide:

func ValidateOne[S ~string | ~[]byte](s S) (trailing S, err Error[S])
func ScanOne[S ~string | ~[]byte](s S, fn func(*Iterator[S]) (err bool)) (trailing S, err Error[S])