Let's suppose there is a class hierarchy in some third-party library. One of those classes has #JsonValue annotation. It results in serialization/deserialization of only one field. Is it possible to ignore #JsonValue annotation without subclassing and get all object fields serialized?
You can define a MixIn and override the #JsonValue via property or getter
- depending on where the annotation has been set in the third-party library.
Assuming we have a class Pojo with a #JsonValue annotated member prop:
class Pojo {
#JsonValue
private String prop;
private String prop2;
// getters and setters
}
We then define an abstract MixIn class like follows, overriding the particular member or method:
abstract class MixIn {
#JsonValue(false) // either annotating members
private String prop;
#JsonValue(false) // or annotating methods
abstract String getProp();
}
This can be added to the ObjectMapper like follows:
objectMapper.addMixIn(Pojo.class, MixIn.class);
Instead of just prop, now both prop and prop2 are serialized.
Related
This is my class:
#Builder
#Value
public class A {
int id;
String name;
#NonNull String lastName;
}
The Lombok #Builder will add the all args constructor.
I need to deserialise a string into a POJO object.
I created the following Jackson mixin containing all three properties:
public abstract class AMixin {
public AMixin(#JsonProperty("name") String name,
#JsonProperty("id") int id,
#JsonProperty("lastName") String lastName) {
}
#JsonProperty("name")
abstract String getName();
#JsonProperty("id")
abstract int getId();
#JsonProperty("lastName")
abstract String getLastName();
}
I deserialise like this:
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(A.class, AMixin.class);
String ss = "{\"id\":1,\"name\":\"some name\",\"lastName\":\"some name\"}\n";
A c = mapper.readValue(ss, A.class);
}
but I get this error:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.bla.test.A` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{"id":1,"name":"some name","lastName":"some name"}
"; line: 1, column: 2]
I found the answer.
Add lombok.config file with content:
lombok.anyConstructor.addConstructorProperties=true
The issue here is that Jackson expects a no-argument constructor or some other configured way of creating the object.
As of Lombok v1.18.14, the #Jacksonized annotation can be added to the class with the #Builder annotation to automatically configure the builder to be used for Jackson deserialization.
#Jacksonized
#Builder
#Value
public class A {
int id;
String name;
#NonNull String lastName;
}
The Lombok documentation for #Jacksonized describes this annotation in more detail:
The #Jacksonized annotation is an add-on annotation for #Builder and #SuperBuilder. It automatically configures the generated builder class to be used by Jackson's deserialization. It only has an effect if present at a context where there is also a #Builder or a #SuperBuilder; a warning is emitted otherwise.
[...]
In particular, the annotation does the following:
Configure Jackson to use the builder for deserialization using #JsonDeserialize(builder=_Foobar_._Foobar_Builder[Impl].class)) on the class (where Foobar is the name of the annotated class, and Impl is added for #SuperBuilder). (An error is emitted if such an annotation already exists.)
Copy Jackson-related configuration annotations (like #JsonIgnoreProperties) from the class to the builder class. This is necessary so that Jackson recognizes them when using the builder.
Insert #JsonPOJOBuilder(withPrefix="") on the generated builder class to override Jackson's default prefix "with". If you configured a different prefix in lombok using setterPrefix, this value is used. If you changed the name of the build() method using using buildMethodName, this is also made known to Jackson.
For #SuperBuilder, make the builder implementation class package-private.
Note: This issue has nothing to do with the usage of a mixin, which can be verified by moving Jackson configuration from the mixin to the class itself and observing that the issue is still present.
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?
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.
TL;DR: using lombok and avoiding a default constructor
make immutable data class using #Value
annotate all your fields with #JsonProperty("name-of-property")
add lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty to your lombok.config to copy those to generated constructors
create an all-args constructor annotated with #JsonCreator
example:
#Value
#AllArgsConstructor(onConstructor_ = #JsonCreator)
class School {
#JsonProperty("schoolId")
String schoolId;
#JsonProperty("schoolName")
String schoolName;
}
long answer
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;
}
I am working on conversion from JSON using Jackson 2, I work with application code that I cannot interfere into (Tacton Software). In there I have a class like that:
public class Quotation {
private Reference currency; // This type does not have deafult constructor
private List<QuotationItem> quotationItems = new ArrayList(); // In this there are abstract fields
// Getters, setters and other fields omitted for clarity
}
So basically I tried already to extend Quotation class like that:
class JSONQuotation extends Quotation{
#JsonSetter("currency")
public void notSetCurrency(JSONReference ref){
this.setCurrency(null);
}
#JsonSetter("items")
#Override
public void setItems (List<QuotationItem> item){
}
}
So there are two types of annotation I used:
Ignore setter for field I don't want to deserialize
Override setter
Anyway I'm still getting errors from ObjectMapper, of course, I had set MixIns
mapper.addMixIn(Quotation.class,JSONQuotation.class);
Anybody can help me with this or recommend some other deserializer ?
I have class with #JsonIgnore-d field:
public class MyClass {
...
#JsonIgnore
private SomeType myfield;
...
// getters & setters
}
Is it possible to configure ObjectWriter so that it includes myfield during serialization even though being ingored?
Rationale: MyClass is serialized in many places and only in single specific one I want to have myfield.
It is possible to configure ObjectMapper to disable a JsonIgnore function. Following are some possible solution you can try with:
1.
Disable JsonIgnore function for a particular annotated field.
You can create a custom JsonIgnore annotation and a custom JacksonAnnotationIntrospector to remove the annotation from mapper context.
Following are the ideas:
Annotate #MyJsonIgnore to the fields that should be ignored while serialization:
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class MyClass {
#MyJsonIgnore
private SomeType myField;
}
#MyJsonIgnore is a simple custom annotation that wrap #JsonIgnore:
#Retention(RetentionPolicy.RUNTIME)
#JacksonAnnotationsInside
#JsonIgnore
public #interface MyJsonIgnore {
}
A custom JacksonAnnotationIntrospector is implemented to remove #MyJsonIgnore from mapper context:
public class DisablingMyJsonIgnoreIntrospector extends JacksonAnnotationIntrospector {
#Override
public boolean isAnnotationBundle(final Annotation ann) {
if (ann.annotationType().equals(MyJsonIgnore.class)) {
return false;
} else {
return super.isAnnotationBundle(ann);
}
}
After that, you can set the introspector on a ObjectMapper during configuration:
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new DisablingMyJsonIgnoreIntrospector());
It results that the fields annotated with #MyJsonIgnore can be marshaled properly.
2.
Disable JsonIgnore function for the mapper
Your can create a custom JacksonAnnotationIntrospector and override hasIgnoreMarker method to always return false:
public static class DisablingJsonIgnoreIntrospector extends JacksonAnnotationIntrospector {
#Override
public boolean hasIgnoreMarker(final AnnotatedMember m) {
return false;
}
}
hasIgnoreMarker is to check whether there is annotation to ignore json property. Return false will disable the JsonIngore function.
3.
Disable all annotations and specify what kinds of properties are auto-detected for a given ObjectMapper:
final ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.USE_ANNOTATIONS);
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
This simply disable all annotations.
Hope this can help.
One more option is to use the AnnotationIntrospector.nopInstance() if you want to avoid all Jackson's annotations in your pojo including #JsonIgnore e.g.
JsonMapper.builder().annotationIntrospector(AnnotationIntrospector.nopInstance()).build()...
or
new ObjectMapper().setAnnotationIntrospector(AnnotationIntrospector.nopInstance())...
class A{
private B b;
//other properties
//getter setter
}
// unable to add jsonIgnore in this class due to dependency in other module
class B {
int id;
String name;
String defname;
}
I want to ignore defname in class A JSON building by codehaus.jackson API.
I need {a:{id:value,name:value}}.
You can use Mixin for this purpose.
First Create an abstract class with JsonIgnore annotation:
abstract class MixIn{
#JsonIgnore
abstract String getDefname(); }
Then use it as below. (Be sure your getter name of defName field as getDefName() in your B class or change it in Mixin class as yours.)
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn( B.class, MixIn.class );
objectMapper.writeValue( System.out, new A() );
This prints:
{"b":{"id":1,"name":"Sercan"}}