77

What happens if I annotate a constructor parameter using @JsonProperty but the Json doesn't specify that property. What value does the constructor get?

How do I differentiate between a property having a null value versus a property that is not present in the JSON?

Francois Bourgeois
  • 3,650
  • 5
  • 30
  • 41
Gili
  • 86,244
  • 97
  • 390
  • 689
  • Related question: [How to tell Jackson to ignore a field during serialization if its value is null?](http://stackoverflow.com/questions/11757487/how-to-tell-jackson-to-ignore-a-field-during-serialization-if-its-value-is-null) – sleske Jul 02 '13 at 10:20

6 Answers6

58

Summarizing excellent answers by Programmer Bruce and StaxMan:

  1. Missing properties referenced by the constructor are assigned a default value as defined by Java.

  2. You can use setter methods to differentiate between properties that are implicitly or explicitly set. Setter methods are only invoked for properties with explicit values. Setter methods can keep track of whether a property was explicitly set using a boolean flag (e.g. isValueSet).

Community
  • 1
  • 1
Gili
  • 86,244
  • 97
  • 390
  • 689
44

What happens if I annotate a constructor parameter using @JsonProperty but the Json doesn't specify that property. What value does the constructor get?

For questions such as this, I like to just write a sample program and see what happens.

Following is such a sample program.

import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.ObjectMapper;

public class JacksonFoo
{
  public static void main(String[] args) throws Exception
  {
    ObjectMapper mapper = new ObjectMapper();

    // {"name":"Fred","id":42}
    String jsonInput1 = "{\"name\":\"Fred\",\"id\":42}";
    Bar bar1 = mapper.readValue(jsonInput1, Bar.class);
    System.out.println(bar1);
    // output: 
    // Bar: name=Fred, id=42

    // {"name":"James"}
    String jsonInput2 = "{\"name\":\"James\"}";
    Bar bar2 = mapper.readValue(jsonInput2, Bar.class);
    System.out.println(bar2);
    // output:
    // Bar: name=James, id=0

    // {"id":7}
    String jsonInput3 = "{\"id\":7}";
    Bar bar3 = mapper.readValue(jsonInput3, Bar.class);
    System.out.println(bar3);
    // output:
    // Bar: name=null, id=7
  }
}

class Bar
{
  private String name = "BLANK";
  private int id = -1;

  Bar(@JsonProperty("name") String n, @JsonProperty("id") int i)
  {
    name = n;
    id = i;
  }

  @Override
  public String toString()
  {
    return String.format("Bar: name=%s, id=%d", name, id);
  }
}

The result is that the constructor is passed the default value for the data type.

How do I differentiate between a property having a null value versus a property that is not present in the JSON?

One simple approach would be to check for a default value post deserialization processing, since if the element were present in the JSON but had a null value, then the null value would be used to replace any default value given the corresponding Java field. For example:

import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility;
import org.codehaus.jackson.annotate.JsonMethod;
import org.codehaus.jackson.map.ObjectMapper;

public class JacksonFooToo
{
  public static void main(String[] args) throws Exception
  {
    ObjectMapper mapper = new ObjectMapper().setVisibility(JsonMethod.FIELD, Visibility.ANY);

    // {"name":null,"id":99}
    String jsonInput1 = "{\"name\":null,\"id\":99}";
    BarToo barToo1 = mapper.readValue(jsonInput1, BarToo.class);
    System.out.println(barToo1);
    // output:
    // BarToo: name=null, id=99

    // {"id":99}
    String jsonInput2 = "{\"id\":99}";
    BarToo barToo2 = mapper.readValue(jsonInput2, BarToo.class);
    System.out.println(barToo2);
    // output:
    // BarToo: name=BLANK, id=99

    // Interrogate barToo1 and barToo2 for 
    // the current value of the name field.
    // If it's null, then it was null in the JSON.
    // If it's BLANK, then it was missing in the JSON.
  }
}

class BarToo
{
  String name = "BLANK";
  int id = -1;

  @Override
  public String toString()
  {
    return String.format("BarToo: name=%s, id=%d", name, id);
  }
}

Another approach would be to implement a custom deserializer that checks for the required JSON elements. And yet another approach would be to log an enhancement request with the Jackson project at http://jira.codehaus.org/browse/JACKSON

Programmer Bruce
  • 64,977
  • 7
  • 99
  • 97
  • 4
    "Proof is in the pudding". I agree, what better way to learn than try. – Brad Christie Nov 30 '11 at 05:10
  • 2
    That project link is dead. – jwv Oct 28 '15 at 14:26
  • 3
    Any info on a implementing a custom deserializer that can check for non-existent properties and assign a default value according to expected type? – speedynomads Mar 24 '16 at 10:37
  • 1
    The problem with this approch is when you have to use Boolean data type as we can not set random value except true or false – newday Aug 18 '17 at 20:32
  • 1
    > For questions such as this, I like to just write a sample program and see what happens. — this is not always a great idea, the current behavior of the library may explicitly be marked as unstable in the documentation, for instance. It's almost always better to check the documentation if possible. – zale Oct 29 '19 at 15:59
17

In addition to constructor behavior explained in @Programmer_Bruce's answer, one way to differentiate between null value and missing value is to define a setter: setter is only called with explicit null value. Custom setter can then set a private boolean flag ("isValueSet" or whatever) if you want to keep track of values set.

Setters have precedence over fields, in case both field and setter exist, so you can "override" behavior this way as well.

StaxMan
  • 113,358
  • 34
  • 211
  • 239
  • Say you use the same class for serializion and deserialization. How do you tell the serializer to omit property `value` if `isValueSet` is false? – Gili Dec 01 '11 at 04:34
  • That would require custom serializer, there is no way to specify sort guard flags. Although one could implement such an extension obviously, using annotation-based approach. – StaxMan Dec 01 '11 at 16:42
3

I'm thinking of using something in the style of an Option class, where a Nothing object would tell me if there is such a value or not. Has anyone done something like this with Jackson (in Java, not Scala, et al)?

Roberto
  • 987
  • 1
  • 9
  • 21
  • 4
    I'm using Guava's `Optional` for optional properties. If the field is _missing_ from the JSON the property does not get assigned. I've solved this by initializing the fields using `Optional.absent()` but that means I can't use `final` fields which is a bit of a limitation. – Rob Fletcher Jul 03 '14 at 11:12
0

(My answer might be useful to some people finding this thread via google, even if it doesn't answer OPs question)
If you are dealing with primitive types which are omittable, and you do not want to use a setter like described in the other answers (for example if you want your field to be final), you can use box objects:

public class Foo {
    private final int number;
    public Foo(@JsonProperty Integer number) {
        if (number == null) {
            this.number = 42; // some default value
        } else {
            this.number = number;
        }
    }
}

this doesn't work if the JSON actually contains null, but it can be sufficient if you know it will only contain primitives or be absent

Felk
  • 7,720
  • 2
  • 35
  • 65
0

another option is to validate the object after deserialization either manually or via frameworks such java bean validation or, if you are using spring, the spring validation support.

abdel
  • 643
  • 1
  • 5
  • 13
  • 1
    This does not work. It won't allow you to differentiate between a property having a null value versus a property that is not present in the JSON. – Gili Jul 26 '18 at 17:10
  • I think if the field is nullable in the domain/dto then it doesn't matter , both a missing json field and a present json field with null value will translate into a null domain field. If the domain field is not nullable , validation will then catch both cases of missing json field and present json with null value. You might also want to play with the @Jsoniignoreproperties(ignoreunknown) to detect/fail on encountering unknown json fields – abdel Jul 27 '18 at 18:16
  • 1
    It matters. I have implemented a REST API in the past where optional properties that were not specified were assigned a default value, but fields that were explicitly set to `null` were validated accordingly (e.g. assigning `null` to a boolean property resulted in an error). Similarly, empty strings are not equivalent to default values for Strings. The point is to avoid validating every possible crazy value that the user can pass in and interpret their input literally. This way there are no surprises. – Gili Jul 28 '18 at 02:19