Jackson Deserialize Record with default value for optional fields
Asked Answered
K

2

5

Assume a JSON structure with multiple optional fields. With classes, you can do something like

    public static final class Foo {
        @JsonProperty("x")
        private int x = 1;

        @JsonProperty("y")
        private int y = 2;

        @JsonProperty("z")
        private int z = 3;
        
    }

which defines default values for the fields in case it is not present in the provided json. Can this be done with records as well?

    public record Foo(int x, int y, int z) {

    }

Constructor overloading is obviously not an option, and as far as I know you could only have a single @JsonCreator annotation anyway.

A custom deserializer should do the trick, but is there any other way, like an annotation that provides a default value to use in the constructor of the record in case it is not provided in the json?

Keramic answered 27/5, 2022 at 11:17 Comment(3)
Java records are fairly new, and as a result, the version of Jackson that you are using matters here. What version of Jackson are you using?Rhondarhondda
I am using 2.13.3 @RhondarhonddaKeramic
Huh, forgive me, but it appears that I was mistaken. Apparently, that is not a feature currently capable in Jackson. Sorry to have misled you.Rhondarhondda
F
7

Jackson does not support defining default values for nulls.
There is no annotation to set default value.
You can set default value only on java class level.
There is open Jackson issue for that functionality.

Solution is to define only one constructor with properties initialization logic in case nulls. Record is immutable, fields filling performs only via constructor. Only in the constructor, you can define default values for record fields.

public record Foo(Integer x, Integer y, Integer z) {
    public Foo(Integer x, Integer y, Integer z) {
        this.x = x == null ? 1 : x;
        this.y = y == null ? 2: y;
        this.z = z == null ? 3: z;
    }
}

Unit test:

    @Test
    public void test() throws Exception {
        int xDefault = 1;
        int yDefault = 2;
        int zDefault = 3;

        String json = "{ \"x\": 11, \"y\":22, \"z\":33 }";
        ObjectMapper objectMapper = new ObjectMapper();
        Foo foo = objectMapper.reader().readValue(json, Foo.class);

        Assert.assertEquals(11, (int) foo.x());
        Assert.assertEquals(22, (int) foo.y());
        Assert.assertEquals(33, (int) foo.z());

        String json2 = "{ \"x\": 11, \"y\":22}";
        Foo foo2 = objectMapper.reader().readValue(json2, Foo.class);

        Assert.assertEquals(11, (int) foo2.x());
        Assert.assertEquals(22, (int) foo2.y());
        Assert.assertEquals(zDefault, (int) foo2.z());

        String json3 = "{ }";
        Foo foo3 = objectMapper.reader().readValue(json3, Foo.class);

        Assert.assertEquals(xDefault, (int) foo3.x());
        Assert.assertEquals(yDefault, (int) foo3.y());
        Assert.assertEquals(zDefault, (int) foo3.z());
    }
Favian answered 29/5, 2022 at 23:45 Comment(1)
Eugene's answer worked for me, I had this issue in a record with a boolean property, creating a custom constructor with the Boolean type instead of the primitive boolean solved it. Also, the issue related to Records is here : github.com/FasterXML/jackson-future-ideas/issues/67Miquelmiquela
W
1

You could override the record's getters to achieve some kind of default value logic:

public record Foo(String requiredValue, Integer optionalValue) {

    @Override
    public Integer optionalValue() {
        return optionalValue == null ? 42 : optionalValue;
    }

}

When you want to provide default values for only some of the record's fields, this might be more convenient than overriding the canonical constructor.

Write answered 15/10, 2022 at 11:13 Comment(1)
nice idea but didnt work for meBrockwell

© 2022 - 2024 — McMap. All rights reserved.