Skip to content

Commit 6504b46

Browse files
authored
Allow exposing CBOR undefined value as JsonToken.VALUE_EMBEDDED_OBJECT (#588)
1 parent 63fdc54 commit 6504b46

File tree

5 files changed

+134
-11
lines changed

5 files changed

+134
-11
lines changed

cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORConstants.java

+7
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,13 @@ public final class CBORConstants
113113

114114
public final static int INT_BREAK = 0xFF;
115115

116+
/**
117+
* Marker for "undefined" value in CBOR spec.
118+
*
119+
* @since 2.20
120+
*/
121+
public final static int SIMPLE_VALUE_UNDEFINED = 0xF7;
122+
116123
/*
117124
/**********************************************************
118125
/* Basic UTF-8 decode/encode table

cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java

+48-6
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,21 @@ public enum Feature implements FormatFeature
4545
*
4646
* @since 2.20
4747
*/
48-
DECODE_USING_STANDARD_NEGATIVE_BIGINT_ENCODING(false)
48+
DECODE_USING_STANDARD_NEGATIVE_BIGINT_ENCODING(false),
49+
50+
/**
51+
* Feature that determines how an ` undefined ` value (0xF7) is decoded.
52+
* <p>
53+
* When enabled, the parser returns {@link JsonToken#VALUE_EMBEDDED_OBJECT} with a
54+
* value of {@code null}, allowing the caller to distinguish `undefined` from actual
55+
* {@link JsonToken#VALUE_NULL}.
56+
*<p>
57+
* When disabled (default, for backwards compatibility), `undefined` value is
58+
* reported as {@link JsonToken#VALUE_NULL}, maintaining legacy behavior from Jackson 2.10 to 2.19.
59+
*
60+
* @since 2.20
61+
*/
62+
HANDLE_UNDEFINED_AS_EMBEDDED_OBJECT(false)
4963
;
5064

5165
final boolean _defaultState;
@@ -1915,6 +1929,25 @@ private final byte[] _getBinaryFromString(Base64Variant variant) throws IOExcept
19151929
return _binaryValue;
19161930
}
19171931

