Skip to content

Commit 33a2294

Browse files
authored
Allow exposing CBOR simple values as VALUE_EMBEDDED_OBJECT (#590)
1 parent 7736252 commit 33a2294

File tree

4 files changed

+107
-24
lines changed

4 files changed

+107
-24
lines changed

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

+68-24
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,21 @@ public enum Feature implements FormatFeature
6060
*
6161
* @since 2.20
6262
*/
63-
READ_UNDEFINED_AS_EMBEDDED_OBJECT(false)
63+
READ_UNDEFINED_AS_EMBEDDED_OBJECT(false),
64+
65+
/**
66+
* Feature that determines how a CBOR "simple value" of major type 7 is exposed by parser.
67+
* <p>
68+
* When enabled, the parser returns {@link JsonToken#VALUE_EMBEDDED_OBJECT} with
69+
* an embedded value of type {@link CBORSimpleValue}, allowing the caller to distinguish
70+
* these values from actual {@link JsonToken#VALUE_NUMBER_INT}s.
71+
* When disabled, simple values are returned as {@link JsonToken#VALUE_NUMBER_INT}.
72+
*<p>
73+
* The default value is {@code false} for backwards compatibility (with versions prior to 2.20).
74+
*
75+
* @since 2.20
76+
*/
77+
READ_SIMPLE_VALUE_AS_EMBEDDED_OBJECT(false)
6478
;
6579

6680
private final boolean _defaultState;
@@ -363,6 +377,14 @@ public int getFirstTag() {
363377
*/
364378
protected TagList _tagValues = new TagList();
365379

380+
/**
381+
* When major type 7 value is encountered and exposed as {@link JsonToken#VALUE_EMBEDDED_OBJECT},
382+
* the value will be stored here.
383+
*
384+
* @since 2.20
385+
*/
386+
protected CBORSimpleValue _simpleValue;
387+
366388
/**
367389
* Flag that indicates that the current token has not yet
368390
* been fully processed, and needs to be finished for
@@ -824,9 +846,9 @@ public JsonToken nextToken() throws IOException
824846
_skipIncomplete();
825847
}
826848
_tokenInputTotal = _currInputProcessed + _inputPtr;
827-
// also: clear any data retained so far
828-
_numTypesValid = NR_UNKNOWN;
829-
_binaryValue = null;
849+
850+
// also: clear any data retained for previous token
851+
clearRetainedValues();
830852

831853
// First: need to keep track of lengths of defined-length Arrays and
832854
// Objects (to materialize END_ARRAY/END_OBJECT as necessary);
@@ -1453,12 +1475,12 @@ public boolean nextFieldName(SerializableString str) throws IOException
14531475
{
14541476
// Two parsing modes; can only succeed if expecting field name, so handle that first:
14551477
if (_streamReadContext.inObject() && _currToken != JsonToken.FIELD_NAME) {
1456-
_numTypesValid = NR_UNKNOWN;
14571478
if (_tokenIncomplete) {
14581479
_skipIncomplete();
14591480
}
14601481
_tokenInputTotal = _currInputProcessed + _inputPtr;
1461-
_binaryValue = null;
1482+
// need to clear retained values for previous token
1483+
clearRetainedValues();
14621484
_tagValues.clear();
14631485
// completed the whole Object?
14641486
if (!_streamReadContext.expectMoreValues()) {
@@ -1506,19 +1528,19 @@ public boolean nextFieldName(SerializableString str) throws IOException
15061528
}
15071529
}
15081530
// otherwise just fall back to default handling; should occur rarely
1509-
return (nextToken() == JsonToken.FIELD_NAME) && str.getValue().equals(getCurrentName());
1531+
return (nextToken() == JsonToken.FIELD_NAME) && str.getValue().equals(currentName());
15101532
}
15111533

15121534
@Override
15131535
public String nextFieldName() throws IOException
15141536
{
15151537
if (_streamReadContext.inObject() && _currToken != JsonToken.FIELD_NAME) {
1516-
_numTypesValid = NR_UNKNOWN;
15171538
if (_tokenIncomplete) {
15181539
_skipIncomplete();
15191540
}
15201541
_tokenInputTotal = _currInputProcessed + _inputPtr;
1521-
_binaryValue = null;
1542+
// need to clear retained values for previous token
1543+
clearRetainedValues();
15221544
_tagValues.clear();
15231545
// completed the whole Object?
15241546
if (!_streamReadContext.expectMoreValues()) {
@@ -1843,7 +1865,10 @@ public Object getEmbeddedObject() throws IOException
18431865
if (_tokenIncomplete) {
18441866
_finishToken();
18451867
}
1846-
if (_currToken == JsonToken.VALUE_EMBEDDED_OBJECT ) {
1868+
if (_currToken == JsonToken.VALUE_EMBEDDED_OBJECT) {
1869+
if (_simpleValue != null) {
1870+
return _simpleValue;
1871+
}
18471872
return _binaryValue;
18481873
}
18491874
return null;
@@ -1933,11 +1958,11 @@ private final byte[] _getBinaryFromString(Base64Variant variant) throws IOExcept
19331958
/**
19341959
* Checking whether the current token represents an `undefined` value (0xF7).
19351960
* <p>
1936-
* This method allows distinguishing between real {@code null} and `undefined`,
1961+
* This method allows distinguishing between real {@code null} and {@code undefined},
19371962
* even if {@link CBORParser.Feature#READ_UNDEFINED_AS_EMBEDDED_OBJECT} is disabled
19381963
* and the token is reported as {@link JsonToken#VALUE_NULL}.
19391964
*
1940-
* @return {@code true} if current token is an `undefined`, {@code false} otherwise
1965+
* @return {@code true} if current token is an {@code undefined}, {@code false} otherwise
19411966
*
19421967
* @since 2.20
19431968
*/
@@ -3713,38 +3738,50 @@ protected JsonToken _decodeUndefinedValue() {
37133738
* Helper method that deals with details of decoding unallocated "simple values"
37143739
* and exposing them as expected token.
37153740
* <p>
3716-
* As of Jackson 2.12, simple values are exposed as
3717-
* {@link JsonToken#VALUE_NUMBER_INT}s,
3718-
* but in later versions this is planned to be changed to separate value type.
3741+
* Starting with Jackson 2.20, this behavior can be changed by enabling the
3742+
* {@link CBORParser.Feature#READ_SIMPLE_VALUE_AS_EMBEDDED_OBJECT}
3743+
* feature, in which case simple values are returned as {@link JsonToken#VALUE_EMBEDDED_OBJECT} with an
3744+
* embedded {@link CBORSimpleValue} instance.
37193745
*
37203746
* @since 2.12
37213747
*/
37223748
public JsonToken _decodeSimpleValue(int lowBits, int ch) throws IOException {
37233749
if (lowBits > 24) {
37243750
_invalidToken(ch);
37253751
}
3752+
final boolean simpleAsEmbedded = Feature.READ_SIMPLE_VALUE_AS_EMBEDDED_OBJECT.enabledIn(_formatFeatures);
37263753
if (lowBits < 24) {
3727-
_numberInt = lowBits;
3754+
if (simpleAsEmbedded) {
3755+
_simpleValue = new CBORSimpleValue(lowBits);
3756+
} else {
3757+
_numberInt = lowBits;
3758+
}
37283759
} else { // need another byte
37293760
if (_inputPtr >= _inputEnd) {
37303761
loadMoreGuaranteed();
37313762
}
3732-
_numberInt = _inputBuffer[_inputPtr++] & 0xFF;
3763+
37333764
// As per CBOR spec, values below 32 not allowed to avoid
37343765
// confusion (as well as guarantee uniqueness of encoding)
3735-
if (_numberInt < 32) {
3766+
int value = _inputBuffer[_inputPtr++] & 0xFF;
3767+
if (value < 32) {
37363768
throw _constructError("Invalid second byte for simple value: 0x"
3737-
+Integer.toHexString(_numberInt)+" (only values 0x20 - 0xFF allowed)");
3769+
+Integer.toHexString(value)+" (only values 0x20 - 0xFF allowed)");
3770+
}
3771+
3772+
if (simpleAsEmbedded) {
3773+
_simpleValue = new CBORSimpleValue(value);
3774+
} else {
3775+
_numberInt = value;
37383776
}
37393777
}
37403778

3741-
// 25-Nov-2020, tatu: Although ideally we should report these
3742-
// as `JsonToken.VALUE_EMBEDDED_OBJECT`, due to late addition
3743-
// of handling in 2.12, simple value in 2.12 will be reported
3744-
// as simple ints.
3779+
if (simpleAsEmbedded) {
3780+
return JsonToken.VALUE_EMBEDDED_OBJECT;
3781+
}
37453782

37463783
_numTypesValid = NR_INT;
3747-
return (JsonToken.VALUE_NUMBER_INT);
3784+
return JsonToken.VALUE_NUMBER_INT;
37483785
}
37493786

37503787
/*
@@ -4101,4 +4138,11 @@ private void createChildObjectContext(final int len) throws IOException {
41014138
_streamReadContext = _streamReadContext.createChildObjectContext(len);
41024139
_streamReadConstraints.validateNestingDepth(_streamReadContext.getNestingDepth());
41034140
}
4141+
4142+
// @since 2.20
4143+
private void clearRetainedValues() {
4144+
_numTypesValid = NR_UNKNOWN;
4145+
_binaryValue = null;
4146+
_simpleValue = null;
4147+
}
41044148
}

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

+32
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,23 @@ public void testTinySimpleValues() throws Exception
3030
}
3131
}
3232

33+
@Test
34+
public void testTinySimpleValuesAsEmbeddedObjectWhenEnabled() throws Exception
35+
{
36+
CBORFactory f = CBORFactory.builder()
37+
.enable(CBORParser.Feature.READ_SIMPLE_VALUE_AS_EMBEDDED_OBJECT)
38+
.build();
39+
// Values 0..19 are unassigned, valid to encounter
40+
for (int v = 0; v <= 19; ++v) {
41+
byte[] doc = new byte[1];
42+
doc[0] = (byte) (CBORConstants.PREFIX_TYPE_MISC + v);
43+
try (CBORParser p = cborParser(f, doc)) {
44+
assertToken(JsonToken.VALUE_EMBEDDED_OBJECT, p.nextToken());
45+
assertEquals(new CBORSimpleValue(v), p.getEmbeddedObject());
46+
}
47+
}
48+
}
49+
3350
@Test
3451
public void testValidByteLengthMinimalValues() throws Exception {
3552
// Values 32..255 are unassigned, valid to encounter
@@ -44,6 +61,21 @@ public void testValidByteLengthMinimalValues() throws Exception {
4461
}
4562
}
4663

64+
@Test
65+
public void testValidByteLengthMinimalValuesAsEmbeddedObjectWhenEnabled() throws Exception {
66+
// Values 32..255 are unassigned, valid to encounter
67+
CBORFactory f = CBORFactory.builder()
68+
.enable(CBORParser.Feature.READ_SIMPLE_VALUE_AS_EMBEDDED_OBJECT)
69+
.build();
70+
for (int v = 32; v <= 255; ++v) {
71+
byte[] doc = { (byte) (CBORConstants.PREFIX_TYPE_MISC + 24), (byte) v };
72+
try (CBORParser p = cborParser(f, doc)) {
73+
assertToken(JsonToken.VALUE_EMBEDDED_OBJECT, p.nextToken());
74+
assertEquals(new CBORSimpleValue(v), p.getEmbeddedObject());
75+
}
76+
}
77+
}
78+
4779
@Test
4880
public void testInvalidByteLengthMinimalValues() throws Exception {
4981
// Values 0..31 are invalid for variant that takes 2 bytes...

release-notes/CREDITS-2.x

+4
Original file line numberDiff line numberDiff line change
@@ -397,3 +397,7 @@ Fawzi Essam (@iifawzi)
397397
* Contributed fix for #431: (cbor) Negative `BigInteger` values not encoded/decoded
398398
correctly
399399
(2.20.0)
400+
* Contributed implementation of #587: (cbor) Allow exposing CBOR Simple values as
401+
`JsonToken.VALUE_EMBEDDED_OBJECT` with a feature flag
402+
(2.20.0)
403+

release-notes/VERSION-2.x

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ Active maintainers:
2222
#431: (cbor) Negative `BigInteger` values not encoded/decoded correctly
2323
(reported by Brian G)
2424
(fix contributed by Fawzi E)
25+
#587: (cbor) Allow exposing CBOR Simple values as `JsonToken.VALUE_EMBEDDED_OBJECT`
26+
with a feature flag
27+
(implementation contributed by Fawzi E)
2528
- Generate SBOMs [JSTEP-14]
2629

2730
2.19.0 (24-Apr-2025)

0 commit comments

Comments
 (0)