Skip to content

Commit 8143a18

Browse files
Reduce arrow-vector dependencies: drop jackson-databind, annotations, jsr310 and commons-codec
Replace jackson-databind/annotations/jackson-datatype-jsr310 usage in arrow-vector with the jackson-core streaming API (JsonGenerator/JsonParser) plus small hand-written helpers (JsonValues, JsonStringSerializer), and replace commons-codec hex handling with java.util.HexFormat. - Schema/Field/DictionaryEncoding/ArrowType JSON (de)serialization now uses jackson-core streaming instead of ObjectMapper + jackson annotations. - OpaqueType and the IPC JsonFileReader/JsonFileWriter migrated to streaming. - JsonStringHashMap/JsonStringArrayList keep their JSON toString() via JsonStringSerializer; explicit serialVersionUID values are pinned to preserve Java deserialization compatibility, and JavaTimeModule's exact numeric output for java.time values is reproduced for byte-for-byte toString() compatibility. - Removed the public ObjectMapperFactory utility (callers updated) and the Text jackson @JsonSerialize annotation. This contains breaking changes (removed public ObjectMapperFactory and Text.TextSerializer, removed jackson annotation/databind interop, dropped transitive databind/annotations/jsr310/commons-codec dependencies, and module-info requires changes). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent cb24576 commit 8143a18

17 files changed

Lines changed: 688 additions & 236 deletions

File tree

adapter/jdbc/src/main/java/org/apache/arrow/adapter/jdbc/consumer/MapConsumer.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import org.apache.arrow.memory.BufferAllocator;
2828
import org.apache.arrow.vector.complex.MapVector;
2929
import org.apache.arrow.vector.complex.impl.UnionMapWriter;
30-
import org.apache.arrow.vector.util.ObjectMapperFactory;
3130

