Skip to content

Commit e23bdb3

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 e23bdb3

File tree

3 files changed

+121
-9
lines changed

3 files changed

+121
-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: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,26 @@ 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 CBOR `undefined` value (0xF7)
52+
* is decoded.
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` values are
59+
* reported as {@link JsonToken#VALUE_NULL}, maintaining legacy behavior from Jackson 2.9.6 onward.
60+
*<p>
61+
* This feature only affects token-level interpretation; a separate accessor
62+
* will be available to explicitly query whether the last token was CBOR `undefined`,
63+
* regardless of token form.
64+
*
65+
* @since 2.20
66+
*/
67+
HANDLE_UNDEFINED_AS_EMBEDDED_OBJECT(false)
4968
;
5069

5170
final boolean _defaultState;
@@ -1915,6 +1934,21 @@ private final byte[] _getBinaryFromString(Base64Variant variant) throws IOExcept
19151934
return _binaryValue;
19161935
}
19171936

1937+
/**
1938+
* Method to check whether the current token represents a CBOR `undefined` value
1939+
* (major type 7, value 23 — 0xF7).
1940+
* <p>
1941+
* This method allows distinguishing between real {@code null} and CBOR `undefined`,
1942+
* even if {@link CBORParser.Feature#HANDLE_UNDEFINED_AS_EMBEDDED_OBJECT} is disabled
1943+
* and the token is reported as {@link JsonToken#VALUE_NULL}.
1944+
*
1945+
* @return {@code true} if current token is a CBOR `undefined`, {@code false} otherwise
1946+
* @since 2.20
1947+
*/
1948+
public boolean isUndefined() {
1949+
return (_inputBuffer[_inputPtr - 1] & 0xFF) == SIMPLE_VALUE_UNDEFINED;
1950+
}
1951+
19181952
/*
19191953
/**********************************************************
19201954
/* Numeric accessors of public API
@@ -3656,13 +3690,22 @@ private final static long _long(int i1, int i2)
36563690
* Helper method to encapsulate details of handling of mysterious `undefined` value
36573691
* that is allowed to be used as something encoder could not handle (as per spec),
36583692
* 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`.
3693+
*
3694+
* <p>
3695+
* For backward compatibility with Jackson 2.9.6 and later, this value is decoded
3696+
* as {@link JsonToken#VALUE_NULL} by default.
3697+
* <p>
3698+
*
3699+
* since 2.20 If {@link CBORParser.Feature#HANDLE_UNDEFINED_AS_EMBEDDED_OBJECT} is enabled,
3700+
* this value will instead be decoded as {@link JsonToken#VALUE_EMBEDDED_OBJECT}
3701+
* with an embedded value of {@code null}.
36623702
*
36633703
* @since 2.9.6
36643704
*/
3665-
protected JsonToken _decodeUndefinedValue() throws IOException {
3705+
protected JsonToken _decodeUndefinedValue() {
3706+
if (CBORParser.Feature.HANDLE_UNDEFINED_AS_EMBEDDED_OBJECT.enabledIn(_formatFeatures)) {
3707+
return JsonToken.VALUE_EMBEDDED_OBJECT;
3708+
}
36663709
return JsonToken.VALUE_NULL;
36673710
}
36683711

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)