@@ -58,17 +58,19 @@ import (
5858//
5959// Marshal supports format string stored under the "cbor" key in the struct
6060// field's tag. CBOR format string can specify the name of the field,
61- // "omitempty" and "keyasint" options, and special case "-" for field omission.
62- // If "cbor" key is absent, Marshal uses "json" key.
61+ // "omitempty", "omitzero" and "keyasint" options, and special case "-" for
62+ // field omission. If "cbor" key is absent, Marshal uses "json" key.
63+ // When using the "json" key, the "omitzero" option is honored when building
64+ // with Go 1.24+ to match stdlib encoding/json behavior.
6365//
6466// Struct field name is treated as integer if it has "keyasint" option in
6567// its format string. The format string must specify an integer as its
6668// field name.
6769//
6870// Special struct field "_" is used to specify struct level options, such as
6971// "toarray". "toarray" option enables Go struct to be encoded as CBOR array.
70- // "omitempty" is disabled by "toarray" to ensure that the same number
71- // of elements are encoded every time.
72+ // "omitempty" and "omitzero" are disabled by "toarray" to ensure that the
73+ // same number of elements are encoded every time.
7274//
7375// Anonymous struct fields are marshaled as if their exported fields
7476// were fields in the outer struct. Marshal follows the same struct fields
@@ -975,6 +977,7 @@ func putEncodeBuffer(e *bytes.Buffer) {
975977
976978type encodeFunc func (e * bytes.Buffer , em * encMode , v reflect.Value ) error
977979type isEmptyFunc func (em * encMode , v reflect.Value ) (empty bool , err error )
980+ type isZeroFunc func (v reflect.Value ) (zero bool , err error )
978981
979982func encode (e * bytes.Buffer , em * encMode , v reflect.Value ) error {
980983 if ! v .IsValid () {
@@ -983,7 +986,7 @@ func encode(e *bytes.Buffer, em *encMode, v reflect.Value) error {
983986 return nil
984987 }
985988 vt := v .Type ()
986- f , _ := getEncodeFunc (vt )
989+ f , _ , _ := getEncodeFunc (vt )
987990 if f == nil {
988991 return & UnsupportedTypeError {vt }
989992 }
@@ -1483,6 +1486,15 @@ func encodeStruct(e *bytes.Buffer, em *encMode, v reflect.Value) (err error) {
14831486 continue
14841487 }
14851488 }
1489+ if f .omitZero {
1490+ zero , err := f .izf (fv )
1491+ if err != nil {
1492+ return err
1493+ }
1494+ if zero {
1495+ continue
1496+ }
1497+ }
14861498
14871499 if ! f .keyAsInt && em .fieldName == FieldNameToByteString {
14881500 e .Write (f .cborNameByteString )
@@ -1775,32 +1787,32 @@ var (
17751787 typeByteString = reflect .TypeOf (ByteString ("" ))
17761788)
17771789
1778- func getEncodeFuncInternal (t reflect.Type ) (ef encodeFunc , ief isEmptyFunc ) {
1790+ func getEncodeFuncInternal (t reflect.Type ) (ef encodeFunc , ief isEmptyFunc , izf isZeroFunc ) {
17791791 k := t .Kind ()
17801792 if k == reflect .Pointer {
1781- return getEncodeIndirectValueFunc (t ), isEmptyPtr
1793+ return getEncodeIndirectValueFunc (t ), isEmptyPtr , getIsZeroFunc ( t )
17821794 }
17831795 switch t {
17841796 case typeSimpleValue :
1785- return encodeMarshalerType , isEmptyUint
1797+ return encodeMarshalerType , isEmptyUint , getIsZeroFunc ( t )
17861798
17871799 case typeTag :
1788- return encodeTag , alwaysNotEmpty
1800+ return encodeTag , alwaysNotEmpty , getIsZeroFunc ( t )
17891801
17901802 case typeTime :
1791- return encodeTime , alwaysNotEmpty
1803+ return encodeTime , alwaysNotEmpty , getIsZeroFunc ( t )
17921804
17931805 case typeBigInt :
1794- return encodeBigInt , alwaysNotEmpty
1806+ return encodeBigInt , alwaysNotEmpty , getIsZeroFunc ( t )
17951807
17961808 case typeRawMessage :
1797- return encodeMarshalerType , isEmptySlice
1809+ return encodeMarshalerType , isEmptySlice , getIsZeroFunc ( t )
17981810
17991811 case typeByteString :
1800- return encodeMarshalerType , isEmptyString
1812+ return encodeMarshalerType , isEmptyString , getIsZeroFunc ( t )
18011813 }
18021814 if reflect .PointerTo (t ).Implements (typeMarshaler ) {
1803- return encodeMarshalerType , alwaysNotEmpty
1815+ return encodeMarshalerType , alwaysNotEmpty , getIsZeroFunc ( t )
18041816 }
18051817 if reflect .PointerTo (t ).Implements (typeBinaryMarshaler ) {
18061818 defer func () {
@@ -1815,63 +1827,63 @@ func getEncodeFuncInternal(t reflect.Type) (ef encodeFunc, ief isEmptyFunc) {
18151827 }
18161828 switch k {
18171829 case reflect .Bool :
1818- return encodeBool , isEmptyBool
1830+ return encodeBool , isEmptyBool , getIsZeroFunc ( t )
18191831
18201832 case reflect .Int , reflect .Int8 , reflect .Int16 , reflect .Int32 , reflect .Int64 :
1821- return encodeInt , isEmptyInt
1833+ return encodeInt , isEmptyInt , getIsZeroFunc ( t )
18221834
18231835 case reflect .Uint , reflect .Uint8 , reflect .Uint16 , reflect .Uint32 , reflect .Uint64 :
1824- return encodeUint , isEmptyUint
1836+ return encodeUint , isEmptyUint , getIsZeroFunc ( t )
18251837
18261838 case reflect .Float32 , reflect .Float64 :
1827- return encodeFloat , isEmptyFloat
1839+ return encodeFloat , isEmptyFloat , getIsZeroFunc ( t )
18281840
18291841 case reflect .String :
1830- return encodeString , isEmptyString
1842+ return encodeString , isEmptyString , getIsZeroFunc ( t )
18311843
18321844 case reflect .Slice :
18331845 if t .Elem ().Kind () == reflect .Uint8 {
1834- return encodeByteString , isEmptySlice
1846+ return encodeByteString , isEmptySlice , getIsZeroFunc ( t )
18351847 }
18361848 fallthrough
18371849
18381850 case reflect .Array :
1839- f , _ := getEncodeFunc (t .Elem ())
1851+ f , _ , _ := getEncodeFunc (t .Elem ())
18401852 if f == nil {
1841- return nil , nil
1853+ return nil , nil , nil
18421854 }
1843- return arrayEncodeFunc {f : f }.encode , isEmptySlice
1855+ return arrayEncodeFunc {f : f }.encode , isEmptySlice , getIsZeroFunc ( t )
18441856
18451857 case reflect .Map :
18461858 f := getEncodeMapFunc (t )
18471859 if f == nil {
1848- return nil , nil
1860+ return nil , nil , nil
18491861 }
1850- return f , isEmptyMap
1862+ return f , isEmptyMap , getIsZeroFunc ( t )
18511863
18521864 case reflect .Struct :
18531865 // Get struct's special field "_" tag options
18541866 if f , ok := t .FieldByName ("_" ); ok {
18551867 tag := f .Tag .Get ("cbor" )
18561868 if tag != "-" {
18571869 if hasToArrayOption (tag ) {
1858- return encodeStructToArray , isEmptyStruct
1870+ return encodeStructToArray , isEmptyStruct , isZeroFieldStruct
18591871 }
18601872 }
18611873 }
1862- return encodeStruct , isEmptyStruct
1874+ return encodeStruct , isEmptyStruct , getIsZeroFunc ( t )
18631875
18641876 case reflect .Interface :
1865- return encodeIntf , isEmptyIntf
1877+ return encodeIntf , isEmptyIntf , getIsZeroFunc ( t )
18661878 }
1867- return nil , nil
1879+ return nil , nil , nil
18681880}
18691881
18701882func getEncodeIndirectValueFunc (t reflect.Type ) encodeFunc {
18711883 for t .Kind () == reflect .Pointer {
18721884 t = t .Elem ()
18731885 }
1874- f , _ := getEncodeFunc (t )
1886+ f , _ , _ := getEncodeFunc (t )
18751887 if f == nil {
18761888 return nil
18771889 }
@@ -1987,3 +1999,96 @@ func float32NaNFromReflectValue(v reflect.Value) float32 {
19871999 f32 := p .Convert (reflect .TypeOf ((* float32 )(nil ))).Elem ().Interface ().(float32 )
19882000 return f32
19892001}
2002+
2003+ type isZeroer interface {
2004+ IsZero () bool
2005+ }
2006+
2007+ var isZeroerType = reflect .TypeOf ((* isZeroer )(nil )).Elem ()
2008+
2009+ // getIsZeroFunc returns a function for the given type that can be called to determine if a given value is zero.
2010+ // Types that implement `IsZero() bool` are delegated to for non-nil values.
2011+ // Types that do not implement `IsZero() bool` use the reflect.Value#IsZero() implementation.
2012+ // The returned function matches behavior of stdlib encoding/json behavior in Go 1.24+.
2013+ func getIsZeroFunc (t reflect.Type ) isZeroFunc {
2014+ // Provide a function that uses a type's IsZero method if defined.
2015+ switch {
2016+ case t == nil :
2017+ return isZeroDefault
2018+ case t .Kind () == reflect .Interface && t .Implements (isZeroerType ):
2019+ return isZeroInterfaceCustom
2020+ case t .Kind () == reflect .Pointer && t .Implements (isZeroerType ):
2021+ return isZeroPointerCustom
2022+ case t .Implements (isZeroerType ):
2023+ return isZeroCustom
2024+ case reflect .PointerTo (t ).Implements (isZeroerType ):
2025+ return isZeroAddrCustom
2026+ default :
2027+ return isZeroDefault
2028+ }
2029+ }
2030+
2031+ // isZeroInterfaceCustom returns true for nil or pointer-to-nil values,
2032+ // and delegates to the custom IsZero() implementation otherwise.
2033+ func isZeroInterfaceCustom (v reflect.Value ) (bool , error ) {
2034+ kind := v .Kind ()
2035+
2036+ switch kind {
2037+ case reflect .Chan , reflect .Func , reflect .Map , reflect .Pointer , reflect .Interface , reflect .Slice :
2038+ if v .IsNil () {
2039+ return true , nil
2040+ }
2041+ }
2042+
2043+ switch kind {
2044+ case reflect .Interface , reflect .Pointer :
2045+ if elem := v .Elem (); elem .Kind () == reflect .Pointer && elem .IsNil () {
2046+ return true , nil
2047+ }
2048+ }
2049+
2050+ return v .Interface ().(isZeroer ).IsZero (), nil
2051+ }
2052+
2053+ // isZeroPointerCustom returns true for nil values,
2054+ // and delegates to the custom IsZero() implementation otherwise.
2055+ func isZeroPointerCustom (v reflect.Value ) (bool , error ) {
2056+ if v .IsNil () {
2057+ return true , nil
2058+ }
2059+ return v .Interface ().(isZeroer ).IsZero (), nil
2060+ }
2061+
2062+ // isZeroCustom delegates to the custom IsZero() implementation.
2063+ func isZeroCustom (v reflect.Value ) (bool , error ) {
2064+ return v .Interface ().(isZeroer ).IsZero (), nil
2065+ }
2066+
2067+ // isZeroAddrCustom delegates to the custom IsZero() implementation of the addr of the value.
2068+ func isZeroAddrCustom (v reflect.Value ) (bool , error ) {
2069+ if ! v .CanAddr () {
2070+ // Temporarily box v so we can take the address.
2071+ v2 := reflect .New (v .Type ()).Elem ()
2072+ v2 .Set (v )
2073+ v = v2
2074+ }
2075+ return v .Addr ().Interface ().(isZeroer ).IsZero (), nil
2076+ }
2077+
2078+ // isZeroDefault calls reflect.Value#IsZero()
2079+ func isZeroDefault (v reflect.Value ) (bool , error ) {
2080+ if ! v .IsValid () {
2081+ // v is zero value
2082+ return true , nil
2083+ }
2084+ return v .IsZero (), nil
2085+ }
2086+
2087+ // isZeroFieldStruct is used to determine whether to omit toarray structs
2088+ func isZeroFieldStruct (v reflect.Value ) (bool , error ) {
2089+ structType , err := getEncodingStructType (v .Type ())
2090+ if err != nil {
2091+ return false , err
2092+ }
2093+ return len (structType .fields ) == 0 , nil
2094+ }
0 commit comments