You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将Avro GenericRecord转换为不含Schema名称的JSON?

问题:Avro GenericRecord转JSON时嵌套类型出现全限定类名冗余

我手里有两个Avro Schema文件:

  • Event.avsc:
{ "type": "record", "namespace": "com.onemount.jobs.transform.schema.avro", "name": "Event", "fields": [ { "name": "id", "type": "string" }, { "name": "mtp_interest_submit", "type": ["null", "InterestSubmitParam"], "default": null } ] }
  • InterestSubmitParam.avsc:
{ "type": "record", "namespace": "com.onemount.jobs.transform.schema.avro", "name": "InterestSubmitParam", "fields": [ { "name": "interest", "type": { "type": "array", "items": "string" } } ] }

我正在从Kafka Confluent消费Avro消息(已经设置了specific.avro.reader=false),需要把GenericRecord转换成ObjectNode。目前用代码转换后得到的结果是这样的:

{ "id": "c8b76e58-9803-4c78-9f82-a185bda1cabf", "mtp_interest_submit": { "com.onemount.jobs.transform.schema.avro.InterestSubmitParam": { "interest": [ "fashion", "travel" ] } } }

但我想要的结果应该是去掉那个冗余的全限定类名,变成:

{ "id": "c8b76e58-9803-4c78-9f82-a185bda1cabf", "mtp_interest_submit": { "interest": [ "fashion", "travel" ] } }

我的转换代码是这样的:

GenericRecord genericRecord = ... 
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { 
    DatumWriter<GenericRecord> writer = new GenericDatumWriter<>(genericRecord.getSchema()); 
    JsonEncoder encoder = EncoderFactory.get().jsonEncoder(genericRecord.getSchema(), outputStream); 
    writer.write(genericRecord, encoder); 
    encoder.flush(); 
    return new String(outputStream.toByteArray(), StandardCharsets.UTF_8); 
}

解决方法

这个问题的根源是:Avro的默认JsonEncoder在处理联合类型(比如你的mtp_interest_submit是null和自定义记录的联合)时,会自动把自定义类型的全限定类名作为key包裹嵌套对象,用来明确联合类型的具体实现。要去掉这个冗余,有两种简单的处理方式:

方法一:修改JsonEncoder配置,关闭类型信息输出

Avro的EncoderFactory提供了配置选项,让我们可以关闭联合类型的类型信息输出。在Avro 1.8及以上版本,只需要给jsonEncoder方法多传一个false参数(代表encodeType设为false)就行,代码改动极小:

GenericRecord genericRecord = ... 
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { 
    DatumWriter<GenericRecord> writer = new GenericDatumWriter<>(genericRecord.getSchema());
    // 第三个参数设为false,关闭联合类型的类型信息输出
    JsonEncoder encoder = EncoderFactory.get()
        .jsonEncoder(genericRecord.getSchema(), outputStream, false); 
    writer.write(genericRecord, encoder); 
    encoder.flush(); 
    return new String(outputStream.toByteArray(), StandardCharsets.UTF_8); 
}

这种方式最适合你的场景,因为你的联合类型只有null和InterestSubmitParam两个选项,关闭类型信息不会有歧义。

方法二:手动遍历GenericRecord,构建ObjectNode

如果你的Avro版本不支持上述配置,或者需要更灵活的转换逻辑,可以手动遍历GenericRecord的字段,逐个构建ObjectNode,遇到嵌套的GenericRecord就递归处理:

import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericRecord;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

private static ObjectNode convertGenericRecordToObjectNode(GenericRecord record, ObjectMapper objectMapper) {
    ObjectNode node = objectMapper.createObjectNode();
    for (String fieldName : record.getSchema().getFieldNames()) {
        Object value = record.get(fieldName);
        if (value == null) {
            node.putNull(fieldName);
            continue;
        }
        // 处理嵌套的自定义记录
        if (value instanceof GenericRecord) {
            node.set(fieldName, convertGenericRecordToObjectNode((GenericRecord) value, objectMapper));
        } else if (value instanceof GenericData.Array) {
            // 处理数组类型
            GenericData.Array<?> array = (GenericData.Array<?>) value;
            JsonNode arrayNode = objectMapper.valueToTree(array);
            node.set(fieldName, arrayNode);
        } else {
            // 处理字符串、数字等基本类型
            node.putPOJO(fieldName, value);
        }
    }
    return node;
}

// 使用示例
GenericRecord genericRecord = ...;
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode resultNode = convertGenericRecordToObjectNode(genericRecord, objectMapper);
String jsonResult = objectMapper.writeValueAsString(resultNode);

这种方式完全由你掌控转换逻辑,不用担心Avro编码器的默认行为干扰。

内容的提问来源于stack exchange,提问作者Viet Phan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 15:27:34