Skip to content

Commit 03b355e

Browse files
committed
Allow exposing CBOR undefined value as JsonToken.VALUE_EMBEDDED_OBJECT
Signed-off-by: Fawzi Essam <iifawzie@gmail.com>
1 parent 63fdc54 commit 03b355e

File tree

3 files changed

+117
-9
lines changed

3 files changed

+117
-9
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ public final class CBORConstants
113113

114114
public final static int INT_BREAK = 0xFF;
115115

116+
public final static int SIMPLE_VALUE_UNDEFINED = 0xF7;
117+
116118
/*
117119
/**********************************************************
118120
/* Basic UTF-8 decode/encode table

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

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,23 @@ 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+
*
53+
* <p>
54+
* When enabled, the parser returns {@link JsonToken#VALUE_EMBEDDED_OBJECT} with a
55+
* value of {@code null}, allowing the caller to distinguish `undefined` from actual
56+
* {@link JsonToken#VALUE_NULL}.
57+
*<p>
58+
* When disabled (default, for backwards compatibility), `undefined` value is
59+
* reported as {@link JsonToken#VALUE_NULL}, maintaining legacy behavior from Jackson 2.9.6 to 2.19.
60+
*<p>
61+
*
62+
* @since 2.20
63+
*/
64+
HANDLE_UNDEFINED_AS_EMBEDDED_OBJECT(false)
4965
;
5066

5167
final boolean _defaultState;
@@ -1915,6 +1931,20 @@ private final byte[] _getBinaryFromString(Base64Variant variant) throws IOExcept
19151931
return _binaryValue;
19161932
}
19171933

1934+
/**
1935+
* Checking whether the current token represents an `undefined` value (0xF7).
1936+
* <p>
1937+
* This method allows distinguishing between real {@code null} and `undefined`,
1938+
* even if {@link CBORParser.Feature#HANDLE_UNDEFINED_AS_EMBEDDED_OBJECT} is disabled
1939+
* and the token is reported as {@link JsonToken#VALUE_NULL}.
1940+
*
1941+
* @return {@code true} if current token is an `undefined`, {@code false} otherwise
1942+
* @since 2.20
1943+
*/
1944+
public boolean isUndefined() {
1945+
return (_inputBuffer[_inputPtr - 1] & 0xFF) == SIMPLE_VALUE_UNDEFINED;
1946+
}
1947+
19181948
/*
19191949
/**********************************************************
19201950
/* Numeric accessors of public API
@@ -3656,13 +3686,22 @@ private final static long _long(int i1, int i2)
36563686
* Helper method to encapsulate details of handling of mysterious `undefined` value
36573687
* that is allowed to be used as something encoder could not handle (as per spec),
36583688
* 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`.
3689+
*
3690+
* <p>
3691+
* For backward compatibility with Jackson 2.9.6 to 2.19, this value is decoded
3692+
* as {@link JsonToken#VALUE_NULL} by default.
3693+
* <p>
3694+
*
3695+
* since 2.20 If {@link CBORParser.Feature#HANDLE_UNDEFINED_AS_EMBEDDED_OBJECT} is enabled,
3696+
* the value will instead be decoded as {@link JsonToken#VALUE_EMBEDDED_OBJECT}
3697+
* with an embedded value of {@code null}.
36623698
*
36633699
* @since 2.9.6
36643700
*/
3665-
protected JsonToken _decodeUndefinedValue() throws IOException {
3701+
protected JsonToken _decodeUndefinedValue() {
3702+
if (CBORParser.Feature.HANDLE_UNDEFINED_AS_EMBEDDED_OBJECT.enabledIn(_formatFeatures)) {
3703+
return JsonToken.VALUE_EMBEDDED_OBJECT;
3704+
}
36663705
return JsonToken.VALUE_NULL;
36673706
}
36683707

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

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
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
@@ -21,8 +21,23 @@ public class UndefinedValueTest extends CBORTestBase
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();

0 commit comments

Comments
 (0)