Commit 333be0f7 authored by Rod Vagg's avatar Rod Vagg

Add {Unm,M}arshalOptions for explicit mode switching for json vs dagjson

parent 79050021
...@@ -16,7 +16,21 @@ import ( ...@@ -16,7 +16,21 @@ import (
// except for the `case ipld.Kind_Link` block, // except for the `case ipld.Kind_Link` block,
// which is dag-json's special sauce for schemafree links. // which is dag-json's special sauce for schemafree links.
func Marshal(n ipld.Node, sink shared.TokenSink, allowLinks bool) error { type MarshalOptions struct {
// If true, will encode nodes with a Link kind using the DAG-JSON
// `{"/":"cid string"}` form.
EncodeLinks bool
// If true, will encode nodes with a Bytes kind using the DAG-JSON
// `{"/":{"bytes":"base64 bytes..."}}` form.
EncodeBytes bool
// If true, will sort map keys prior to encoding using plain bytewise
// comparison.
SortMapKeys bool
}
func Marshal(n ipld.Node, sink shared.TokenSink, options MarshalOptions) error {
var tk tok.Token var tk tok.Token
switch n.Kind() { switch n.Kind() {
case ipld.Kind_Invalid: case ipld.Kind_Invalid:
...@@ -32,33 +46,54 @@ func Marshal(n ipld.Node, sink shared.TokenSink, allowLinks bool) error { ...@@ -32,33 +46,54 @@ func Marshal(n ipld.Node, sink shared.TokenSink, allowLinks bool) error {
if _, err := sink.Step(&tk); err != nil { if _, err := sink.Step(&tk); err != nil {
return err return err
} }
// Collect map entries, then sort by key if options.SortMapKeys {
type entry struct { // Collect map entries, then sort by key
key string type entry struct {
value ipld.Node key string
} value ipld.Node
entries := []entry{} }
for itr := n.MapIterator(); !itr.Done(); { entries := []entry{}
k, v, err := itr.Next() for itr := n.MapIterator(); !itr.Done(); {
if err != nil { k, v, err := itr.Next()
return err if err != nil {
} return err
keyStr, err := k.AsString() }
if err != nil { keyStr, err := k.AsString()
return err if err != nil {
} return err
entries = append(entries, entry{keyStr, v}) }
} entries = append(entries, entry{keyStr, v})
sort.Slice(entries, func(i, j int) bool { return entries[i].key < entries[j].key }) }
// Emit map contents (and recurse). sort.Slice(entries, func(i, j int) bool { return entries[i].key < entries[j].key })
for _, e := range entries { // Emit map contents (and recurse).
tk.Type = tok.TString for _, e := range entries {
tk.Str = e.key tk.Type = tok.TString
if _, err := sink.Step(&tk); err != nil { tk.Str = e.key
return err if _, err := sink.Step(&tk); err != nil {
return err
}
if err := Marshal(e.value, sink, options); err != nil {
return err
}
} }
if err := Marshal(e.value, sink, allowLinks); err != nil { } else {
return err // Don't sort map, emit map contents (and recurse).
for itr := n.MapIterator(); !itr.Done(); {
k, v, err := itr.Next()
if err != nil {
return err
}
tk.Type = tok.TString
tk.Str, err = k.AsString()
if err != nil {
return err
}
if _, err := sink.Step(&tk); err != nil {
return err
}
if err := Marshal(v, sink, options); err != nil {
return err
}
} }
} }
// Emit map close. // Emit map close.
...@@ -79,7 +114,7 @@ func Marshal(n ipld.Node, sink shared.TokenSink, allowLinks bool) error { ...@@ -79,7 +114,7 @@ func Marshal(n ipld.Node, sink shared.TokenSink, allowLinks bool) error {
if err != nil { if err != nil {
return err return err
} }
if err := Marshal(v, sink, allowLinks); err != nil { if err := Marshal(v, sink, options); err != nil {
return err return err
} }
} }
...@@ -128,7 +163,7 @@ func Marshal(n ipld.Node, sink shared.TokenSink, allowLinks bool) error { ...@@ -128,7 +163,7 @@ func Marshal(n ipld.Node, sink shared.TokenSink, allowLinks bool) error {
if err != nil { if err != nil {
return err return err
} }
if allowLinks { if options.EncodeBytes {
// Precisely seven tokens to emit: // Precisely seven tokens to emit:
tk.Type = tok.TMapOpen tk.Type = tok.TMapOpen
tk.Length = 1 tk.Length = 1
...@@ -170,7 +205,7 @@ func Marshal(n ipld.Node, sink shared.TokenSink, allowLinks bool) error { ...@@ -170,7 +205,7 @@ func Marshal(n ipld.Node, sink shared.TokenSink, allowLinks bool) error {
return err return err
} }
case ipld.Kind_Link: case ipld.Kind_Link:
if !allowLinks { if !options.EncodeLinks {
return fmt.Errorf("cannot Marshal ipld links to JSON") return fmt.Errorf("cannot Marshal ipld links to JSON")
} }
v, err := n.AsLink() v, err := n.AsLink()
......
...@@ -21,7 +21,10 @@ func init() { ...@@ -21,7 +21,10 @@ func init() {
} }
func Decode(na ipld.NodeAssembler, r io.Reader) error { func Decode(na ipld.NodeAssembler, r io.Reader) error {
err := Unmarshal(na, json.NewDecoder(r), true) err := Unmarshal(na, json.NewDecoder(r), UnmarshalOptions{
ParseLinks: true,
ParseBytes: true,
})
if err != nil { if err != nil {
return err return err
} }
...@@ -53,5 +56,10 @@ func Encode(n ipld.Node, w io.Writer) error { ...@@ -53,5 +56,10 @@ func Encode(n ipld.Node, w io.Writer) error {
// Shell out directly to generic inspection path. // Shell out directly to generic inspection path.
// (There's not really any fastpaths of note for json.) // (There's not really any fastpaths of note for json.)
// Write another function if you need to tune encoding options about whitespace. // Write another function if you need to tune encoding options about whitespace.
return Marshal(n, json.NewEncoder(w, json.EncodeOptions{}), true) return Marshal(n, json.NewEncoder(w, json.EncodeOptions{}),
MarshalOptions{
EncodeLinks: true,
EncodeBytes: true,
SortMapKeys: true,
})
} }
...@@ -20,9 +20,19 @@ import ( ...@@ -20,9 +20,19 @@ import (
// several steps of handling maps, because it necessitates peeking several // several steps of handling maps, because it necessitates peeking several
// tokens before deciding what kind of value to create). // tokens before deciding what kind of value to create).
func Unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource, parseLinks bool) error { type UnmarshalOptions struct {
// If true, parse DAG-CBOR `{"/":"cid string"}` as a Link kind node rather
// than a plain map
ParseLinks bool
// If true, parse DAG-CBOR `{"/":{"bytes":"base64 bytes..."}}` as a Bytes kind
// node rather than nested plain maps
ParseBytes bool
}
func Unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource, options UnmarshalOptions) error {
var st unmarshalState var st unmarshalState
st.parseLinks = parseLinks st.options = options
done, err := tokSrc.Step(&st.tk[0]) done, err := tokSrc.Step(&st.tk[0])
if err != nil { if err != nil {
return err return err
...@@ -34,9 +44,9 @@ func Unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource, parseLinks bool ...@@ -34,9 +44,9 @@ func Unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource, parseLinks bool
} }
type unmarshalState struct { type unmarshalState struct {
tk [7]tok.Token // mostly, only 0'th is used... but [1:7] are used during lookahead for links. tk [7]tok.Token // mostly, only 0'th is used... but [1:7] are used during lookahead for links.
shift int // how many times to slide something out of tk[1:7] instead of getting a new token. shift int // how many times to slide something out of tk[1:7] instead of getting a new token.
parseLinks bool options UnmarshalOptions
} }
// step leaves a "new" token in tk[0], // step leaves a "new" token in tk[0],
...@@ -229,7 +239,7 @@ func (st *unmarshalState) unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSo ...@@ -229,7 +239,7 @@ func (st *unmarshalState) unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSo
case tok.TMapOpen: case tok.TMapOpen:
// dag-json has special needs: we pump a few tokens ahead to look for dag-json's "link" pattern. // dag-json has special needs: we pump a few tokens ahead to look for dag-json's "link" pattern.
// We can't actually call BeginMap until we're sure it's not gonna turn out to be a link. // We can't actually call BeginMap until we're sure it's not gonna turn out to be a link.
if st.parseLinks { if st.options.ParseLinks {
gotLink, err := st.linkLookahead(na, tokSrc) gotLink, err := st.linkLookahead(na, tokSrc)
if err != nil { // return in error if any token peeks failed or if structure looked like a link but failed to parse as CID. if err != nil { // return in error if any token peeks failed or if structure looked like a link but failed to parse as CID.
return err return err
...@@ -237,7 +247,9 @@ func (st *unmarshalState) unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSo ...@@ -237,7 +247,9 @@ func (st *unmarshalState) unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSo
if gotLink { if gotLink {
return nil return nil
} }
}
if st.options.ParseBytes {
gotBytes, err := st.bytesLookahead(na, tokSrc) gotBytes, err := st.bytesLookahead(na, tokSrc)
if err != nil { if err != nil {
return err return err
......
...@@ -24,7 +24,10 @@ func init() { ...@@ -24,7 +24,10 @@ func init() {
func Decode(na ipld.NodeAssembler, r io.Reader) error { func Decode(na ipld.NodeAssembler, r io.Reader) error {
// Shell out directly to generic builder path. // Shell out directly to generic builder path.
// (There's not really any fastpaths of note for json.) // (There's not really any fastpaths of note for json.)
err := dagjson.Unmarshal(na, rfmtjson.NewDecoder(r), false) err := dagjson.Unmarshal(na, rfmtjson.NewDecoder(r), dagjson.UnmarshalOptions{
ParseLinks: false,
ParseBytes: false,
})
if err != nil { if err != nil {
return err return err
} }
...@@ -59,5 +62,9 @@ func Encode(n ipld.Node, w io.Writer) error { ...@@ -59,5 +62,9 @@ func Encode(n ipld.Node, w io.Writer) error {
return dagjson.Marshal(n, rfmtjson.NewEncoder(w, rfmtjson.EncodeOptions{ return dagjson.Marshal(n, rfmtjson.NewEncoder(w, rfmtjson.EncodeOptions{
Line: []byte{'\n'}, Line: []byte{'\n'},
Indent: []byte{'\t'}, Indent: []byte{'\t'},
}), false) }), dagjson.MarshalOptions{
EncodeLinks: false,
EncodeBytes: false,
SortMapKeys: false,
})
} }
...@@ -37,10 +37,9 @@ import ( ...@@ -37,10 +37,9 @@ import (
"bytes" "bytes"
"io" "io"
"github.com/polydawn/refmt/json"
ipld "github.com/ipld/go-ipld-prime" ipld "github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/codec/dagjson" "github.com/ipld/go-ipld-prime/codec/dagjson"
"github.com/ipld/go-ipld-prime/codec/json"
basicnode "github.com/ipld/go-ipld-prime/node/basic" basicnode "github.com/ipld/go-ipld-prime/node/basic"
) )
...@@ -168,8 +167,8 @@ func (tab *table) Finalize() { ...@@ -168,8 +167,8 @@ func (tab *table) Finalize() {
var buf bytes.Buffer var buf bytes.Buffer
for _, cn := range cols { for _, cn := range cols {
buf.Reset() buf.Reset()
dagjson.Marshal(basicnode.NewString(string(cn)), json.NewEncoder(&buf, json.EncodeOptions{}), false) // FIXME this would be a lot less irritating if we had more plumbing access to the json encoding -- we want to encode exactly one string into a buffer, it literally can't error. json.Encode(basicnode.NewString(string(cn)), &buf) // FIXME this would be a lot less irritating if we had more plumbing access to the json encoding -- we want to encode exactly one string into a buffer, it literally can't error.
tab.keySize[cn] = buf.Len() // FIXME this is ignoring charsets, renderable glyphs, etc at present. tab.keySize[cn] = buf.Len() // FIXME this is ignoring charsets, renderable glyphs, etc at present.
} }
} }
...@@ -297,9 +296,7 @@ func marshal(ctx *state, n ipld.Node, w io.Writer) error { ...@@ -297,9 +296,7 @@ func marshal(ctx *state, n ipld.Node, w io.Writer) error {
// It doesn't colorize or anything else. To replace it with something clever that does, // It doesn't colorize or anything else. To replace it with something clever that does,
// we'll have to tear deeper into the plumbing level of json serializers; will, but later. // we'll have to tear deeper into the plumbing level of json serializers; will, but later.
func marshalPlain(ctx *state, n ipld.Node, w io.Writer) error { func marshalPlain(ctx *state, n ipld.Node, w io.Writer) error {
err := dagjson.Marshal(n, json.NewEncoder(w, json.EncodeOptions{ err := dagjson.Encode(n, w) // never indent here: these values will always end up being emitted mid-line.
// never indent here: these values will always end up being emitted mid-line.
}), true)
if err != nil { if err != nil {
return recordErrorPosition(ctx, err) return recordErrorPosition(ctx, err)
} }
...@@ -470,7 +467,7 @@ func emitKey(ctx *state, k ipld.Node, w io.Writer) error { ...@@ -470,7 +467,7 @@ func emitKey(ctx *state, k ipld.Node, w io.Writer) error {
if ctx.cfg.Color.Enabled { if ctx.cfg.Color.Enabled {
w.Write(ctx.cfg.Color.KeyHighlight) w.Write(ctx.cfg.Color.KeyHighlight)
} }
if err := dagjson.Marshal(k, json.NewEncoder(w, json.EncodeOptions{}), true); err != nil { if err := dagjson.Encode(k, w); err != nil {
return recordErrorPosition(ctx, err) return recordErrorPosition(ctx, err)
} }
if ctx.cfg.Color.Enabled { if ctx.cfg.Color.Enabled {
......
...@@ -210,7 +210,11 @@ func testMarshal(t *testing.T, n ipld.Node, data string) { ...@@ -210,7 +210,11 @@ func testMarshal(t *testing.T, n ipld.Node, data string) {
// We'll marshal with "pretty" linebreaks and indents (and re-format the fixture to the same) for better diffing. // We'll marshal with "pretty" linebreaks and indents (and re-format the fixture to the same) for better diffing.
prettyprint := json.EncodeOptions{Line: []byte{'\n'}, Indent: []byte{'\t'}} prettyprint := json.EncodeOptions{Line: []byte{'\n'}, Indent: []byte{'\t'}}
var buf bytes.Buffer var buf bytes.Buffer
err := dagjson.Marshal(n, json.NewEncoder(&buf, prettyprint), true) err := dagjson.Marshal(n, json.NewEncoder(&buf, prettyprint), dagjson.MarshalOptions{
EncodeLinks: true,
EncodeBytes: true,
SortMapKeys: true,
})
if err != nil { if err != nil {
t.Errorf("marshal failed: %s", err) t.Errorf("marshal failed: %s", err)
} }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment