I like to make my objects immutable based on this article (Why objects must be immutable).
However, I am trying to parse an object using Jackson Object Mapper. I was initially getting JsonMappingException: No suitable constructor found for type [simple type, class ]: cannot instantiate from JSON object.
I could fix it as mentioned here, by providing a default constructor and making my fields non-final.
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
@AllArgsConstructor
// @NoArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
@Data
public class School {
@NonNull
private final String schoolId;
@NonNull
private final String schoolName;
}
What is a good programming style that I should follow to overcome this problem? Is the only way around is to make my objects mutable?
Can I use a different mapper that does not use the default constructor?
I have seen private constructor,factory method in Singleton pattern which makes sense. But when we talk of object immutability, are we also restricting object construction/instantiation when we mention private constructor and static factory methods?? Yes.
Now, it is clear that Person is not an Immutable class. This doesn't mean, an instance of Person can't be a member of another class that is (supposedly) immutable. Now, we have an immutable class, but, it does contain a setter. This setter actively changes a member of the (final) field p.
To create an immutable class in Java, you have to do the following steps. Declare the class as final so it can't be extended. Make all fields private so that direct access is not allowed. Don't provide setter methods for variables.
An object is immutable if its state cannot change after construction. Immutable objects don't expose any way for other objects to modify their state; the object's fields are initialized only once inside the constructor and never change again.
You can use a Jackson factory (method annotated with @JsonCreator) that reads fields off a map and calls your non-default constructor:
class School {
//fields
public School(String id, String name) {
this.schoolId = id;
this.schoolName = name;
}
@JsonCreator
public static School create(Map<String, Object> object) {
return new School((String) object.get("schoolId"),
(String) object.get("schoolName"));
}
//getters
}
Jackson will call the create
method with a Map
version of the json. And this effectively solves the problem.
I believe your question looks for a Jackson solution, rather than a new pattern/style.
@Value
@JsonProperty("name-of-property")
lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty
to your lombok.config
to copy those to generated constructors@JsonCreator
example:
@Value
@AllArgsConstructor(onConstructor_ = @JsonCreator)
class School {
@JsonProperty("schoolId")
String schoolId;
@JsonProperty("schoolName")
String schoolName;
}
There is an imo better alternative to a static factory method annotated with @JsonCreator
, and that is having a constructor for all Elements (as is required for immutable classes anyway). Annotate that with @JsonCreator
and also annotate all parameters with @JsonProperty
like this:
class School {
//fields
@JsonCreator
public School(
@JsonProperty("id") String id,
@JsonProperty("name") String name) {
this.schoolId = id;
this.schoolName = name;
}
//getters
}
Those are the options the @JsonCreator
annotation gives you. It describes them like this in its documentation:
- Single-argument constructor/factory method without JsonProperty annotation for the argument: if so, this is so-called "delegate creator", in which case Jackson first binds JSON into type of the argument, and then calls creator. This is often used in conjunction with JsonValue (used for serialization).
- Constructor/factory method where every argument is annotated with either JsonProperty or JacksonInject, to indicate name of property to bind to
You might not even need to explicitly specify the parameter name under some circumstances. The documentation regarding that for @JsonCreator
further states:
Also note that all JsonProperty annotations must specify actual name (NOT empty String for "default") unless you use one of extension modules that can detect parameter name; this because default JDK versions before 8 have not been able to store and/or retrieve parameter names from bytecode. But with JDK 8 (or using helper libraries such as Paranamer, or other JVM languages like Scala or Kotlin), specifying name is optional.
Alternatively this will also work nicely with lombok version 1.18.3
or up, where you can add lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty
to your lombok.config
and therefore have it copy the JsonProperty
annotations to the constructor, given that you do annotate all fields with it (which one should do anyway imo). To put the @JsonCreator
-annotation on the constructor, you can use the experimental onX feature. Using lombok's @Value
for immutable data classes, your DTO then might just look like this (untested):
@Value
//@AllArgsConstructor(onConstructor = @__(@JsonCreator)) // JDK7 or below
@AllArgsConstructor(onConstructor_ = @JsonCreator) // starting from JDK8
class School {
@JsonProperty("schoolId")
String schoolId;
@JsonProperty("schoolName")
String schoolName;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With