11
votes

Here's the code

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.ToString;

public class Main {
    public static void main(String[] args) throws Exception {
        Fields f1 = new Fields(1);
        System.out.println(f1);

        ObjectMapper mapper = new ObjectMapper();
        String str = mapper.writeValueAsString(f1);
        System.out.println(str);

        Fields f2 = mapper.readValue(str, Fields.class);
        System.out.println(f2);
    }

    @Data
    @ToString
    public static class Fields {
        private final long value1;
        private final long value2;

        public Fields(@JsonProperty("blah") long value) {
            this.value1 = value++;
            this.value2 = value++;
            System.out.println(this);
        }
    }
}

Output

Main.Fields(value1=1, value2=2)
Main.Fields(value1=1, value2=2)
{"value1":1,"value2":2}
Main.Fields(value1=0, value2=1)
Main.Fields(value1=1, value2=2)

My questions are:

  • Why did jackson modify private final fields that do not have setters after finish constructing it? If this is intended, how do I turn it off?
  • If jackson is able to set fields directly, why is it required that I annotate the constructor with @JsonProperty? (Removing @JsonProperty from Fields results in error; and I didn't even need to annotate with correct properties)

Thank you

2

2 Answers

14
votes

Why is it required that I annotate the constructor with @JsonProperty?

It's not. What is required is an accessible constructor. You can either have a parameterless constructor

public Fields() {
    this.value1 = 0;
    this.value2 = 0;
    System.out.println("cons: " + this);
}

(that necessarily initializes the fields since they are final) or you can have a constructor that Jackson will try to resolve based on the declared @JsonProperty name. Note that JsonProperty#required is false by default.

Why did jackson modify private final fields that do not have setters after finish constructing it? If this is intended, how do I turn it off?

Because it can. It thus allows you to use immutable types with deserialization. There is no built-in way that I know of through which you can disable this feature.

14
votes

Why did jackson modify private final fields that do not have setters after finish constructing it? If this is intended, how do I turn it off?

You can set MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS property to false (it is true by default) when configuring your mapper.

Example:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Temp {

private static final String RAW = "{\"value1\": \"aabbcc\",\"value2\":\"zzzzz\"}";

public static void main(String[] args) throws Exception {
    System.out.println(new ObjectMapper().readValue(RAW, TestClass.class));

    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // you will receive UnrecognizedPropertyException without this line
    mapper.configure(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS, false);

    System.out.println(mapper.readValue(RAW, TestClass.class));
}

public static class TestClass {

    private final String value1;
    private final String value2;

    @JsonCreator
    public static TestClass createTestClass(
            @JsonProperty("value1") String value1,
            @JsonProperty("blah") String value2) {

        return new TestClass(value1, value2);
    }

    private TestClass(String value1, String value2) {
        this.value1 = value1;
        this.value2 = value2;
    }

    public String getValue1() {
        return value1;
    }

    public String getValue2() {
        return value2;
    }

    @Override
    public String toString() {
        return "TestClass{" + "value1=" + value1 + ", value2=" + value2 + '}';
    }
}

}

Output:

TestClass{value1=aabbcc, value2=zzzzz}
TestClass{value1=aabbcc, value2=null}