From 08beef919461a882467a816976348410f4d8b323 Mon Sep 17 00:00:00 2001 From: lw-mcno Date: Thu, 19 Feb 2026 22:25:45 +0100 Subject: [PATCH] AVRO-4235: [Java] Fix default values of fields with UNION schemas where the NULL schema comes first The assumption in JacksonUtils#toObject(JsonNode, Schema), that the actual data schema in a UNION schema (of some data with NULL), always comes first, is incorrect. For example, a field with this schema will have its default value converted incorrectly: ``` { "name": "exampleDateTime", "type": ["null", { "type": "long", "logicalType": "local-timestamp-millis" }], "default": 1746088255000 } ``` Here the "null" schema explicitly comes first and the assumption is no longer correct (JacksonUtils tries to interpret the value 1746088255000 using the NULL schema). Another example using Avro IDL (nullable field, containing an array of nullable items): ``` record SomeRecord { union{array, null} optArrayOptItemVal = [4]; } ``` In this case, the "optArrayOptItemVal" field fulfills the assumption, but the array item's schema does not. On my machine, with Avro 1.12.1, the item's schema is the following: ``` "type": ["null", { "type": "int" }] ``` which also fails the non-NULL schema first assumption. --- .../org/apache/avro/util/internal/JacksonUtils.java | 6 +++++- .../apache/avro/util/internal/TestJacksonUtils.java | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lang/java/avro/src/main/java/org/apache/avro/util/internal/JacksonUtils.java b/lang/java/avro/src/main/java/org/apache/avro/util/internal/JacksonUtils.java index 29593fba092..21df8369022 100644 --- a/lang/java/avro/src/main/java/org/apache/avro/util/internal/JacksonUtils.java +++ b/lang/java/avro/src/main/java/org/apache/avro/util/internal/JacksonUtils.java @@ -115,7 +115,11 @@ public static Object toObject(JsonNode jsonNode) { public static Object toObject(JsonNode jsonNode, Schema schema) { if (schema != null && schema.getType().equals(Schema.Type.UNION)) { - return toObject(jsonNode, schema.getTypes().get(0)); + if (schema.getTypes().get(0).getType() != (Schema.Type.NULL)) { + return toObject(jsonNode, schema.getTypes().get(0)); + } else { + return toObject(jsonNode, schema.getTypes().get(1)); + } } if (jsonNode == null) { return null; diff --git a/lang/java/avro/src/test/java/org/apache/avro/util/internal/TestJacksonUtils.java b/lang/java/avro/src/test/java/org/apache/avro/util/internal/TestJacksonUtils.java index cd6fe3f74da..d00a4955d47 100644 --- a/lang/java/avro/src/test/java/org/apache/avro/util/internal/TestJacksonUtils.java +++ b/lang/java/avro/src/test/java/org/apache/avro/util/internal/TestJacksonUtils.java @@ -79,6 +79,10 @@ void testToObject() { assertEquals(1, toObject(IntNode.valueOf(1))); assertEquals(2L, toObject(IntNode.valueOf(2), Schema.create(Schema.Type.LONG))); assertEquals(1.0f, toObject(DoubleNode.valueOf(1.0), Schema.create(Schema.Type.FLOAT))); + assertEquals(1.1f, toObject(DoubleNode.valueOf(1.1), + Schema.createUnion(Schema.create(Schema.Type.NULL), Schema.create(Schema.Type.FLOAT)))); + assertEquals(1.2f, toObject(DoubleNode.valueOf(1.2), + Schema.createUnion(Schema.create(Schema.Type.FLOAT), Schema.create(Schema.Type.NULL)))); assertEquals(2.0, toObject(DoubleNode.valueOf(2.0))); assertEquals(TextNode.valueOf("\u0001\u0002"), toJsonNode(new byte[] { 1, 2 })); assertArrayEquals(new byte[] { 1, 2 }, @@ -90,6 +94,15 @@ void testToObject() { an.add(1); assertEquals(Collections.singletonList(1), toObject(an)); + assertEquals(Collections.singletonList(1), toObject(an, Schema.createUnion(Schema.create(Schema.Type.NULL), + Schema.createArray(Schema.createUnion(Schema.create(Schema.Type.NULL), Schema.create(Schema.Type.INT)))))); + + assertEquals(Collections.singletonList(1), + toObject(an, + Schema.createUnion( + Schema.createArray(Schema.createUnion(Schema.create(Schema.Type.INT), Schema.create(Schema.Type.NULL))), + Schema.create(Schema.Type.NULL)))); + ObjectNode on = JsonNodeFactory.instance.objectNode(); on.put("a", 1); assertEquals(Collections.singletonMap("a", 1), toObject(on));