Skip to content

Commit 83e4f58

Browse files
committed
Add optional encoding support for json.Marshaler.
If the user provides a JSON-to-CBOR transcode function, a value whose type implements json.Marshaler and not cbor.Marshaler will be encoded by first calling its MarshalJSON method, then transcoding the result to CBOR. Signed-off-by: Ben Luddy <bluddy@redhat.com>
1 parent 3e7e036 commit 83e4f58

4 files changed

Lines changed: 400 additions & 32 deletions

File tree

common.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package cbor
55

66
import (
77
"fmt"
8+
"io"
89
"strconv"
910
)
1011

@@ -180,3 +181,11 @@ func validBuiltinTag(tagNum uint64, contentHead byte) error {
180181

181182
return nil
182183
}
184+
185+
// Transcoder is a scheme for transcoding a single CBOR encoded data item to or from a different
186+
// data format.
187+
type Transcoder interface {
188+
// Transcode reads the data item in its source format from a Reader and writes a
189+
// corresponding representation in its destination format to a Writer.
190+
Transcode(dst io.Writer, src io.Reader) error
191+
}

encode.go

Lines changed: 107 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,20 @@ func (e *MarshalerError) Unwrap() error {
132132
return e.err
133133
}
134134

135+
type TranscodeError struct {
136+
err error
137+
rtype reflect.Type
138+
source, target string
139+
}
140+
141+
func (e TranscodeError) Error() string {
142+
return "cbor: error transcoding " + e.rtype.String() + " from " + e.source + " to " + e.target + ": " + e.err.Error()
143+
}
144+
145+
func (e TranscodeError) Unwrap() error {
146+
return e.err
147+
}
148+
135149
// UnsupportedTypeError is returned by Marshal when attempting to encode value
136150
// of an unsupported type.
137151
type UnsupportedTypeError struct {
@@ -567,6 +581,11 @@ type EncOptions struct {
567581

568582
// BinaryMarshaler specifies how to encode types that implement encoding.BinaryMarshaler.
569583
BinaryMarshaler BinaryMarshalerMode
584+
585+
// JSONMarshalerTranscoder sets the transcoding scheme used to marshal types that implement
586+
// json.Marshaler but do not also implement cbor.Marshaler. If nil, encoding behavior is not
587+
// influenced by whether or not a type implements json.Marshaler.
588+
JSONMarshalerTranscoder Transcoder
570589
}
571590

572591
// CanonicalEncOptions returns EncOptions for "Canonical CBOR" encoding,
@@ -796,6 +815,7 @@ func (opts EncOptions) encMode() (*encMode, error) { //nolint:gocritic // ignore
796815
byteSliceLaterEncodingTag: byteSliceLaterEncodingTag,
797816
byteArray: opts.ByteArray,
798817
binaryMarshaler: opts.BinaryMarshaler,
818+
jsonMarshalerTranscoder: opts.JSONMarshalerTranscoder,
799819
}
800820
return &em, nil
801821
}
@@ -841,6 +861,7 @@ type encMode struct {
841861
byteSliceLaterEncodingTag uint64
842862
byteArray ByteArrayMode
843863
binaryMarshaler BinaryMarshalerMode
864+
jsonMarshalerTranscoder Transcoder
844865
}
845866

846867
var defaultEncMode, _ = EncOptions{}.encMode()
@@ -917,22 +938,23 @@ func getMarshalerDecMode(indefLength IndefLengthMode, tagsMd TagsMode) *decMode
917938
// EncOptions returns user specified options used to create this EncMode.
918939
func (em *encMode) EncOptions() EncOptions {
919940
return EncOptions{
920-
Sort: em.sort,
921-
ShortestFloat: em.shortestFloat,
922-
NaNConvert: em.nanConvert,
923-
InfConvert: em.infConvert,
924-
BigIntConvert: em.bigIntConvert,
925-
Time: em.time,
926-
TimeTag: em.timeTag,
927-
IndefLength: em.indefLength,
928-
NilContainers: em.nilContainers,
929-
TagsMd: em.tagsMd,
930-
OmitEmpty: em.omitEmpty,
931-
String: em.stringType,
932-
FieldName: em.fieldName,
933-
ByteSliceLaterFormat: em.byteSliceLaterFormat,
934-
ByteArray: em.byteArray,
935-
BinaryMarshaler: em.binaryMarshaler,
941+
Sort: em.sort,
942+
ShortestFloat: em.shortestFloat,
943+
NaNConvert: em.nanConvert,
944+
InfConvert: em.infConvert,
945+
BigIntConvert: em.bigIntConvert,
946+
Time: em.time,
947+
TimeTag: em.timeTag,
948+
IndefLength: em.indefLength,
949+
NilContainers: em.nilContainers,
950+
TagsMd: em.tagsMd,
951+
OmitEmpty: em.omitEmpty,
952+
String: em.stringType,
953+
FieldName: em.fieldName,
954+
ByteSliceLaterFormat: em.byteSliceLaterFormat,
955+
ByteArray: em.byteArray,
956+
BinaryMarshaler: em.binaryMarshaler,
957+
JSONMarshalerTranscoder: em.jsonMarshalerTranscoder,
936958
}
937959
}
938960

