1. 概述
本文将深入探讨如何在 Java 中将 JSON 数据转换为 Apache Avro 对象。Avro 是一个数据序列化框架,提供丰富的数据结构和紧凑的二进制数据格式。 与其他序列化框架不同,Avro 使用 JSON 格式定义模式(schema),无需为序列化生成代码。
其核心优势在于支持模式演进(schema evolution),特别适合处理随时间变化的数据结构。同时,紧凑的数据格式使其成为高数据量处理场景的理想选择。
2. JSON 转 Avro 转换
在 Avro 中,JSON 转对象需要两个关键要素:定义数据结构的模式(schema)和转换机制(本文通过 convertJsonToAvro()
方法实现)。
模式定义数据格式(包括字段名和类型),而转换方法则利用该模式将 JSON 转换为 Avro 对象。
2.1. 实现转换方法
首先添加 Maven 依赖:
<dependency>
<groupId>org.apache.avro</groupId>
<artifactId>avro</artifactId>
<version>1.12.0</version>
</dependency>
接着创建 JSON 需遵循的模式:
private static final String SCHEMA_JSON = """
{
"type": "record",
"name": "Customer",
"namespace": "com.baeldung.avro",
"fields": [
{"name": "name", "type": "string"},
{"name": "age", "type": "int"},
{"name": "email", "type": ["null", "string"], "default": null}
]
}""";
然后实现转换方法。转换过程包含三个核心组件:模式、根据模式解析 JSON 的解码器(decoder)、创建 Avro 对象的 DatumReader
:
GenericRecord convertJsonToAvro(String json) throws IOException {
try {
DatumReader<GenericRecord> reader = new GenericDatumReader<>(schema);
Decoder decoder = DecoderFactory.get().jsonDecoder(schema, json);
return reader.read(null, decoder);
} catch (IOException e) {
throw new IOException("Error converting JSON to Avro", e);
}
}
解码器验证 JSON 输入是否符合模式结构,DatumReader
则结合模式和解码器创建 GenericRecord
对象,无需生成特定类即可表示数据。
转换流程如下:
- ✅ 根据模式验证 JSON 输入
- ✅ 解码器按模式结构解析 JSON
- ✅
DatumReader
创建包含数据的GenericRecord
- ✅ JSON 中缺失但模式中定义的字段自动赋予默认值
⚠️ 联合类型(union types)的特殊处理:当字段定义为联合类型(如 ["null", "string"]
),JSON 必须显式指定类型。例如字符串值需包装为 {"string": "value"}
,而非直接使用 "value"
。
2.2. 测试 JSON 转 Avro 转换
测试完整 JSON 对象转换:
@Test
void whenValidJsonInput_thenConvertsToAvro() throws IOException {
JsonToAvroConverter converter = new JsonToAvroConverter();
String json = "{\"name\":\"John Doe\",\"age\":30,\"email\":{\"string\":\"john.doe@example.com\"}}";
GenericRecord record = converter.convertJsonToAvro(json);
assertEquals("John Doe", record.get("name").toString());
assertEquals(30, record.get("age"));
assertEquals("john.doe@example.com", record.get("email").toString());
}
注意 email
字段的特殊格式:这是 Avro 联合类型的语法要求。
测试含 null
字段的 JSON 转换:
@Test
void whenJsonWithNullableField_thenConvertsToAvro() throws IOException {
JsonToAvroConverter converter = new JsonToAvroConverter();
String json = "{\"name\":\"John Doe\",\"age\":30,\"email\":null}";
GenericRecord record = converter.convertJsonToAvro(json);
assertEquals("John Doe", record.get("name").toString());
assertEquals(30, record.get("age"));
assertNull(record.get("email"));
}
该测试验证了可空字段(email
定义为 ["null", "string"]
)正确处理 null
值。
3. 高级用法
基础转换覆盖多数场景,但实际应用常需更复杂操作。下面探讨两个关键场景:处理 JSON 数组和二进制序列化。
3.1. 处理 JSON 数组
批量处理多个 JSON 对象是常见需求。 扩展转换器以支持数组:
List<GenericRecord> convertJsonArrayToAvro(String jsonArray) throws IOException {
List<GenericRecord> records = new ArrayList<>();
Schema arraySchema = Schema.createArray(schema);
Decoder decoder = DecoderFactory.get().jsonDecoder(arraySchema, jsonArray);
DatumReader<List<GenericRecord>> reader = new GenericDatumReader<>(arraySchema);
List<GenericRecord> result = reader.read(null, decoder);
return result;
}
方法解析:
- 创建数组模式(基于记录模式)
- 使用 Avro 内置 JSON 解码器验证结构
- 自动处理联合类型等特殊字段
- 通过
DatumReader
一次性读取整个数组
3.2. 测试 JSON 数组处理
验证数组转换功能:
@Test
void whenJsonArray_thenConvertsToAvroList() throws IOException {
JsonToAvroConverter converter = new JsonToAvroConverter();
String jsonArray = """
[
{"name":"John Doe","age":30,"email":{"string":"john.doe@example.com"}},
{"name":"Jane Doe","age":28,"email":{"string":"jane.doe@example.com"}}
]""";
List<GenericRecord> records = converter.convertJsonArrayToAvro(jsonArray);
assertEquals(2, records.size());
assertEquals("John Doe", records.get(0).get("name").toString());
assertEquals("jane.doe@example.com", records.get(1).get("email").toString());
}
⚠️ 注意:即使数组中,联合类型字段(如 email
)仍需保持 Avro JSON 格式。
3.3. 二进制序列化
JSON 转 Avro 对象虽便于处理,但实际应用常需高效存储或传输数据。Avro 的二进制序列化相比 JSON/XML 具有显著优势:
- 更紧凑的数据格式
- 更高的序列化/反序列化性能
- 内置模式演进支持
实现 GenericRecord
二进制序列化:
byte[] serializeAvroRecord(GenericRecord record) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
DatumWriter<GenericRecord> writer = new GenericDatumWriter<>(schema);
BinaryEncoder encoder = EncoderFactory.get().binaryEncoder(outputStream, null);
writer.write(record, encoder);
encoder.flush();
return outputStream.toByteArray();
}
3.4. 测试二进制序列化
验证序列化功能:
@Test
void whenSerializingAvroRecord_thenProducesByteArray() throws IOException {
String json = """
{"name":"John Doe","age":30,"email":{"string":"john.doe@example.com"}}
""" ;
JsonToAvroConverter converter = new JsonToAvroConverter();
GenericRecord record = converter.convertJsonToAvro(json);
byte[] bytes = converter.serializeAvroRecord(record);
assertNotNull(bytes);
assertTrue(bytes.length > 0);
}
测试流程:
- JSON →
GenericRecord
- 通过
serializeAvroRecord()
二进制序列化 - 验证输出为非空字节数组
4. 总结
本文系统介绍了 Java 中 JSON 转 Avro 对象的完整流程,涵盖基础转换、数组处理、二进制序列化和验证等核心场景。通过实战示例展示了 Avro 的关键特性:
- ✅ 模式驱动的数据转换
- ✅ 联合类型与可空字段处理
- ✅ 批量数据处理能力
- ✅ 高效二进制序列化
这些技术为 Java 应用中处理 JSON 和 Avro 提供了坚实基础,特别适合需要高性能数据序列化和模式演进的系统。