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

如何将Avro自定义BigDecimal字段转为指定数值格式的JSON?

问题:Avro固定类型Decimal序列化为JSON格式的BigDecimal需求

问题背景

收到一份包含如下Schema的Avro文件,其中positionQuantity字段定义为带decimal逻辑类型的fixed类型,需要反序列化为BigDecimal:

{
   "name": "positionQuantity",
   "type": [
        {
           "type": "fixed",
            "name": "fixed",
            "namespace": "topLevel.payload.positionQuantity",
            "size": 10,
            "logicalType": "decimal",
            "precision": 22,
            "scale": 2
        },
        "null"
    ]
}

现有处理代码及异常结果

方案1:通过BinaryEncoder转Jackson JsonNode

此前使用以下代码处理其他字段正常,但该字段无法得到预期结果:

public static JsonNode toJsonNode(GenericRecord record) {
     try (var outStream = new ByteArrayOutputStream()) {
                var datumWriter = new GenericDatumWriter<>(record.getSchema());
                var binaryEncoder = EncoderFactory.get().directBinaryEncoder(outStream, null);
                datumWriter.write(record, binaryEncoder);
                binaryEncoder.flush();
                byte[] b = outStream.toByteArray();
                return objectMapper.readerFor(ObjectNode.class)
                    .with(new AvroSchema(record.getSchema()))
                    .readValue(b);
            }
}

处理后通过JsonNode.toString()序列化得到:

"positionQuantity":"AAAAAAAAAAFAig=="

方案2:Avro官方JsonEncoder直接序列化

使用官方推荐代码序列化:

@SneakyThrows
public static String avroToJson(GenericRecord datum, Schema schema) {
    ByteArrayOutputStream output = new ByteArrayOutputStream();
    DatumWriter<Object> writer = new GenericDatumWriter<>(schema);
    JsonEncoder encoder = EncoderFactory.get().jsonEncoder(schema, output, false);
    writer.write(datum, encoder);
    encoder.flush();
    output.flush();
    return new String(output.toByteArray(), "UTF-8");
}

得到结果:

"positionQuantity":{"topLevel.payload.positionQuantity.fixed":"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0001@Š"}

预期结果

期望得到以下两种格式之一:

"positionQuantity": "850.21" 

或

"positionQuantity": 850.21

需要解决的问题

  1. 应如何序列化BigDecimal类型?
  2. 如何实现上述预期的结果格式?

另外考虑过将字段类型改为bytes来解决自动反序列化问题,但尚未尝试。


解决方案

核心思路

Avro的fixed类型+decimal逻辑类型,默认序列化只会输出原始二进制的编码(Base64或转义字符),需要手动将二进制数据转换为BigDecimal,再序列化为JSON的字符串/数值格式。

具体实现方式

方式1:自定义Avro DatumWriter(推荐)

扩展GenericDatumWriter,在写入positionQuantity字段时手动转换二进制为BigDecimal,再输出JSON格式:

@SneakyThrows
public static String avroToJsonWithDecimal(GenericRecord datum, Schema schema) {
    ByteArrayOutputStream output = new ByteArrayOutputStream();
    JsonEncoder encoder = EncoderFactory.get().jsonEncoder(schema, output, false);
    
    GenericDatumWriter<Object> writer = new GenericDatumWriter<>(schema) {
        @Override
        protected void writeField(Object datum, Schema.Field field, Encoder out) throws IOException {
            if ("positionQuantity".equals(field.name())) {
                Object value = ((GenericRecord) datum).get(field.name());
                if (value != null && value instanceof GenericFixed) {
                    GenericFixed fixedValue = (GenericFixed) value;
                    // 将fixed二进制转换为BigDecimal
                    byte[] bytes = fixedValue.bytes();
                    BigInteger bigInteger = new BigInteger(bytes);
                    BigDecimal decimal = new BigDecimal(bigInteger).movePointLeft(2); // 对应schema的scale=2
                    // 输出字符串格式(避免浮点精度丢失)
                    out.writeString(decimal.toPlainString());
                    return;
                } else if (value == null) {
                    out.writeNull();
                    return;
                }
            }
            // 其他字段按默认逻辑处理
            super.writeField(datum, field, out);
        }
    };
    
    writer.write(datum, encoder);
    encoder.flush();
    output.flush();
    return new String(output.toByteArray(), StandardCharsets.UTF_8);
}

方式2:生成POJO后序列化(直观易维护)

使用Avro代码生成工具(如avro-maven-plugin),基于Schema生成带有BigDecimal类型的POJO类(工具会自动识别decimal逻辑类型),再用Jackson直接序列化POJO:

ObjectMapper objectMapper = new ObjectMapper();
// generatedPojo为Avro工具生成的实体类
String json = objectMapper.writeValueAsString(generatedPojo);

方式3:修改Schema为bytes类型(可选)

如果允许修改Avro Schema,将fixed类型改为bytes并保留decimal逻辑类型:

{
   "name": "positionQuantity",
   "type": [
        {
           "type": "bytes",
            "logicalType": "decimal",
            "precision": 22,
            "scale": 2
        },
        "null"
    ]
}

部分Avro-Jackson集成库(如jackson-dataformat-avro)对bytes+decimal的支持更友好,可能自动转换为BigDecimal,可根据环境验证效果。

问题解答

  1. 如何序列化BigDecimal类型?

    • 针对Avro的decimal逻辑类型,需先将底层fixed/bytes二进制数据转换为BigDecimal,再根据需求序列化为JSON的字符串或数值(推荐字符串格式避免浮点精度丢失)。
    • 可通过自定义DatumWriter、生成POJO或修改Schema为bytes类型实现。
  2. 如何实现预期的结果格式?

    • 使用自定义DatumWriter的方式,在写入字段时调用out.writeString(decimal.toPlainString()),即可得到"positionQuantity": "850.21"的格式;若要输出数值,可调用out.writeNumber(decimal.doubleValue()),但需注意可能存在精度丢失风险。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 09:55:56