3231
/**
3332
* Consumer which consume map type values from {@link ResultSet}. Write the data into {@link
@@ -36,7 +35,7 @@
3635
public class MapConsumer extends BaseConsumer<MapVector> {
3736

3837
private final UnionMapWriter writer;
39-
private final ObjectMapper objectMapper = ObjectMapperFactory.newObjectMapper();
38+
private final ObjectMapper objectMapper = new ObjectMapper();
4039
private final TypeReference<Map<String, String>> typeReference =
4140
new TypeReference<Map<String, String>>() {};
4241
private int currentRow;

adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/JdbcToArrowTestHelper.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@
5656
import org.apache.arrow.vector.types.pojo.Schema;
5757
import org.apache.arrow.vector.util.JsonStringArrayList;
5858
import org.apache.arrow.vector.util.JsonStringHashMap;
59-
import org.apache.arrow.vector.util.ObjectMapperFactory;
6059
import org.apache.arrow.vector.util.Text;
6160

6261
/**
@@ -295,7 +294,7 @@ public static void assertMapVectorValues(
295294
public static Map<String, String>[] getMapValues(String[] values, String dataType) {
296295
String[] dataArr = getValues(values, dataType);
297296
Map<String, String>[] maps = new Map[dataArr.length];
298-
ObjectMapper objectMapper = ObjectMapperFactory.newObjectMapper();
297+
ObjectMapper objectMapper = new ObjectMapper();
299298
TypeReference<Map<String, String>> typeReference = new TypeReference<Map<String, String>>() {};
300299
for (int idx = 0; idx < dataArr.length; idx++) {
301300
String jsonString = dataArr[idx].replace("|", ",");

vector/pom.xml

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,23 +45,6 @@ under the License.
4545
<groupId>com.fasterxml.jackson.core</groupId>
4646
<artifactId>jackson-core</artifactId>
4747
</dependency>
48-
<dependency>
49-
<groupId>com.fasterxml.jackson.core</groupId>
50-
<artifactId>jackson-annotations</artifactId>
51-
</dependency>
52-
<dependency>
53-
<groupId>com.fasterxml.jackson.core</groupId>
54-
<artifactId>jackson-databind</artifactId>
55-
</dependency>
56-
<dependency>
57-
<groupId>com.fasterxml.jackson.datatype</groupId>
58-
<artifactId>jackson-datatype-jsr310</artifactId>
59-
</dependency>
60-
<dependency>
61-
<groupId>commons-codec</groupId>
62-
<artifactId>commons-codec</artifactId>
63-
<version>1.22.0</version>
64-
</dependency>
6548
<dependency>
6649
<groupId>org.apache.arrow</groupId>
6750
<artifactId>arrow-memory-netty</artifactId>

vector/src/main/codegen/templates/ArrowType.java

Lines changed: 72 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
import com.google.flatbuffers.FlatBufferBuilder;
2525

26+
import java.io.IOException;
27+
import java.util.Locale;
2628
import java.util.Objects;
2729

2830
import org.apache.arrow.flatbuf.Type;
@@ -32,25 +34,12 @@
3234
import org.apache.arrow.vector.FieldVector;
3335
import org.apache.arrow.vector.ValueVector;
3436

35-
import com.fasterxml.jackson.annotation.JsonCreator;
36-
import com.fasterxml.jackson.annotation.JsonIgnore;
37-
import com.fasterxml.jackson.annotation.JsonProperty;
38-
import com.fasterxml.jackson.annotation.JsonSubTypes;
39-
import com.fasterxml.jackson.annotation.JsonTypeInfo;
37+
import com.fasterxml.jackson.core.JsonGenerator;
4038

4139
/**
4240
* Arrow types
4341
* Source code generated using FreeMarker template ${.template_name}
4442
**/
45-
@JsonTypeInfo(
46-
use = JsonTypeInfo.Id.NAME,
47-
include = JsonTypeInfo.As.PROPERTY,
48-
property = "name")
49-
@JsonSubTypes({
50-
<#list arrowTypes.types as type>
51-
@JsonSubTypes.Type(value = ArrowType.${type.name?remove_ending("_")}.class, name = "${type.name?remove_ending("_")?lower_case}"),
52-
</#list>
53-
})
5443
public abstract class ArrowType {
5544

5645
public static abstract class PrimitiveType extends ArrowType {
@@ -93,13 +82,48 @@ private ArrowTypeID(byte flatbufType) {
9382
}
9483
}
9584

96-
@JsonIgnore
9785
public abstract ArrowTypeID getTypeID();
98-
@JsonIgnore
9986
public abstract boolean isComplex();
10087
public abstract int getType(FlatBufferBuilder builder);
10188
public abstract <T> T accept(ArrowTypeVisitor<T> visitor);
10289

90+
/** The lower-case type name used as the JSON discriminator for this type. */
91+
String getNameAsString() {
92+
return getTypeID().name().toLowerCase(Locale.ROOT);
93+
}
94+
95+
/** Serializes this type to JSON, as a {@code {"name": ..., ...}} object. */
96+
void writeJson(JsonGenerator generator) throws IOException {
97+
generator.writeStartObject();
98+
generator.writeStringField("name", getNameAsString());
99+
writeJsonFields(generator);
100+
generator.writeEndObject();
101+
}
102+
103+
/** Writes the type-specific fields (everything except the discriminator) to JSON. */
104+
void writeJsonFields(JsonGenerator generator) throws IOException {
105+
}
106+
107+
/** Constructs an ArrowType from its JSON discriminator name and parsed fields. */
108+
static ArrowType getArrowType(String name, java.util.Map<String, Object> fields) {
109+
switch (name) {
110+
<#list arrowTypes.types as type>
111+
<#assign typeName = type.name?remove_ending("_")>
112+
case "${type.name?remove_ending("_")?lower_case}":
113+
<#if type.name == "Decimal">
114+
return new Decimal(
115+
JsonValues.asInt(fields.get("precision")),
116+
JsonValues.asInt(fields.get("scale")),
117+
fields.get("bitWidth") == null ? 128 : JsonValues.asInt(fields.get("bitWidth")));
118+
<#else>
119+
return new ${typeName}(<#list type.fields as field><#if field.valueType??>JsonValues.parseEnum(${field.valueType}.class, fields.get("${field.name}"))<#elseif field.type == "int">JsonValues.asInt(fields.get("${field.name}"))<#elseif field.type == "boolean">JsonValues.asBoolean(fields.get("${field.name}"))<#elseif field.type == "int[]">JsonValues.asIntArray(fields.get("${field.name}"))<#else>JsonValues.asString(fields.get("${field.name}"))</#if><#if field_has_next>, </#if></#list>);
120+
</#if>
121+
</#list>
122+
default:
123+
throw new IllegalArgumentException("Unknown type: " + name);
124+
}
125+
}
126+
103127
/**
104128
* to visit the ArrowTypes
105129
* <code>
@@ -170,11 +194,10 @@ public static class ${name} extends <#if type.complex>ComplexType<#else>Primitiv
170194
171195
<#if type.name == "Decimal">
172196
// Needed to support golden file integration tests.
173-
@JsonCreator
174197
public static Decimal createDecimal(
175-
@JsonProperty("precision") int precision,
176-
@JsonProperty("scale") int scale,
177-
@JsonProperty("bitWidth") Integer bitWidth) {
198+
int precision,
199+
int scale,
200+
Integer bitWidth) {
178201
179202
return new Decimal(precision, scale, bitWidth == null ? 128 : bitWidth);
180203
}
@@ -192,13 +215,11 @@ public Decimal(int precision, int scale) {
192215
this(precision, scale, 128);
193216
}
194217
195-
<#else>
196-
@JsonCreator
197218
</#if>
198219
public ${type.name}(
199220
<#list type.fields as field>
200221
<#assign fieldType = field.valueType!field.type>
201-
@JsonProperty("${field.name}") ${fieldType} ${field.name}<#if field_has_next>, </#if>
222+
${fieldType} ${field.name}<#if field_has_next>, </#if>
202223
</#list>
203224
) {
204225
<#list type.fields as field>
@@ -212,6 +233,29 @@ public Decimal(int precision, int scale) {
212233
return ${field.name};
213234
}
214235
</#list>
236+
237+
@Override
238+
void writeJsonFields(JsonGenerator generator) throws IOException {
239+
<#list type.fields as field>
240+
<#if field.valueType??>
241+
generator.writeStringField("${field.name}", ${field.name} == null ? null : ${field.name}.name());
242+
<#elseif field.type == "int">
243+
generator.writeNumberField("${field.name}", ${field.name});
244+
<#elseif field.type == "boolean">
245+
generator.writeBooleanField("${field.name}", ${field.name});
246+
<#elseif field.type == "int[]">
247+
generator.writeArrayFieldStart("${field.name}");
248+
if (${field.name} != null) {
249+
for (int value : ${field.name}) {
250+
generator.writeNumber(value);
251+
}
252+
}
253+
generator.writeEndArray();
254+
<#else>
255+
generator.writeStringField("${field.name}", ${field.name});
256+
</#if>
257+
</#list>
258+
}
215259
</#if>
216260
217261
@Override
@@ -312,6 +356,11 @@ public int getType(FlatBufferBuilder builder) {
312356
return storageType().getType(builder);
313357
}
314358

359+
@Override
360+
void writeJson(JsonGenerator generator) throws IOException {
361+
storageType().writeJson(generator);
362+
}
363+
315364
public String toString() {
316365
return "ExtensionType(" + extensionName() + ", " + storageType().toString() + ")";
317366
}

vector/src/main/java/module-info.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,11 @@
3535
exports org.apache.arrow.vector.util;
3636
exports org.apache.arrow.vector.validate;
3737

38-
opens org.apache.arrow.vector.types.pojo to
39-
com.fasterxml.jackson.databind;
40-
41-
requires com.fasterxml.jackson.annotation;
4238
requires com.fasterxml.jackson.core;
43-
requires com.fasterxml.jackson.databind;
44-
requires com.fasterxml.jackson.datatype.jsr310;
4539
requires flatbuffers.java;
4640
requires jdk.unsupported;
4741
requires org.apache.arrow.format;
4842
requires org.apache.arrow.memory.core;
49-
requires org.apache.commons.codec;
5043
requires org.slf4j;
5144

5245
uses org.apache.arrow.vector.compression.CompressionCodec.Factory;

vector/src/main/java/org/apache/arrow/vector/extension/OpaqueType.java

Lines changed: 69 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616
*/
1717
package org.apache.arrow.vector.extension;
1818

19-
import com.fasterxml.jackson.core.JsonProcessingException;
20-
import com.fasterxml.jackson.databind.JsonNode;
21-
import com.fasterxml.jackson.databind.ObjectMapper;
22-
import com.fasterxml.jackson.databind.node.ObjectNode;
19+
import com.fasterxml.jackson.core.JsonFactory;
20+
import com.fasterxml.jackson.core.JsonGenerator;
21+
import com.fasterxml.jackson.core.JsonParser;
22+
import com.fasterxml.jackson.core.JsonToken;
23+
import java.io.IOException;
24+
import java.io.StringWriter;
2325
import java.util.Collections;
2426
import java.util.Objects;
2527
import java.util.concurrent.atomic.AtomicBoolean;
@@ -72,6 +74,7 @@
7274
*/
7375
public class OpaqueType extends ArrowType.ExtensionType {
7476
private static final AtomicBoolean registered = new AtomicBoolean(false);
77+
private static final JsonFactory JSON_FACTORY = new JsonFactory();
7578
public static final String EXTENSION_NAME = "arrow.opaque";
7679
private final ArrowType storageType;
7780
private final String typeName;
@@ -128,41 +131,85 @@ public boolean extensionEquals(ExtensionType other) {
128131

129132
@Override
130133
public String serialize() {
131-
ObjectMapper mapper = new ObjectMapper();
132-
ObjectNode object = mapper.createObjectNode();
133-
object.put("type_name", typeName);
134-
object.put("vendor_name", vendorName);
135-
try {
136-
return mapper.writeValueAsString(object);
137-
} catch (JsonProcessingException e) {
134+
try (StringWriter writer = new StringWriter();
135+
JsonGenerator generator = JSON_FACTORY.createGenerator(writer)) {
136+
generator.writeStartObject();
137+
generator.writeStringField("type_name", typeName);
138+
generator.writeStringField("vendor_name", vendorName);
139+
generator.writeEndObject();
140+
generator.flush();
141+
return writer.toString();
142+
} catch (IOException e) {
138143
throw new RuntimeException("Could not serialize " + this, e);
139144
}
140145
}
141146

142147
@Override
143148
public ArrowType deserialize(ArrowType storageType, String serializedData) {
144-
ObjectMapper mapper = new ObjectMapper();
145-
JsonNode object;
149+
java.util.Map<String, Object> object;
146150
try {
147-
object = mapper.readTree(serializedData);
148-
} catch (JsonProcessingException e) {
151+
object = parseObject(serializedData);
152+
} catch (IOException e) {
149153
throw new InvalidExtensionMetadataException("Extension metadata is invalid", e);
150154
}
151-
JsonNode typeName = object.get("type_name");
152-
JsonNode vendorName = object.get("vendor_name");
153-
if (typeName == null) {
155+
if (object == null) {
156+
throw new InvalidExtensionMetadataException("Extension metadata is invalid");
157+
}
158+
if (!object.containsKey("type_name")) {
154159
throw new InvalidExtensionMetadataException("typeName is missing");
155160
}
156-
if (vendorName == null) {
161+
if (!object.containsKey("vendor_name")) {
157162
throw new InvalidExtensionMetadataException("vendorName is missing");
158163
}
159-
if (!typeName.isTextual()) {
164+
Object typeName = object.get("type_name");
165+
Object vendorName = object.get("vendor_name");
166+
if (!(typeName instanceof String)) {
160167
throw new InvalidExtensionMetadataException("typeName should be string, was " + typeName);
161168
}
162-
if (!vendorName.isTextual()) {
169+
if (!(vendorName instanceof String)) {
163170
throw new InvalidExtensionMetadataException("vendorName should be string, was " + vendorName);
164171
}
165-
return new OpaqueType(storageType, typeName.asText(), vendorName.asText());
172+
return new OpaqueType(storageType, (String) typeName, (String) vendorName);
173+
}
174+
175+
/** Parses a JSON object into a generic map, or returns null if the root is not an object. */
176+
private static java.util.Map<String, Object> parseObject(String serializedData)
177+
throws IOException {
178+
try (JsonParser parser = JSON_FACTORY.createParser(serializedData)) {
179+
if (parser.nextToken() != JsonToken.START_OBJECT) {
180+
return null;
181+
}
182+
java.util.Map<String, Object> result = new java.util.LinkedHashMap<>();
183+
while (parser.nextToken() != JsonToken.END_OBJECT) {
184+
String name = parser.currentName();
185+
result.put(name, readScalarOrSkip(parser));
186+
}
187+
return result;
188+
}
189+
}
190+
191+
private static Object readScalarOrSkip(JsonParser parser) throws IOException {
192+
JsonToken token = parser.nextToken();
193+
switch (token) {
194+
case VALUE_STRING:
195+
return parser.getValueAsString();
196+
case VALUE_NUMBER_INT:
197+
return parser.getLongValue();
198+
case VALUE_NUMBER_FLOAT:
199+
return parser.getDoubleValue();
200+
case VALUE_TRUE:
201+
return Boolean.TRUE;
202+
case VALUE_FALSE:
203+
return Boolean.FALSE;
204+
case VALUE_NULL:
205+
return null;
206+
case START_OBJECT:
207+
case START_ARRAY:
208+
parser.skipChildren();
209+
return new Object();
210+
default:
211+
return null;
212+
}
166213
}
167214

168215
@Override

0 commit comments

Comments
 (0)