1

I have following json

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

Field scale represent how much is decimal point shifted in value (filed val). In this case there are to places, therefore result is value 5.01.

I would like to map it to following class

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

I would like to use custom deserializer for this however it is not clear to me how to access the other fields of JSON from within the deserializer then the annotated one.

@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. I know that I could you @JsonCreator in this simple case.

public class ValueClass {
    private BigDecimal value;

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

Nevertheless the real use case is much more complex and it would be more beneficial to keep the the logic inside deserializer (if possible) for easier reuse.

Thanks for help.

Edit 1

As a replay to Chaosfire here is a a bit more clarification to my case. More real JSON which I need to parse looks this

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

Value of scale filed is shared as divider for multiple fields.

The JSON object has about 10 fields like above and 50 other fields which are relatively straightforward. The reason why I would prefer the deserializer is to avoid huge @JsonCreator which would mainly repeat input values.

John Smith
  • 333
  • 2
  • 11
  • I would not recommend that you put business logic into your deserialization logic. Please keep concerns separated, it will keep code easier maintainable. – cyberbrain May 09 '22 at 08:05

1 Answers1

0

This is not possible with your current setup, you provide to the deserializer only the val node, but you need the entire object to access scale node.

Since using @JsonCreator is undesirable, you could change the deserializer to handle 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;
    }
}

To avoid manually writing property names and setting their values, properties are introspected from java type. This approach is heavily inspired by this answer, you can check it for additional info and possible pitfalls. I believe setting the rest of the fields should be straightforward, using this as a basis.

And simple test:

@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 +
                '}';
    }
}

Main:

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);
    }
}

Prints - ValueClass{value1=5.01, val2=4.07, val3=0.86}.

Chaosfire
  • 4,818
  • 4
  • 8
  • 23