1932+
/**
1933+
* Checking whether the current token represents an `undefined` value (0xF7).
1934+
* <p>
1935+
* This method allows distinguishing between real {@code null} and `undefined`,
1936+
* even if {@link CBORParser.Feature#HANDLE_UNDEFINED_AS_EMBEDDED_OBJECT} is disabled
1937+
* and the token is reported as {@link JsonToken#VALUE_NULL}.
1938+
*
1939+
* @return {@code true} if current token is an `undefined`, {@code false} otherwise
1940+
*
1941+
* @since 2.20
1942+
*/
1943+
public boolean isUndefined() {
1944+
if ((_currToken == JsonToken.VALUE_NULL) || (_currToken == JsonToken.VALUE_EMBEDDED_OBJECT)) {
1945+
return (_inputBuffer != null)
1946+
&& (_inputBuffer[_inputPtr - 1] & 0xFF) == SIMPLE_VALUE_UNDEFINED;
1947+
}
1948+
return false;
1949+
}
1950+
19181951
/*
19191952
/**********************************************************
19201953
/* Numeric accessors of public API
@@ -3656,13 +3689,22 @@ private final static long _long(int i1, int i2)
36563689
* Helper method to encapsulate details of handling of mysterious `undefined` value
36573690
* that is allowed to be used as something encoder could not handle (as per spec),
36583691
* whatever the heck that should be.
3659-
* Current definition for 2.9 is that we will be return {@link JsonToken#VALUE_NULL}, but
3660-
* for later versions it is likely that we will alternatively allow decoding as
3661-
* {@link JsonToken#VALUE_EMBEDDED_OBJECT} with "embedded value" of `null`.
3692+
* <p>
3693+
* For backward compatibility with Jackson 2.10 to 2.19, this value is decoded
3694+
* as {@link JsonToken#VALUE_NULL} by default.
3695+
* <p>
36623696
*
3663-
* @since 2.9.6
3697+
* since 2.20 If {@link CBORParser.Feature#HANDLE_UNDEFINED_AS_EMBEDDED_OBJECT} is enabled,
3698+
* the value will instead be decoded as {@link JsonToken#VALUE_EMBEDDED_OBJECT}
3699+
* with an embedded value of {@code null}.
3700+
*
3701+
* @since 2.10
36643702
*/
3665-
protected JsonToken _decodeUndefinedValue() throws IOException {
3703+
protected JsonToken _decodeUndefinedValue() {
3704+
if (Feature.HANDLE_UNDEFINED_AS_EMBEDDED_OBJECT.enabledIn(_formatFeatures)) {
3705+
_binaryValue = null; // should be clear but just in case
3706+
return JsonToken.VALUE_EMBEDDED_OBJECT;
3707+
}
36663708
return JsonToken.VALUE_NULL;
36673709
}
36683710

cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/UndefinedValueTest.java

+72-5
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,40 @@
44

55
import org.junit.jupiter.api.Test;
66

7-
import com.fasterxml.jackson.core.JsonParser;
87
import com.fasterxml.jackson.core.JsonToken;
98
import com.fasterxml.jackson.dataformat.cbor.*;
109

1110
import static org.junit.jupiter.api.Assertions.assertEquals;
1211
import static org.junit.jupiter.api.Assertions.assertNull;
12+
import static org.junit.jupiter.api.Assertions.assertTrue;
1313

1414
// for [dataformat-binary#93]
1515
public class UndefinedValueTest extends CBORTestBase
1616
{
17-
private final static byte BYTE_UNDEFINED = (byte) 0xF7;
17+
private final static byte BYTE_UNDEFINED = (byte) CBORConstants.SIMPLE_VALUE_UNDEFINED;
1818

1919
private final CBORFactory CBOR_F = cborFactory();
2020

2121
@Test
2222
public void testUndefinedLiteralStreaming() throws Exception
2323
{
24-
JsonParser p = cborParser(CBOR_F, new byte[] { BYTE_UNDEFINED });
24+
CBORParser p = cborParser(CBOR_F, new byte[] { BYTE_UNDEFINED });
2525
assertEquals(JsonToken.VALUE_NULL, p.nextToken());
26+
assertTrue(p.isUndefined());
27+
assertNull(p.nextToken());
28+
p.close();
29+
}
30+
31+
// @since 2.20 [jackson-dataformats-binary/137]
32+
@Test
33+
public void testUndefinedLiteralAsEmbeddedObject() throws Exception {
34+
CBORFactory f = CBORFactory.builder()
35+
.enable(CBORParser.Feature.HANDLE_UNDEFINED_AS_EMBEDDED_OBJECT)
36+
.build();
37+
CBORParser p = cborParser(f, new byte[] { BYTE_UNDEFINED });
38+
39+
assertEquals(JsonToken.VALUE_EMBEDDED_OBJECT, p.nextToken());
40+
assertTrue(p.isUndefined());
2641
assertNull(p.nextToken());
2742
p.close();
2843
}
@@ -34,9 +49,30 @@ public void testUndefinedInArray() throws Exception
3449
out.write(CBORConstants.BYTE_ARRAY_INDEFINITE);
3550
out.write(BYTE_UNDEFINED);
3651
out.write(CBORConstants.BYTE_BREAK);
37-
JsonParser p = cborParser(CBOR_F, out.toByteArray());
52+
CBORParser p = cborParser(CBOR_F, out.toByteArray());
3853
assertEquals(JsonToken.START_ARRAY, p.nextToken());
3954
assertEquals(JsonToken.VALUE_NULL, p.nextToken());
55+
assertTrue(p.isUndefined());
56+
assertEquals(JsonToken.END_ARRAY, p.nextToken());
57+
assertNull(p.nextToken());
58+
p.close();
59+
}
60+
61+
// @since 2.20 [jackson-dataformats-binary/137]
62+
@Test
63+
public void testUndefinedInArrayAsEmbeddedObject() throws Exception {
64+
CBORFactory f = CBORFactory.builder()
65+
.enable(CBORParser.Feature.HANDLE_UNDEFINED_AS_EMBEDDED_OBJECT)
66+
.build();
67+
68+
ByteArrayOutputStream out = new ByteArrayOutputStream();
69+
out.write(CBORConstants.BYTE_ARRAY_INDEFINITE);
70+
out.write(BYTE_UNDEFINED);
71+
out.write(CBORConstants.BYTE_BREAK);
72+
CBORParser p = cborParser(f, out.toByteArray());
73+
assertEquals(JsonToken.START_ARRAY, p.nextToken());
74+
assertEquals(JsonToken.VALUE_EMBEDDED_OBJECT, p.nextToken());
75+
assertTrue(p.isUndefined());
4076
assertEquals(JsonToken.END_ARRAY, p.nextToken());
4177
assertNull(p.nextToken());
4278
p.close();
@@ -57,11 +93,42 @@ public void testUndefinedInObject() throws Exception
5793
// assume we use end marker for Object, so
5894
doc[doc.length-2] = BYTE_UNDEFINED;
5995

60-
JsonParser p = cborParser(CBOR_F, doc);
96+
CBORParser p = cborParser(CBOR_F, doc);
6197
assertEquals(JsonToken.START_OBJECT, p.nextToken());
6298
assertEquals(JsonToken.FIELD_NAME, p.nextToken());
6399
assertEquals("bar", p.currentName());
64100
assertEquals(JsonToken.VALUE_NULL, p.nextToken());
101+
assertTrue(p.isUndefined());
102+
assertEquals(JsonToken.END_OBJECT, p.nextToken());
103+
assertNull(p.nextToken());
104+
p.close();
105+
}
106+
107+
// @since 2.20 [jackson-dataformats-binary/137]
108+
@Test
109+
public void testUndefinedInObjectAsEmbeddedObject() throws Exception {
110+
CBORFactory f = CBORFactory.builder()
111+
.enable(CBORParser.Feature.HANDLE_UNDEFINED_AS_EMBEDDED_OBJECT)
112+
.build();
113+
114+
ByteArrayOutputStream out = new ByteArrayOutputStream();
115+
CBORGenerator g = cborGenerator(out);
116+
g.writeStartObject();
117+
g.writeFieldName("bar");
118+
g.writeBoolean(true);
119+
g.writeEndObject();
120+
g.close();
121+
122+
byte[] doc = out.toByteArray();
123+
// assume we use end marker for Object, so
124+
doc[doc.length - 2] = BYTE_UNDEFINED;
125+
126+
CBORParser p = cborParser(f, doc);
127+
assertEquals(JsonToken.START_OBJECT, p.nextToken());
128+
assertEquals(JsonToken.FIELD_NAME, p.nextToken());
129+
assertEquals("bar", p.currentName());
130+
assertEquals(JsonToken.VALUE_EMBEDDED_OBJECT, p.nextToken());
131+
assertTrue(p.isUndefined());
65132
assertEquals(JsonToken.END_OBJECT, p.nextToken());
66133
assertNull(p.nextToken());
67134
p.close();

release-notes/CREDITS-2.x

+3
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,9 @@ Brian Gruber (@bgruber)
391391
(2.20.0)
392392

393393
Fawzi Essam (@iifawzi)
394+
* Contributed implementation of #137: (cbor) Allow exposing CBOR "undefined" value as
395+
`JsonToken.VALUE_EMBEDDED_OBJECT`; with embedded value of `null`
396+
(2.20.0)
394397
* Contributed fix for #431: (cbor) Negative `BigInteger` values not encoded/decoded
395398
correctly
396399
(2.20.0)

release-notes/VERSION-2.x

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ Active maintainers:
1616

1717
2.20.0 (not yet released)
1818

19+
#137: (cbor) Allow exposing CBOR "undefined" value as `JsonToken.VALUE_EMBEDDED_OBJECT`;
20+
with embedded value of `null`
21+
(implementation contributed by Fawzi E)
22+
1923
#431: (cbor) Negative `BigInteger` values not encoded/decoded correctly
2024
(reported by Brian G)
2125
(fix contributed by Fawzi E)

0 commit comments

Comments
 (0)