@@ -1704,6 +1726,59 @@ func (bme binaryMarshalerEncoder) isEmpty(em *encMode, v reflect.Value) (bool, e
17041726
return len(data) == 0, nil
17051727
}
17061728

1729+
type jsonMarshalerEncoder struct {
1730+
alternateEncode encodeFunc
1731+
alternateIsEmpty isEmptyFunc
1732+
}
1733+
1734+
func (jme jsonMarshalerEncoder) encode(e *bytes.Buffer, em *encMode, v reflect.Value) error {
1735+
if em.jsonMarshalerTranscoder == nil {
1736+
return jme.alternateEncode(e, em, v)
1737+
}
1738+
1739+
vt := v.Type()
1740+
m, ok := v.Interface().(jsonMarshaler)
1741+
if !ok {
1742+
pv := reflect.New(vt)
1743+
pv.Elem().Set(v)
1744+
m = pv.Interface().(jsonMarshaler)
1745+
}
1746+
1747+
json, err := m.MarshalJSON()
1748+
if err != nil {
1749+
return err
1750+
}
1751+
1752+
offset := e.Len()
1753+
1754+
if b := em.encTagBytes(vt); b != nil {
1755+
e.Write(b)
1756+
}
1757+
1758+
if err := em.jsonMarshalerTranscoder.Transcode(e, bytes.NewReader(json)); err != nil {
1759+
return &TranscodeError{err: err, rtype: vt, source: "json", target: "cbor"}
1760+
}
1761+
1762+
// Validate that the transcode function has written exactly one well-formed data item.
1763+
d := decoder{data: e.Bytes()[offset:], dm: getMarshalerDecMode(em.indefLength, em.tagsMd)}
1764+
if err := d.wellformed(false, true); err != nil {
1765+
e.Truncate(offset)
1766+
return &TranscodeError{err: err, rtype: vt, source: "json", target: "cbor"}
1767+
}
1768+
1769+
return nil
1770+
}
1771+
1772+
func (jme jsonMarshalerEncoder) isEmpty(em *encMode, v reflect.Value) (bool, error) {
1773+
if em.jsonMarshalerTranscoder == nil {
1774+
return jme.alternateIsEmpty(em, v)
1775+
}
1776+
1777+
// As with types implementing cbor.Marshaler, transcoded json.Marshaler values always encode
1778+
// as exactly one complete CBOR data item.
1779+
return false, nil
1780+
}
1781+
17071782
func encodeMarshalerType(e *bytes.Buffer, em *encMode, v reflect.Value) error {
17081783
if em.tagsMd == TagsForbidden && v.Type() == typeRawTag {
17091784
return errors.New("cbor: cannot encode cbor.RawTag when TagsMd is TagsForbidden")
@@ -1807,9 +1882,12 @@ func encodeHead(e *bytes.Buffer, t byte, n uint64) int {
18071882
return headSize
18081883
}
18091884

1885+
type jsonMarshaler interface{ MarshalJSON() ([]byte, error) }
1886+
18101887
var (
18111888
typeMarshaler = reflect.TypeOf((*Marshaler)(nil)).Elem()
18121889
typeBinaryMarshaler = reflect.TypeOf((*encoding.BinaryMarshaler)(nil)).Elem()
1890+
typeJSONMarshaler = reflect.TypeOf((*jsonMarshaler)(nil)).Elem()
18131891
typeRawMessage = reflect.TypeOf(RawMessage(nil))
18141892
typeByteString = reflect.TypeOf(ByteString(""))
18151893
)
@@ -1852,6 +1930,19 @@ func getEncodeFuncInternal(t reflect.Type) (ef encodeFunc, ief isEmptyFunc, izf
18521930
ief = bme.isEmpty
18531931
}()
18541932
}
1933+
if reflect.PointerTo(t).Implements(typeJSONMarshaler) {
1934+
defer func() {
1935+
// capture encoding method used for modes that don't support transcoding
1936+
// from types that implement json.Marshaler.
1937+
jme := jsonMarshalerEncoder{
1938+
alternateEncode: ef,
1939+
alternateIsEmpty: ief,
1940+
}
1941+
ef = jme.encode
1942+
ief = jme.isEmpty
1943+
}()
1944+
}
1945+
18551946
switch k {
18561947
case reflect.Bool:
18571948
return encodeBool, isEmptyBool, getIsZeroFunc(t)

0 commit comments

Comments
 (0)