@@ -1597,6 +1597,393 @@ func TestOmitEmptyForBigInt(t *testing.T) {
15971597 testRoundTrip (t , []roundTripTest {{"default values" , v , want }}, em , dm )
15981598}
15991599
1600+ func TestOmitZeroForBuiltinType (t * testing.T ) {
1601+ type T struct {
1602+ B bool `cbor:"b"`
1603+ Bo bool `cbor:"bo,omitzero"`
1604+ UI uint `cbor:"ui"`
1605+ UIo uint `cbor:"uio,omitzero"`
1606+ I int `cbor:"i"`
1607+ Io int `cbor:"io,omitzero"`
1608+ F float64 `cbor:"f"`
1609+ Fo float64 `cbor:"fo,omitzero"`
1610+ S string `cbor:"s"`
1611+ So string `cbor:"so,omitzero"`
1612+ Slc []string `cbor:"slc"`
1613+ Slco []string `cbor:"slco,omitzero"`
1614+ M map [int ]string `cbor:"m"`
1615+ Mo map [int ]string `cbor:"mo,omitzero"`
1616+ P * int `cbor:"p"`
1617+ Po * int `cbor:"po,omitzero"`
1618+ Intf any `cbor:"intf"`
1619+ Intfo any `cbor:"intfo,omitzero"`
1620+ }
1621+
1622+ v := T {}
1623+ // {"b": false, "ui": 0, "i":0, "f": 0, "s": "", "slc": null, "m": {}, "p": nil, "intf": nil }
1624+ want := []byte {0xa9 ,
1625+ 0x61 , 0x62 , 0xf4 ,
1626+ 0x62 , 0x75 , 0x69 , 0x00 ,
1627+ 0x61 , 0x69 , 0x00 ,
1628+ 0x61 , 0x66 , 0xfb , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
1629+ 0x61 , 0x73 , 0x60 ,
1630+ 0x63 , 0x73 , 0x6c , 0x63 , 0xf6 ,
1631+ 0x61 , 0x6d , 0xf6 ,
1632+ 0x61 , 0x70 , 0xf6 ,
1633+ 0x64 , 0x69 , 0x6e , 0x74 , 0x66 , 0xf6 ,
1634+ }
1635+
1636+ em , _ := EncOptions {}.EncMode ()
1637+ dm , _ := DecOptions {}.DecMode ()
1638+ testRoundTrip (t , []roundTripTest {{"default values" , v , want }}, em , dm )
1639+ }
1640+
1641+ func TestOmitZeroForAnonymousStruct (t * testing.T ) {
1642+ type T struct {
1643+ Str struct {} `cbor:"str"`
1644+ Stro struct {} `cbor:"stro,omitzero"`
1645+ }
1646+
1647+ v := T {}
1648+ want := []byte {0xa1 , 0x63 , 0x73 , 0x74 , 0x72 , 0xa0 } // {"str": {}}
1649+
1650+ em , _ := EncOptions {}.EncMode ()
1651+ dm , _ := DecOptions {}.DecMode ()
1652+ testRoundTrip (t , []roundTripTest {{"default values" , v , want }}, em , dm )
1653+ }
1654+
1655+ func TestOmitZeroForStruct1 (t * testing.T ) {
1656+ type T1 struct {
1657+ Bo bool `cbor:"bo,omitzero"`
1658+ UIo uint `cbor:"uio,omitzero"`
1659+ Io int `cbor:"io,omitzero"`
1660+ Fo float64 `cbor:"fo,omitzero"`
1661+ So string `cbor:"so,omitzero"`
1662+ Slco []string `cbor:"slco,omitzero"`
1663+ Mo map [int ]string `cbor:"mo,omitzero"`
1664+ Po * int `cbor:"po,omitzero"`
1665+ Intfo any `cbor:"intfo,omitzero"`
1666+ }
1667+ type T struct {
1668+ Str T1 `cbor:"str"`
1669+ Stro T1 `cbor:"stro,omitzero"`
1670+ }
1671+
1672+ v := T {}
1673+ want := []byte {0xa1 , 0x63 , 0x73 , 0x74 , 0x72 , 0xa0 } // {"str": {}}
1674+
1675+ em , _ := EncOptions {}.EncMode ()
1676+ dm , _ := DecOptions {}.DecMode ()
1677+ testRoundTrip (t , []roundTripTest {{"default values" , v , want }}, em , dm )
1678+ }
1679+
1680+ func TestOmitZeroForStruct2 (t * testing.T ) {
1681+ type T1 struct {
1682+ Bo bool `cbor:"bo,omitzero"`
1683+ UIo uint `cbor:"uio,omitzero"`
1684+ Io int `cbor:"io,omitzero"`
1685+ Fo float64 `cbor:"fo,omitzero"`
1686+ So string `cbor:"so,omitzero"`
1687+ Slco []string `cbor:"slco,omitzero"`
1688+ Mo map [int ]string `cbor:"mo,omitzero"`
1689+ Po * int `cbor:"po,omitzero"`
1690+ Intfo any `cbor:"intfo"`
1691+ }
1692+ type T struct {
1693+ Stro T1 `cbor:"stro,omitzero"`
1694+ }
1695+
1696+ v := T {}
1697+ want := []byte {0xa1 , 0x64 , 0x73 , 0x74 , 0x72 , 0x6f , 0xa1 , 0x65 , 0x69 , 0x6e , 0x74 , 0x66 , 0x6f , 0xf6 } // {"stro": {intfo: nil}}
1698+
1699+ em , _ := EncOptions {}.EncMode ()
1700+ dm , _ := DecOptions {}.DecMode ()
1701+ testRoundTrip (t , []roundTripTest {{"non-default values" , v , want }}, em , dm )
1702+ }
1703+
1704+ func TestOmitZeroForNestedStruct (t * testing.T ) {
1705+ type T1 struct {
1706+ Bo bool `cbor:"bo,omitzero"`
1707+ UIo uint `cbor:"uio,omitzero"`
1708+ Io int `cbor:"io,omitzero"`
1709+ Fo float64 `cbor:"fo,omitzero"`
1710+ So string `cbor:"so,omitzero"`
1711+ Slco []string `cbor:"slco,omitzero"`
1712+ Mo map [int ]string `cbor:"mo,omitzero"`
1713+ Po * int `cbor:"po,omitzero"`
1714+ Intfo any `cbor:"intfo,omitzero"`
1715+ }
1716+ type T2 struct {
1717+ Stro T1 `cbor:"stro,omitzero"`
1718+ }
1719+ type T struct {
1720+ Str T2 `cbor:"str"`
1721+ Stro T2 `cbor:"stro,omitzero"`
1722+ }
1723+
1724+ v := T {}
1725+ want := []byte {0xa1 , 0x63 , 0x73 , 0x74 , 0x72 , 0xa0 } // {"str": {}}
1726+
1727+ em , _ := EncOptions {}.EncMode ()
1728+ dm , _ := DecOptions {}.DecMode ()
1729+ testRoundTrip (t , []roundTripTest {{"default values" , v , want }}, em , dm )
1730+ }
1731+
1732+ func TestOmitZeroForToArrayStruct1 (t * testing.T ) {
1733+ type T1 struct {
1734+ _ struct {} `cbor:",toarray"`
1735+ b bool
1736+ ui uint
1737+ i int
1738+ f float64
1739+ s string
1740+ slc []string
1741+ m map [int ]string
1742+ p * int
1743+ intf any
1744+ }
1745+ type T struct {
1746+ Str T1 `cbor:"str"`
1747+ Stro T1 `cbor:"stro,omitzero"`
1748+ }
1749+
1750+ v := T {
1751+ Str : T1 {b : false , ui : 0 , i : 0 , f : 0.0 , s : "" , slc : nil , m : nil , p : nil , intf : nil },
1752+ Stro : T1 {b : false , ui : 0 , i : 0 , f : 0.0 , s : "" , slc : nil , m : nil , p : nil , intf : nil },
1753+ }
1754+ want := []byte {0xa1 , 0x63 , 0x73 , 0x74 , 0x72 , 0x80 } // {"str": []}
1755+
1756+ em , _ := EncOptions {}.EncMode ()
1757+ dm , _ := DecOptions {}.DecMode ()
1758+ testRoundTrip (t , []roundTripTest {{"no exportable fields" , v , want }}, em , dm )
1759+ }
1760+
1761+ func TestOmitZeroForToArrayStruct2 (t * testing.T ) {
1762+ type T1 struct {
1763+ _ struct {} `cbor:",toarray"`
1764+ Bo bool `cbor:"bo"`
1765+ UIo uint `cbor:"uio"`
1766+ Io int `cbor:"io"`
1767+ Fo float64 `cbor:"fo"`
1768+ So string `cbor:"so"`
1769+ Slco []string `cbor:"slco"`
1770+ Mo map [int ]string `cbor:"mo"`
1771+ Po * int `cbor:"po"`
1772+ Intfo any `cbor:"intfo"`
1773+ }
1774+ type T struct {
1775+ Stro T1 `cbor:"stro,omitzero"`
1776+ }
1777+
1778+ v := T {}
1779+ // {"stro": [false, 0, 0, 0.0, "", [], {}, nil, nil]}
1780+ want := []byte {0xa1 , 0x64 , 0x73 , 0x74 , 0x72 , 0x6f , 0x89 , 0xf4 , 0x00 , 0x00 , 0xfb , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x60 , 0xf6 , 0xf6 , 0xf6 , 0xf6 }
1781+
1782+ em , _ := EncOptions {}.EncMode ()
1783+ dm , _ := DecOptions {}.DecMode ()
1784+ testRoundTrip (t , []roundTripTest {{"has exportable fields" , v , want }}, em , dm )
1785+ }
1786+
1787+ func TestOmitZeroForStructWithPtrToAnonymousField (t * testing.T ) {
1788+ type (
1789+ T1 struct {
1790+ X int `cbor:"x,omitzero"`
1791+ Y int `cbor:"y,omitzero"`
1792+ }
1793+ T2 struct {
1794+ * T1
1795+ }
1796+ T struct {
1797+ Stro T2 `cbor:"stro,omitzero"`
1798+ }
1799+ )
1800+
1801+ testCases := []struct {
1802+ name string
1803+ obj any
1804+ wantCborData []byte
1805+ }{
1806+ {
1807+ name : "null pointer to anonymous field" ,
1808+ obj : T {},
1809+ wantCborData : []byte {0xa0 }, // {}
1810+ },
1811+ {
1812+ name : "not-null pointer to anonymous field" ,
1813+ obj : T {T2 {& T1 {}}},
1814+ wantCborData : []byte {0xa0 }, // {}
1815+ },
1816+ {
1817+ name : "not empty value in field 1" ,
1818+ obj : T {T2 {& T1 {X : 1 }}},
1819+ wantCborData : []byte {0xa1 , 0x64 , 0x73 , 0x74 , 0x72 , 0x6f , 0xa1 , 0x61 , 0x78 , 0x01 }, // {stro:{x:1}}
1820+ },
1821+ {
1822+ name : "not empty value in field 2" ,
1823+ obj : T {T2 {& T1 {Y : 2 }}},
1824+ wantCborData : []byte {0xa1 , 0x64 , 0x73 , 0x74 , 0x72 , 0x6f , 0xa1 , 0x61 , 0x79 , 0x02 }, // {stro:{y:2}}
1825+ },
1826+ {
1827+ name : "not empty value in all fields" ,
1828+ obj : T {T2 {& T1 {X : 1 , Y : 2 }}},
1829+ wantCborData : []byte {0xa1 , 0x64 , 0x73 , 0x74 , 0x72 , 0x6f , 0xa2 , 0x61 , 0x78 , 0x01 , 0x61 , 0x79 , 0x02 }, // {stro:{x:1, y:2}}
1830+ },
1831+ }
1832+
1833+ for _ , tc := range testCases {
1834+ t .Run (tc .name , func (t * testing.T ) {
1835+ b , err := Marshal (tc .obj )
1836+ if err != nil {
1837+ t .Errorf ("Marshal(%+v) returned error %v" , tc .obj , err )
1838+ }
1839+ if ! bytes .Equal (b , tc .wantCborData ) {
1840+ t .Errorf ("Marshal(%+v) = 0x%x, want 0x%x" , tc .obj , b , tc .wantCborData )
1841+ }
1842+ })
1843+ }
1844+ }
1845+
1846+ func TestOmitZeroForStructWithAnonymousField (t * testing.T ) {
1847+ type (
1848+ T1 struct {
1849+ X int `cbor:"x,omitzero"`
1850+ Y int `cbor:"y,omitzero"`
1851+ }
1852+ T2 struct {
1853+ T1
1854+ }
1855+ T struct {
1856+ Stro T2 `cbor:"stro,omitzero"`
1857+ }
1858+ )
1859+
1860+ testCases := []struct {
1861+ name string
1862+ obj any
1863+ wantCborData []byte
1864+ }{
1865+ {
1866+ name : "default values" ,
1867+ obj : T {},
1868+ wantCborData : []byte {0xa0 }, // {}
1869+ },
1870+ {
1871+ name : "default values" ,
1872+ obj : T {T2 {T1 {}}},
1873+ wantCborData : []byte {0xa0 }, // {}
1874+ },
1875+ {
1876+ name : "not empty value in field 1" ,
1877+ obj : T {T2 {T1 {X : 1 }}},
1878+ wantCborData : []byte {0xa1 , 0x64 , 0x73 , 0x74 , 0x72 , 0x6f , 0xa1 , 0x61 , 0x78 , 0x01 }, // {stro:{x:1}}
1879+ },
1880+ {
1881+ name : "not empty value in field 2" ,
1882+ obj : T {T2 {T1 {Y : 2 }}},
1883+ wantCborData : []byte {0xa1 , 0x64 , 0x73 , 0x74 , 0x72 , 0x6f , 0xa1 , 0x61 , 0x79 , 0x02 }, // {stro:{y:2}}
1884+ },
1885+ {
1886+ name : "not empty value in all fields" ,
1887+ obj : T {T2 {T1 {X : 1 , Y : 2 }}},
1888+ wantCborData : []byte {0xa1 , 0x64 , 0x73 , 0x74 , 0x72 , 0x6f , 0xa2 , 0x61 , 0x78 , 0x01 , 0x61 , 0x79 , 0x02 }, // {stro:{x:1, y:2}}
1889+ },
1890+ }
1891+
1892+ for _ , tc := range testCases {
1893+ t .Run (tc .name , func (t * testing.T ) {
1894+ b , err := Marshal (tc .obj )
1895+ if err != nil {
1896+ t .Errorf ("Marshal(%+v) returned error %v" , tc .obj , err )
1897+ }
1898+ if ! bytes .Equal (b , tc .wantCborData ) {
1899+ t .Errorf ("Marshal(%+v) = 0x%x, want 0x%x" , tc .obj , b , tc .wantCborData )
1900+ }
1901+ })
1902+ }
1903+ }
1904+
1905+ func TestOmitZeroForBinaryMarshaler1 (t * testing.T ) {
1906+ type T1 struct {
1907+ No number `cbor:"no,omitzero"`
1908+ }
1909+ type T struct {
1910+ Str T1 `cbor:"str"`
1911+ Stro T1 `cbor:"stro,omitzero"`
1912+ }
1913+
1914+ testCases := []roundTripTest {
1915+ {
1916+ "empty BinaryMarshaler" ,
1917+ T1 {},
1918+ []byte {0xa0 }, // {}
1919+ },
1920+ {
1921+ "empty struct containing empty BinaryMarshaler" ,
1922+ T {},
1923+ []byte {0xa1 , 0x63 , 0x73 , 0x74 , 0x72 , 0xa0 }, // {str: {}}
1924+ },
1925+ }
1926+
1927+ em , _ := EncOptions {}.EncMode ()
1928+ dm , _ := DecOptions {}.DecMode ()
1929+ testRoundTrip (t , testCases , em , dm )
1930+ }
1931+
1932+ func TestOmitZeroForBinaryMarshaler2 (t * testing.T ) {
1933+ type T1 struct {
1934+ So stru `cbor:"so,omitzero"`
1935+ }
1936+ type T struct {
1937+ Str T1 `cbor:"str"`
1938+ Stro T1 `cbor:"stro,omitzero"`
1939+ }
1940+
1941+ testCases := []roundTripTest {
1942+ {
1943+ "empty BinaryMarshaler" ,
1944+ T1 {},
1945+ []byte {0xa0 }, // {}
1946+ },
1947+ {
1948+ "empty struct containing empty BinaryMarshaler" ,
1949+ T {},
1950+ []byte {0xa1 , 0x63 , 0x73 , 0x74 , 0x72 , 0xa0 }, // {str: {}}
1951+ },
1952+ }
1953+
1954+ em , _ := EncOptions {}.EncMode ()
1955+ dm , _ := DecOptions {}.DecMode ()
1956+ testRoundTrip (t , testCases , em , dm )
1957+ }
1958+
1959+ // omitzero is a no-op for time.Time.
1960+ func TestOmitZeroForTime (t * testing.T ) {
1961+ type T struct {
1962+ Tm time.Time `cbor:"t,omitzero"`
1963+ }
1964+
1965+ v := T {}
1966+ want := []byte {0xa1 , 0x61 , 0x74 , 0xf6 } // {"t": nil}
1967+
1968+ em , _ := EncOptions {}.EncMode ()
1969+ dm , _ := DecOptions {}.DecMode ()
1970+ testRoundTrip (t , []roundTripTest {{"default values" , v , want }}, em , dm )
1971+ }
1972+
1973+ // omitzero is a no-op for big.Int.
1974+ func TestOmitZeroForBigInt (t * testing.T ) {
1975+ type T struct {
1976+ I big.Int `cbor:"bi,omitzero"`
1977+ }
1978+
1979+ v := T {}
1980+ want := []byte {0xa1 , 0x62 , 0x62 , 0x69 , 0xc2 , 0x40 } // {"bi": 2([])}
1981+
1982+ em , _ := EncOptions {BigIntConvert : BigIntConvertNone }.EncMode ()
1983+ dm , _ := DecOptions {}.DecMode ()
1984+ testRoundTrip (t , []roundTripTest {{"default values" , v , want }}, em , dm )
1985+ }
1986+
16001987func TestTaggedField (t * testing.T ) {
16011988 // A field (T2.X) with a tag dominates untagged field.
16021989 type (
0 commit comments