Skip to content

Commit 6f23503

Browse files
committed
Add optional support for json.Unmarshaler.
Users can provide a function to transcode an encoded CBOR data item to JSON. If provided, then unmarshaling into a value whose type implements json.Unmarshaler, but not cbor.Unmarshaler, will first transcode the input bytes to JSON and then invoke UnmarshalJSON on the destination value. Signed-off-by: Ben Luddy <bluddy@redhat.com>
1 parent 83e4f58 commit 6f23503

4 files changed

Lines changed: 262 additions & 105 deletions

File tree

cache.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const (
3737
specialTypeIface
3838
specialTypeTag
3939
specialTypeTime
40+
specialTypeJSONUnmarshalerIface
4041
)
4142

4243
type typeInfo struct {
@@ -75,6 +76,8 @@ func newTypeInfo(t reflect.Type) *typeInfo {
7576
tInfo.spclType = specialTypeUnexportedUnmarshalerIface
7677
} else if reflect.PointerTo(t).Implements(typeUnmarshaler) {
7778
tInfo.spclType = specialTypeUnmarshalerIface
79+
} else if reflect.PointerTo(t).Implements(typeJSONUnmarshaler) {
80+
tInfo.spclType = specialTypeJSONUnmarshalerIface
7881
}
7982

8083
switch k {

decode.go

Lines changed: 124 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package cbor
55

66
import (
7+
"bytes"
78
"encoding"
89
"encoding/base64"
910
"encoding/binary"
@@ -883,6 +884,11 @@ type DecOptions struct {
883884
// BinaryUnmarshaler specifies how to decode into types that implement
884885
// encoding.BinaryUnmarshaler.
885886
BinaryUnmarshaler BinaryUnmarshalerMode
887+
888+
// JSONUnmarshalerTranscoder sets the transcoding scheme used to unmarshal types that
889+
// implement json.Unmarshaler but do not also implement cbor.Unmarshaler. If nil, decoding
890+
// behavior is not influenced by whether or not a type implements json.Unmarshaler.
891+
JSONUnmarshalerTranscoder Transcoder
886892
}
887893

888894
// DecMode returns DecMode with immutable options and no tags (safe for concurrency).
@@ -1096,32 +1102,33 @@ func (opts DecOptions) decMode() (*decMode, error) { //nolint:gocritic // ignore
10961102
}
10971103

10981104
dm := decMode{
1099-
dupMapKey: opts.DupMapKey,
1100-
timeTag: opts.TimeTag,
1101-
maxNestedLevels: opts.MaxNestedLevels,
1102-
maxArrayElements: opts.MaxArrayElements,
1103-
maxMapPairs: opts.MaxMapPairs,
1104-
indefLength: opts.IndefLength,
1105-
tagsMd: opts.TagsMd,
1106-
intDec: opts.IntDec,
1107-
mapKeyByteString: opts.MapKeyByteString,
1108-
extraReturnErrors: opts.ExtraReturnErrors,
1109-
defaultMapType: opts.DefaultMapType,
1110-
utf8: opts.UTF8,
1111-
fieldNameMatching: opts.FieldNameMatching,
1112-
bigIntDec: opts.BigIntDec,
1113-
defaultByteStringType: opts.DefaultByteStringType,
1114-
byteStringToString: opts.ByteStringToString,
1115-
fieldNameByteString: opts.FieldNameByteString,
1116-
unrecognizedTagToAny: opts.UnrecognizedTagToAny,
1117-
timeTagToAny: opts.TimeTagToAny,
1118-
simpleValues: simpleValues,
1119-
nanDec: opts.NaN,
1120-
infDec: opts.Inf,
1121-
byteStringToTime: opts.ByteStringToTime,
1122-
byteStringExpectedFormat: opts.ByteStringExpectedFormat,
1123-
bignumTag: opts.BignumTag,
1124-
binaryUnmarshaler: opts.BinaryUnmarshaler,
1105+
dupMapKey: opts.DupMapKey,
1106+
timeTag: opts.TimeTag,
1107+
maxNestedLevels: opts.MaxNestedLevels,
1108+
maxArrayElements: opts.MaxArrayElements,
1109+
maxMapPairs: opts.MaxMapPairs,
1110+
indefLength: opts.IndefLength,
1111+
tagsMd: opts.TagsMd,
1112+
intDec: opts.IntDec,
1113+
mapKeyByteString: opts.MapKeyByteString,
1114+
extraReturnErrors: opts.ExtraReturnErrors,
1115+
defaultMapType: opts.DefaultMapType,
1116+
utf8: opts.UTF8,
1117+
fieldNameMatching: opts.FieldNameMatching,
1118+
bigIntDec: opts.BigIntDec,
1119+
defaultByteStringType: opts.DefaultByteStringType,
1120+
byteStringToString: opts.ByteStringToString,
1121+
fieldNameByteString: opts.FieldNameByteString,
1122+
unrecognizedTagToAny: opts.UnrecognizedTagToAny,
1123+
timeTagToAny: opts.TimeTagToAny,
1124+
simpleValues: simpleValues,
1125+
nanDec: opts.NaN,
1126+
infDec: opts.Inf,
1127+
byteStringToTime: opts.ByteStringToTime,
1128+
byteStringExpectedFormat: opts.ByteStringExpectedFormat,
1129+
bignumTag: opts.BignumTag,
1130+
binaryUnmarshaler: opts.BinaryUnmarshaler,
1131+
jsonUnmarshalerTranscoder: opts.JSONUnmarshalerTranscoder,
11251132
}
11261133

11271134
return &dm, nil
@@ -1174,33 +1181,34 @@ type DecMode interface {
11741181
}
11751182

11761183
type decMode struct {
1177-
tags tagProvider
1178-
dupMapKey DupMapKeyMode
1179-
timeTag DecTagMode
1180-
maxNestedLevels int
1181-
maxArrayElements int
1182-
maxMapPairs int
1183-
indefLength IndefLengthMode
1184-
tagsMd TagsMode
1185-
intDec IntDecMode
1186-
mapKeyByteString MapKeyByteStringMode
1187-
extraReturnErrors ExtraDecErrorCond
1188-
defaultMapType reflect.Type
1189-
utf8 UTF8Mode
1190-
fieldNameMatching FieldNameMatchingMode
1191-
bigIntDec BigIntDecMode
1192-
defaultByteStringType reflect.Type
1193-
byteStringToString ByteStringToStringMode
1194-
fieldNameByteString FieldNameByteStringMode
1195-
unrecognizedTagToAny UnrecognizedTagToAnyMode
1196-
timeTagToAny TimeTagToAnyMode
1197-
simpleValues *SimpleValueRegistry
1198-
nanDec NaNMode
1199-
infDec InfMode
1200-
byteStringToTime ByteStringToTimeMode
1201-
byteStringExpectedFormat ByteStringExpectedFormatMode
1202-
bignumTag BignumTagMode
1203-
binaryUnmarshaler BinaryUnmarshalerMode
1184+
tags tagProvider
1185+
dupMapKey DupMapKeyMode
1186+
timeTag DecTagMode
1187+
maxNestedLevels int
1188+
maxArrayElements int
1189+
maxMapPairs int
1190+
indefLength IndefLengthMode
1191+
tagsMd TagsMode
1192+
intDec IntDecMode
1193+
mapKeyByteString MapKeyByteStringMode
1194+
extraReturnErrors ExtraDecErrorCond
1195+
defaultMapType reflect.Type
1196+
utf8 UTF8Mode
1197+
fieldNameMatching FieldNameMatchingMode
1198+
bigIntDec BigIntDecMode
1199+
defaultByteStringType reflect.Type
1200+
byteStringToString ByteStringToStringMode
1201+
fieldNameByteString FieldNameByteStringMode
1202+
unrecognizedTagToAny UnrecognizedTagToAnyMode
1203+
timeTagToAny TimeTagToAnyMode
1204+
simpleValues *SimpleValueRegistry
1205+
nanDec NaNMode
1206+
infDec InfMode
1207+
byteStringToTime ByteStringToTimeMode
1208+
byteStringExpectedFormat ByteStringExpectedFormatMode
1209+
bignumTag BignumTagMode
1210+
binaryUnmarshaler BinaryUnmarshalerMode
1211+
jsonUnmarshalerTranscoder Transcoder
12041212
}
12051213

12061214
var defaultDecMode, _ = DecOptions{}.decMode()
@@ -1215,32 +1223,33 @@ func (dm *decMode) DecOptions() DecOptions {
12151223
}
12161224

12171225
return DecOptions{
1218-
DupMapKey: dm.dupMapKey,
1219-
TimeTag: dm.timeTag,
1220-
MaxNestedLevels: dm.maxNestedLevels,
1221-
MaxArrayElements: dm.maxArrayElements,
1222-
MaxMapPairs: dm.maxMapPairs,
1223-
IndefLength: dm.indefLength,
1224-
TagsMd: dm.tagsMd,
1225-
IntDec: dm.intDec,
1226-
MapKeyByteString: dm.mapKeyByteString,
1227-
ExtraReturnErrors: dm.extraReturnErrors,
1228-
DefaultMapType: dm.defaultMapType,
1229-
UTF8: dm.utf8,
1230-
FieldNameMatching: dm.fieldNameMatching,
1231-
BigIntDec: dm.bigIntDec,
1232-
DefaultByteStringType: dm.defaultByteStringType,
1233-
ByteStringToString: dm.byteStringToString,
1234-
FieldNameByteString: dm.fieldNameByteString,
1235-
UnrecognizedTagToAny: dm.unrecognizedTagToAny,
1236-
TimeTagToAny: dm.timeTagToAny,
1237-
SimpleValues: simpleValues,
1238-
NaN: dm.nanDec,
1239-
Inf: dm.infDec,
1240-
ByteStringToTime: dm.byteStringToTime,
1241-
ByteStringExpectedFormat: dm.byteStringExpectedFormat,
1242-
BignumTag: dm.bignumTag,
1243-
BinaryUnmarshaler: dm.binaryUnmarshaler,
1226+
DupMapKey: dm.dupMapKey,
1227+
TimeTag: dm.timeTag,
1228+
MaxNestedLevels: dm.maxNestedLevels,
1229+
MaxArrayElements: dm.maxArrayElements,
1230+
MaxMapPairs: dm.maxMapPairs,
1231+
IndefLength: dm.indefLength,
1232+
TagsMd: dm.tagsMd,
1233+
IntDec: dm.intDec,
1234+
MapKeyByteString: dm.mapKeyByteString,
1235+
ExtraReturnErrors: dm.extraReturnErrors,
1236+
DefaultMapType: dm.defaultMapType,
1237+
UTF8: dm.utf8,
1238+
FieldNameMatching: dm.fieldNameMatching,
1239+
BigIntDec: dm.bigIntDec,
1240+
DefaultByteStringType: dm.defaultByteStringType,
1241+
ByteStringToString: dm.byteStringToString,
1242+
FieldNameByteString: dm.fieldNameByteString,
1243+
UnrecognizedTagToAny: dm.unrecognizedTagToAny,
1244+
TimeTagToAny: dm.timeTagToAny,
1245+
SimpleValues: simpleValues,
1246+
NaN: dm.nanDec,
1247+
Inf: dm.infDec,
1248+
ByteStringToTime: dm.byteStringToTime,
1249+
ByteStringExpectedFormat: dm.byteStringExpectedFormat,
1250+
BignumTag: dm.bignumTag,
1251+
BinaryUnmarshaler: dm.binaryUnmarshaler,
1252+
JSONUnmarshalerTranscoder: dm.jsonUnmarshalerTranscoder,
12441253
}
12451254
}
12461255

@@ -1467,6 +1476,14 @@ func (d *decoder) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolin
14671476

14681477
case specialTypeUnexportedUnmarshalerIface:
14691478
return d.parseToUnexportedUnmarshaler(v)
1479+
1480+
case specialTypeJSONUnmarshalerIface:
1481+
// This special type implies that the type does not also implement
1482+
// cbor.Umarshaler.
1483+
if d.dm.jsonUnmarshalerTranscoder == nil {
1484+
break
1485+
}
1486+
return d.parseToJSONUnmarshaler(v)
14701487
}
14711488
}
14721489

