如何将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
需要解决的问题
- 应如何序列化BigDecimal类型?
- 如何实现上述预期的结果格式?
另外考虑过将字段类型改为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,可根据环境验证效果。
问题解答
如何序列化BigDecimal类型?
- 针对Avro的decimal逻辑类型,需先将底层fixed/bytes二进制数据转换为BigDecimal,再根据需求序列化为JSON的字符串或数值(推荐字符串格式避免浮点精度丢失)。
- 可通过自定义DatumWriter、生成POJO或修改Schema为bytes类型实现。
如何实现预期的结果格式?
- 使用自定义DatumWriter的方式,在写入字段时调用
out.writeString(decimal.toPlainString()),即可得到"positionQuantity": "850.21"的格式;若要输出数值,可调用out.writeNumber(decimal.doubleValue()),但需注意可能存在精度丢失风险。
- 使用自定义DatumWriter的方式,在写入字段时调用
内容的提问来源于stack exchange,提问作者MS13
相关产品推荐
相关产品推荐

