From 92f7b97a36c5dd79a5077bbbd95a01f6de2fea2f Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Sat, 26 Apr 2025 18:34:15 -0500 Subject: [PATCH] Add example of Embedded JSON Tag for CBOR For more info, see https://github.com/toravir/CBOR-Tag-Specs/blob/master/embeddedJSON.md --- encode.go | 2 +- example_embedded_json_tag_for_cbor_test.go | 130 +++++++++++++++++++++ 2 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 example_embedded_json_tag_for_cbor_test.go diff --git a/encode.go b/encode.go index 19471b5..c3daf73 100644 --- a/encode.go +++ b/encode.go @@ -314,7 +314,7 @@ const ( // TimeUnixMicro causes time.Time to encode to a CBOR time (tag 1) with a floating point content // representing seconds elapsed (with up to 1-microsecond precision) since UNIX Epoch UTC. // NOTE: The floating point content is encoded to the shortest floating-point encoding that preserves - // the 64-bit floating point content. I.e., the floating point encoding can be IEEE 764: + // the 64-bit floating point value. I.e., the floating point encoding can be IEEE 764: // binary64, binary32, or binary16 depending on the content's value. TimeUnixMicro diff --git a/example_embedded_json_tag_for_cbor_test.go b/example_embedded_json_tag_for_cbor_test.go new file mode 100644 index 0000000..29ee431 --- /dev/null +++ b/example_embedded_json_tag_for_cbor_test.go @@ -0,0 +1,130 @@ +// Copyright (c) Faye Amacker. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +package cbor_test + +// fxamacker/cbor allows user apps to use almost any current or future +// CBOR tag number by implementing cbor.Marshaler and cbor.Unmarshaler +// interfaces. Essentially, MarshalCBOR and UnmarshalCBOR functions that +// are implemented by user apps will automatically be called by this +// CBOR codec's Marshal, Unmarshal, etc. +// +// This example shows how to encode and decode a tagged CBOR data item with +// tag number 262 and the tag content is a JSON object "embedded" as a +// CBOR byte string (major type 2). +// +// NOTE: RFC 8949 does not mention tag number 262. IANA assigned +// CBOR tag number 262 as "Embedded JSON Object" specified by the +// document Embedded JSON Tag for CBOR: +// +// "Tag 262 can be applied to a byte string (major type 2) to indicate +// that the byte string is a JSON Object. The length of the byte string +// indicates the content." +// +// For more info, see Embedded JSON Tag for CBOR at: +// https://github.com/toravir/CBOR-Tag-Specs/blob/master/embeddedJSON.md + +import ( + "bytes" + "encoding/json" + "fmt" + + "github.com/fxamacker/cbor/v2" +) + +// cborTagNumForEmbeddedJSON is the CBOR tag number 262. +const cborTagNumForEmbeddedJSON = 262 + +// EmbeddedJSON represents a Go value to be encoded as a tagged CBOR data item +// with tag number 262 and the tag content is a JSON object "embedded" as a +// CBOR byte string (major type 2). +type EmbeddedJSON struct { + any +} + +func NewEmbeddedJSON(val any) EmbeddedJSON { + return EmbeddedJSON{val} +} + +// MarshalCBOR encodes EmbeddedJSON to a tagged CBOR data item with the +// tag number 262 and the tag content is a JSON object that is +// "embedded" as a CBOR byte string. +func (v EmbeddedJSON) MarshalCBOR() ([]byte, error) { + // Encode v to JSON object. + data, err := json.Marshal(v) + if err != nil { + return nil, err + } + + // Create cbor.Tag representing a tagged CBOR data item. + tag := cbor.Tag{ + Number: cborTagNumForEmbeddedJSON, + Content: data, + } + + // Marshal to a tagged CBOR data item. + return cbor.Marshal(tag) +} + +// UnmarshalCBOR decodes a tagged CBOR data item to EmbeddedJSON. +// The byte slice provided to this function must contain a single +// tagged CBOR data item with the tag number 262 and tag content +// must be a JSON object "embedded" as a CBOR byte string. +func (v *EmbeddedJSON) UnmarshalCBOR(b []byte) error { + // Unmarshal tagged CBOR data item. + var tag cbor.Tag + if err := cbor.Unmarshal(b, &tag); err != nil { + return err + } + + // Check tag number. + if tag.Number != cborTagNumForEmbeddedJSON { + return fmt.Errorf("got tag number %d, expect tag number %d", tag.Number, cborTagNumForEmbeddedJSON) + } + + // Check tag content. + jsonData, isByteString := tag.Content.([]byte) + if !isByteString { + return fmt.Errorf("got tag content type %T, expect tag content []byte", tag.Content) + } + + // Unmarshal JSON object. + return json.Unmarshal(jsonData, v) +} + +// MarshalJSON encodes EmbeddedJSON to a JSON object. +func (v EmbeddedJSON) MarshalJSON() ([]byte, error) { + return json.Marshal(v.any) +} + +// UnmarshalJSON decodes a JSON object. +func (v *EmbeddedJSON) UnmarshalJSON(b []byte) error { + dec := json.NewDecoder(bytes.NewReader(b)) + dec.UseNumber() + return dec.Decode(&v.any) +} + +func Example_embeddedJSONTagForCBOR() { + value := NewEmbeddedJSON(map[string]any{ + "name": "gopher", + "id": json.Number("42"), + }) + + data, err := cbor.Marshal(value) + if err != nil { + panic(err) + } + + fmt.Printf("cbor: %x\n", data) + + var v EmbeddedJSON + err = cbor.Unmarshal(data, &v) + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", v.any) + for k, v := range v.any.(map[string]any) { + fmt.Printf(" %s: %v (%T)\n", k, v, v) + } +}