@@ -1832,6 +1849,31 @@ func (d *decoder) parseToUnexportedUnmarshaler(v reflect.Value) error {
18321849
return errors.New("cbor: failed to assert " + v.Type().String() + " as cbor.unmarshaler")
18331850
}
18341851

1852+
// parseToJSONUnmarshaler parses CBOR data to be transcoded to JSON and passed to the value's
1853+
// implementation of the json.Unmarshaler interface. It assumes data is well-formed, and does not
1854+
// perform bounds checking.
1855+
func (d *decoder) parseToJSONUnmarshaler(v reflect.Value) error {
1856+
if d.nextCBORNil() && v.Kind() == reflect.Pointer && v.IsNil() {
1857+
d.skip()
1858+
return nil
1859+
}
1860+
1861+
if v.Kind() != reflect.Pointer && v.CanAddr() {
1862+
v = v.Addr()
1863+
}
1864+
if u, ok := v.Interface().(jsonUnmarshaler); ok {
1865+
start := d.off
1866+
d.skip()
1867+
var jbuffer bytes.Buffer
1868+
if err := d.dm.jsonUnmarshalerTranscoder.Transcode(&jbuffer, bytes.NewReader(d.data[start:d.off])); err != nil {
1869+
return &TranscodeError{err: err, rtype: v.Type(), source: "cbor", target: "json"}
1870+
}
1871+
return u.UnmarshalJSON(jbuffer.Bytes())
1872+
}
1873+
d.skip()
1874+
return errors.New("cbor: failed to assert " + v.Type().String() + " as json.Unmarshaler")
1875+
}
1876+
18351877
// parse parses CBOR data and returns value in default Go type.
18361878
// It assumes data is well-formed, and does not perform bounds checking.
18371879
func (d *decoder) parse(skipSelfDescribedTag bool) (any, error) { //nolint:gocyclo
@@ -2988,13 +3030,16 @@ func (d *decoder) nextCBORNil() bool {
29883030
return d.data[d.off] == 0xf6 || d.data[d.off] == 0xf7
29893031
}
29903032

3033+
type jsonUnmarshaler interface{ UnmarshalJSON([]byte) error }
3034+
29913035
var (
29923036
typeIntf = reflect.TypeOf([]any(nil)).Elem()
29933037
typeTime = reflect.TypeOf(time.Time{})
29943038
typeBigInt = reflect.TypeOf(big.Int{})
29953039
typeUnmarshaler = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
29963040
typeUnexportedUnmarshaler = reflect.TypeOf((*unmarshaler)(nil)).Elem()
29973041
typeBinaryUnmarshaler = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem()
3042+
typeJSONUnmarshaler = reflect.TypeOf((*jsonUnmarshaler)(nil)).Elem()
29983043
typeString = reflect.TypeOf("")
29993044
typeByteSlice = reflect.TypeOf([]byte(nil))
30003045
)

0 commit comments

Comments
 (0)