diff --git a/c14n/README.md b/c14n/README.md index 794bf8d3..6eff4a6d 100644 --- a/c14n/README.md +++ b/c14n/README.md @@ -12,9 +12,9 @@ One of the objectives of GoBL is to create a document that could potentially be This `c14n` package, inspired by the works of others, thus aims to define a simple standardized approach to canonical JSON that could potentially be implemented easily in other languages. More than just a definition, the code here is a reference implementation from which libraries can be made in languages other than Go. -## GoBL JSON C14n +## GOBL JSON C14n -GoBL considers the following JSON values as explicit types: +GOBL considers the following JSON values as explicit types: - a string - a number, which extends the JSON spec and is split into: @@ -56,7 +56,7 @@ JSON in canonical form: 2. using six-character `\u00XX` uppercase hexadecimal escape sequences for control characters that require escaping but lack a two-character sequence described previously, and 3. reject any string containing invalid encoding. -The GoBL JSON c14n package has been designed to operate using any raw JSON source and uses the Go [`encoding/json`](https://golang.org/pkg/encoding/json/) library's streaming methods to parse and recreate a document in memory. A simplified object model is used to map JSON structures ready to be converted into canonical JSON. +The GOBL JSON c14n package has been designed to operate using any raw JSON source and uses the Go [`encoding/json`](https://golang.org/pkg/encoding/json/) library's streaming methods to parse and recreate a document in memory. A simplified object model is used to map JSON structures ready to be converted into canonical JSON. ## Usage Example diff --git a/c14n/c14n.go b/c14n/c14n.go index e72046da..d48dc9a1 100644 --- a/c14n/c14n.go +++ b/c14n/c14n.go @@ -2,8 +2,10 @@ package c14n import ( + "bytes" "encoding/json" "errors" + "fmt" "io" ) @@ -23,6 +25,17 @@ func UnmarshalJSON(src io.Reader) (Canonicalable, error) { return res, nil } +// MarshalJSON takes any Go object that can be serialized into JSON and generates +// the canonical JSON representation of that object. +func MarshalJSON(src any) ([]byte, error) { + data := new(bytes.Buffer) + enc := json.NewEncoder(data) + if err := enc.Encode(src); err != nil { + return nil, fmt.Errorf("encoding: %w", err) + } + return CanonicalJSON(data) +} + // CanonicalJSON performs the unmarshal and marshal commands in one go. func CanonicalJSON(src io.Reader) ([]byte, error) { obj, err := UnmarshalJSON(src) diff --git a/c14n/c14n_test.go b/c14n/c14n_test.go index 6aad6394..e262d8b6 100644 --- a/c14n/c14n_test.go +++ b/c14n/c14n_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/invopop/gobl/c14n" + "github.com/stretchr/testify/assert" ) func TestJSONToArray(t *testing.T) { @@ -69,3 +70,19 @@ func TestJSONToArray(t *testing.T) { t.Errorf("unexpected sum, please check marshaled data, got: %v", s) } } + +func TestMarshalJSON(t *testing.T) { + obj := struct { + Title string `json:"title"` + Idx int64 `json:"idx"` + Body string `json:"body,omitempty"` + }{ + Title: "test", + Idx: 1, + Body: "Test body to play around with", + } + d, err := c14n.MarshalJSON(obj) + assert.NoError(t, err) + out := `{"body":"Test body to play around with","idx":1,"title":"test"}` + assert.Contains(t, string(d), out) +}