Jackson 将多个字段反序列化为一个字段

Deserialize multiple fields to one by Jackson

我关注json

{"val": 501, "scale": 2}

字段 scale 表示值(字段 val)小数点移动了多少。在这种情况下有 to 地方,因此结果是值 5.01.

我想将其映射到以下 class

public class ValueClass {
    @JsonProperty("val")
    @JsonDeserialize(using = ValueDeserializer.class)
    private BigDecimal value;
}

我想为此使用自定义反序列化器,但我不清楚如何从反序列化器中访问 JSON 的其他字段,然后是带注释的反序列化器。

@SuppressWarnings("serial")
class ValueDeserializer extends StdDeserializer<BigDecimal> {

    protected ValueDeserializer() {
        super(BigDecimal.class);
    }

    @Override
    public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        var val = p.readValueAs(Integer.class);
        int scale = ??; // <-- How to access "scale" field here?
        return new BigDecimal(val).scaleByPowerOfTen(-scale);
    }
}

P.S。我知道我可以 @JsonCreator 在这个简单的案例中。

public class ValueClass {
    private BigDecimal value;

    @JsonCreator
    public ValueClass(//
            @JsonProperty("val") Integer val, //
            @JsonProperty("scale") Integer scale //
    ) {
        this.value = new BigDecimal(val).scaleByPowerOfTen(-scale);
    }
}

尽管如此,实际用例要复杂得多,将逻辑保留在反序列化器中(如果可能)以便于重用会更有好处。

感谢您的帮助。

编辑 1

作为对 Chaosfire 的重播,这里对我的案例进行了更详细的说明。 我需要解析的更真实的 JSON 看起来像这样

{"val1":501, "scale":2, "val2":407, "val3":86}

scale 字段的值被共享为多个字段的分隔符。

JSON对象有大约10个类似上面的字段和50个其他相对简单的字段。我更喜欢反序列化器的原因是为了避免巨大的@JsonCreator,它主要重复输入值。

这对于您当前的设置是不可能的,您只向反序列化器提供 val 节点,但您需要整个对象才能访问 scale 节点。

由于不希望使用 @JsonCreator,您可以更改反序列化器以处理 ValueClass:

public class ValueDeserializer extends StdDeserializer<ValueClass> {

    public ValueDeserializer() {
        super(ValueClass.class);
    }

    @Override
    public ValueClass deserialize(JsonParser parser, DeserializationContext context) throws IOException {
        JsonNode node = parser.getCodec().readTree(parser);
        int scale = node.get("scale").asInt();
        ValueClass valueClass = new ValueClass();

        JavaType javaType = context.getTypeFactory().constructType(ValueClass.class);
        // Introspect the given type
        BeanDescription beanDescription = context.getConfig().introspect(javaType);
        // Find properties
        List<BeanPropertyDefinition> properties = beanDescription.findProperties();
        for (BeanPropertyDefinition property : properties) {
            String propertyName = property.getName();//get name as in json
            String propertyValue = node.get(propertyName).asText();
            BigDecimal decimal = new BigDecimal(propertyValue).scaleByPowerOfTen(-scale);
            AnnotatedMember accessor = property.getMutator();
            accessor.setValue(valueClass, decimal);
        }
        return valueClass;
    }
}

为避免手动编写 属性 名称和设置它们的值,属性从 java 类型进行内省。这种方法在很大程度上受到 的启发,您可以检查它以获取更多信息和可能的陷阱。我认为设置其余字段应该很简单,以此为基础。

简单测试:

@JsonDeserialize(using = ValueDeserializer.class)
public class ValueClass {

    @JsonProperty("val1")
    private BigDecimal value1;
    private BigDecimal val2;
    private BigDecimal val3;

    //setters and getters

    @Override
    public String toString() {
        return "ValueClass{" +
                "value1=" + value1 +
                ", val2=" + val2 +
                ", val3=" + val3 +
                '}';
    }
}

主要:

public class Main {

    public static void main(String[] args) throws Exception {
        String json = "{\"val1\":501, \"scale\":2, \"val2\":407, \"val3\":86}";
        ObjectMapper mapper = new ObjectMapper();
        ValueClass value = mapper.readValue(json, ValueClass.class);
        System.out.println(value);
    }
}

打印 - ValueClass{value1=5.01, val2=4.07, val3=0.86}