diff --git a/codec/dagjson/nongreedy_test.go b/codec/dagjson/nongreedy_test.go new file mode 100644 index 00000000..b6b03a4f --- /dev/null +++ b/codec/dagjson/nongreedy_test.go @@ -0,0 +1,42 @@ +package dagjson + +import ( + "bytes" + "testing" + + "github.com/ipld/go-ipld-prime/datamodel" + "github.com/ipld/go-ipld-prime/node/basicnode" +) + +func TestNonGreedy(t *testing.T) { + buf := bytes.NewBufferString(`{"a": 1}{"b": 2}`) + opts := DecodeOptions{ + ParseLinks: false, + ParseBytes: false, + DontParseBeyondEnd: true, + } + nb1 := basicnode.Prototype.Map.NewBuilder() + err := opts.Decode(nb1, buf) + if err != nil { + t.Fatalf("first decode (%v)", err) + } + n1 := nb1.Build() + if n1.Kind() != datamodel.Kind_Map { + t.Errorf("expecting a map") + } + if _, err := n1.LookupByString("a"); err != nil { + t.Fatalf("missing fist key") + } + nb2 := basicnode.Prototype.Map.NewBuilder() + err = opts.Decode(nb2, buf) + if err != nil { + t.Fatalf("second decode (%v)", err) + } + n2 := nb2.Build() + if n2.Kind() != datamodel.Kind_Map { + t.Errorf("expecting a map") + } + if _, err := n2.LookupByString("b"); err != nil { + t.Fatalf("missing second key") + } +} diff --git a/codec/dagjson/unmarshal.go b/codec/dagjson/unmarshal.go index 637aa3a7..b51f4dac 100644 --- a/codec/dagjson/unmarshal.go +++ b/codec/dagjson/unmarshal.go @@ -32,6 +32,13 @@ type DecodeOptions struct { // If true, parse DAG-JSON `{"/":{"bytes":"base64 bytes..."}}` as a Bytes kind // node rather than nested plain maps ParseBytes bool + + // If true, the decoder stops reading from the stream at the end of the JSON structure. + // i.e. it does not slurp remaining whitespaces and EOF. + // As per standard IPLD behavior, the parser considers the entire block to be + // part of the JSON structure and will error if there is extraneous + // non-whitespace data. + DontParseBeyondEnd bool } // Decode deserializes data from the given io.Reader and feeds it into the given datamodel.NodeAssembler. @@ -43,6 +50,9 @@ func (cfg DecodeOptions) Decode(na datamodel.NodeAssembler, r io.Reader) error { if err != nil { return err } + if cfg.DontParseBeyondEnd { + return nil + } // Slurp any remaining whitespace. // This behavior may be due for review. // (This is relevant if our reader is tee'ing bytes to a hasher, and