Skip to content

Commit 728a295

Browse files
authored
Fix #6059 (#6061)
1 parent 74d181c commit 728a295

3 files changed

Lines changed: 72 additions & 3 deletions

File tree

release-notes/VERSION

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ No changes since 3.2
2525
using creator-based instantiation (such as `record`)
2626
(reported by Théo S)
2727
(fix by @seonwooj0810)
28+
#6059: `ObjectMapper.valueToTree()` with `byte[]` does not produce `BinaryNode`
29+
(reported by @sebastien-leveque)
30+
(fix by @cowtowncoder, w/ Claude code)
2831
#6060: `@JsonView` by-passed for `@JsonUnwrapped` Field/Setter properties
2932

3033
3.2.0 (08-Jun-2026)

src/main/java/tools/jackson/databind/node/TreeBuildingGenerator.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -463,8 +463,13 @@ public JsonGenerator writePOJO(Object value)
463463
// but for now let's follow what `TokenBuffer` does and by default
464464
// fully serialize given Java value... unless it's one of a small
465465
// number of "well-known" types to preserve.
466-
final Class<?> raw = value.getClass();
467-
if (raw == byte[].class || (value instanceof RawValue)) {
466+
// 26-Jun-2026, tatu: [databind#6059] `byte[]` must become `BinaryNode`,
467+
// not `POJONode`, to match the `writeBinary()` path
468+
if (value instanceof byte[] bytes) {
469+
_tokenWriteContext.writeBinary(bytes);
470+
return this;
471+
}
472+
if (value instanceof RawValue) {
468473
_tokenWriteContext.writePOJO(value);
469474
return this;
470475
}
@@ -498,7 +503,9 @@ public JsonGenerator writeBinary(Base64Variant b64variant, byte[] data, int offs
498503
// 12-Jan-2021, tatu: Should we try to preserve the variant? Depends a
499504
// lot on whether this during read (no need to retain probably) or
500505
// write (probably important)
501-
return writePOJO(Arrays.copyOfRange(data, offset, offset + len));
506+
// 26-Jun-2026, tatu: [databind#6059] Must create `BinaryNode`, not `POJONode`
507+
_tokenWriteContext.writeBinary(Arrays.copyOfRange(data, offset, offset + len));
508+
return this;
502509
}
503510

504511
/**
@@ -542,6 +549,11 @@ public JsonGenerator writeObjectId(Object id) {
542549
public JsonGenerator writeEmbeddedObject(Object object) {
543550
// 02-Mar-2021, tatu: Bit tricky, this one; should we try to
544551
// auto-detect types or something?
552+
// 26-Jun-2026, tatu: [databind#6059] Preserve `byte[]` as `BinaryNode`
553+
if (object instanceof byte[] bytes) {
554+
_tokenWriteContext.writeBinary(bytes);
555+
return this;
556+
}
545557
_tokenWriteContext.writePOJO(object);
546558
return this;
547559
}

src/test/java/tools/jackson/databind/node/JsonNodeConversionsTest.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,4 +447,58 @@ public void treeToValueWithMissingNode4932() throws Exception {
447447
assertNull(r.treeToValue(MAPPER.nullNode(), Object.class));
448448
assertNull(r.treeToValue(MAPPER.missingNode(), Object.class));
449449
}
450+
451+
// [databind#6059]: `byte[]` should convert to `BinaryNode`, not `POJONode`
452+
@Test
453+
public void byteArrayToTree6059() {
454+
final byte[] bytes = { 1, 2, 3 };
455+
JsonNode node = MAPPER.valueToTree(bytes);
456+
457+
// Must be a `BinaryNode` (not `POJONode`, as it was prior to fix)...
458+
assertInstanceOf(BinaryNode.class, node);
459+
assertTrue(node.isBinary());
460+
// ... with contents preserved...
461+
assertArrayEquals(bytes, node.binaryValue());
462+
463+
// ... and, the actual regression reported, equal to a node built
464+
// from a separate-but-equal array (`BinaryNode` compares by content,
465+
// `POJONode` only by identity):
466+
JsonNode node2 = MAPPER.valueToTree(new byte[] { 1, 2, 3 });
467+
assertEquals(node, node2);
468+
469+
// Empty array, too (from the original report):
470+
assertEquals(MAPPER.valueToTree(new byte[0]),
471+
MAPPER.valueToTree(new byte[0]));
472+
}
473+
474+
// [databind#6059]: also when `byte[]` is nested within Object/Array contexts
475+
@Test
476+
public void nestedByteArrayToTree6059() {
477+
final byte[] bytes = { 4, 5, 6 };
478+
479+
// As a value within an Object (Map):
480+
JsonNode objNode = MAPPER.valueToTree(Collections.singletonMap("b", bytes));
481+
JsonNode objBinary = objNode.get("b");
482+
assertInstanceOf(BinaryNode.class, objBinary);
483+
assertArrayEquals(bytes, objBinary.binaryValue());
484+
485+
// As an element within an Array (List):
486+
JsonNode arrNode = MAPPER.valueToTree(Collections.singletonList(bytes));
487+
JsonNode arrBinary = arrNode.get(0);
488+
assertInstanceOf(BinaryNode.class, arrBinary);
489+
assertArrayEquals(bytes, arrBinary.binaryValue());
490+
}
491+
492+
// [databind#6059]: also when `byte[]` reaches the tree via `TokenBuffer`
493+
// replay (`writeEmbeddedObject()` path), not just the direct `writeBinary()`
494+
@Test
495+
public void byteArrayViaTokenBufferToTree6059() {
496+
final byte[] bytes = { 7, 8, 9 };
497+
TokenBuffer buf = TokenBuffer.forGeneration();
498+
buf.writeBinary(bytes);
499+
500+
JsonNode node = MAPPER.valueToTree(buf);
501+
assertInstanceOf(BinaryNode.class, node);
502+
assertArrayEquals(bytes, node.binaryValue());
503+
}
450504
}

0 commit comments

Comments
 (0)