Jackson passing null value to constructor - json

Considering a Java class constructor that take two parameters but allows the second to be null.
public class Category {
String name;
#JsonIgnore Category parent;
Category(String name,Category parent){this.name = name;this.parent=parent;}
}
I skipped the serialization of parent with #JsonIgnore annotation because I don't need it. Now Jackson is not capable to deserialize it because it don't find the parent property in the resulting Jason.
Is there any another solution but to define a constructor taking only the name parameter?

It is ok to have named constructor parameters that are missing -- you will simply get null instead of value. So you could just have:
#JsonCreator
public Category(#JsonProperty("name") String name, #JsonProperty("whatever") Category parent) { ... }
and whatever is found is passed. No exception will be thrown; Jackson never requires a property to exist. It just complains about things it does not recognize (unless configured not to).

Related

Error thrown: "No serializer found for class java.lang.Long..." from controller while serializing JPA entity containing lazy "many-to-one" property

I am on Spring Boot 2.0.6, where an entity pet do have a Lazy many-to-one relationship to another entity owner
Pet entity
#Entity
#Table(name = "pets")
public class Pet extends AbstractPersistable<Long> {
#NonNull
private String name;
private String birthday;
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
#JsonIdentityReference(alwaysAsId=true)
#JsonProperty("ownerId")
#ManyToOne(fetch=FetchType.LAZY)
private Owner owner;
But while submitting a request like /pets through a client(eg: PostMan), the controller.get() method run into an exception as is given below:-
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.lang.Long and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: java.util.ArrayList[0]->com.petowner.entity.Pet["ownerId"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77) ~[jackson-databind-2.9.7.jar:2.9.7]
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1191) ~[jackson-databind-2.9.7.jar:2.9.7]
Controller.get implementation
#GetMapping("/pets")
public #ResponseBody List<Pet> get() {
List<Pet> pets = petRepository.findAll();
return pets;
}
My observations
Tried to invoke explicitly the getters within owner through pet to force the lazy-loading from the javaassist proxy object of owner within the pet. But did not work.
#GetMapping("/pets")
public #ResponseBody List<Pet> get() {
List<Pet> pets = petRepository.findAll();
pets.forEach( pet -> pet.getOwner().getId());
return pets;
}
Tried as suggested by this stackoverflow answer at https://stackoverflow.com/a/51129212/5107365 to have controller call to delegate to a service bean within the transaction scope to force lazy-loading. But that did not work too.
#Service
#Transactional(readOnly = true)
public class PetServiceImpl implements PetService {
#Autowired
private PetRepository petRepository;
#Override
public List<Pet> loadPets() {
List<Pet> pets = petRepository.findAll();
pets.forEach(pet -> pet.getOwner().getId());
return pets;
}
}
It works when Service/Controller returning a DTO created out from the entity. Obviously, the reason is JSON serializer get to work with a POJO instead of an ORM entity without any mock objects in it.
Changing the entity fetch mode to FetchType.EAGER would solve the problem, but I did not want to change it.
I am curious to know why it is thrown the exception in case of (1) and (2). Those should have forced the explicit loading of lazy objects.
Probably the answer might be connected to the life and scope of that javassist objects got created to maintain the lazy objects. Yet, wondering how would Jackson serializer not find a serializer for a java wrapper type like java.lang.Long. Please do rememeber here that the exception thrown did indicate that Jackson serializer got access to owner.getId as it recognised the type of the property ownerId as java.lang.Long.
Any clues would be highly appreciated.
Edit
The edited part from the accepted answer explains the causes. Suggestion to use a custom serializer is very useful one in case if I don't need to go in DTO's path.
I did a bit of scanning through the Jackson sources to dig down to the root causes. Thought to share that too.
Jackson caches most of the serialization metadata on first use. Logic related to the use case in discussion starts at this method com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(Collection<?> value, JsonGenerator g, SerializerProvider provider). And, the respective code snippet is:-
The statement serializer = _findAndAddDynamic(serializers, cc, provider) at Line #140 trigger the flow to assign serializers for pet-level properties while skipping ownerId to be later processed through serializer.serializeWithType at line #147.
Assigning of serializers is done at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.resolve(SerializerProvider provider) method. The respective snippet is shown below:-
Serializers are assigned at line #340 only for those properties which are confirmed as final through the check at line #333.
When owner comes here, its proxied properties are found to be of type com.fasterxml.jackson.databind.type.SimpleType. Had this associated entity been loaded eagerly, the proxied properties obviously won't be there. Instead, original properties would be found with the values that are typed with final classes like Long, String, etc. (just like the pet properties).
Wondering why can't Jackson address this from their end by using the getter's type instead of using that of the proxied property. Anyway, that could be a different topic to discuss :-)
This has to do with the way that Hibernate (internally what spring boot uses for JPA by default) hydrates objects. A lazy object is not loaded until some parameter of the object is requested. Hibernate returns a proxy which delegates to the dto after firing queries to hydrate the objects.
In your scenario, loading OwnerId does not help because it is the key via which you are referencing the owner object i.e. the OwnerId is already present in the Pet object, so the hydration will not take place.
In both 1 and 2, you have not actually loaded the owner object, so when Jackson tries to serialize it at the controller level it fails. In 3 and 4, the owner object has been loaded explicitly, which is why Jackson does not run into any issues.
If you want 2 to work then load some parameter of owner, other than id, and hibernate will hydrate the object, and then jackson will be able to serialize it.
Edited Answer
The problem here is with the default Jackson serializer. This inspects the class returned and fetches the value of each attribute via reflection. In the case of hibernate entities, the object returned is a delegator proxy class in which all parameters are null, but all getters are redirected to the contained instance. When the object is inspected, the values of each attribute are still null, which is defaulted to an error as explained here
So basically, you need to tell jackson how to serialize this object. You can do so by creating a serializer class
public class OwnerSerializer extends StdSerializer<Owner> {
public OwnerSerializer() {
this(null);
}
public OwnerSerializer(Class<Owner> t) {
super(t);
}
#Override
public void serialize(Owner value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeNumberField("id", value.getId());
jgen.writeStringField("firstName", value.getFirstName());
jgen.writeStringField("lastName", value.getLastName());
jgen.writeEndObject();
}
}
And setting it as the default serializer for the object
#JsonSerialize(using = OwnerSerializer.class)
public class Owner extends AbstractPersistable<Long> {
Alternatively, you can create a new Object of type Owner from the proxy class, manually populate it and set it in the response.
It is a little roundabout, but as a general practice you should not expose your DTO's externally anyway. The controller/domain should be decoupled from the storage layer.

Exculde fields in json based on some condition

I have a list of objects of a class which I am rendering as json to the browser. Now there are certain attributes in the objects which I want to exclude from the json response if certain condition is not met.
So those attributes will be there for some objects of the list and will be absent for the other objects of that list.
How do I achieve that?
Mine is a spring boot application. Jackson is being used.
I am using Transformer for converting Entity to Bean and then ResponseEntity to convert the bean to json.
Please suggest possible solutions.
Thanks.
Make those values (which you want to be excluded) as null and then make use of the #JsonInclude annotation to suppress all null values.
#JsonInclude(Include.NON_NULL)
class Foo {
String bar;
}
you can exclude null values for specific fields too (As opposed to excluding null values for the entire object)
public class Foo {
private String field1;
private String field2;
#JsonInclude(Include.NON_NULL)
private String field3;
...
...
}
in version 2.x+ the syntax for this annotation is:
#JsonInclude(JsonSerialize.Inclusion.NON_NULL)
Or you can also set the global option:
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
In a Spring Project, objectMapper is the singleton instance of class ObjectMapper which you can either #Autowired or get from ApplicationContext

Access deserialized object from custom deserializer

All classes in my model are subclasses of a common base-class Node.
When serializing/deserializing with Jackson, I want references to other objects to be replaced by their ID. The problem is, that the ID is a combination of two values: the ID of the instance itself, and the ID of a source. I do this with a custom serializer and deserializer.
Serializing is no problem. I write JSON with a structure like this:
{"id":1,"source":2,"name":"Some record","reference":3}
But when deserilizing, I need to know the ID of the source and the ID of the referenced node, to be able to look it up in my custom deserializer.
Is it possible, to access the values of the deserialized instance, to get access to the ID of the source when deserializing the reference?
Here is what I tried so far:
public class MyDeserializer extends JsonDeserializer<Node>
{
#Override
public NodeData deserialize(...)
{
Node parent = (Node)parser.getCurrentValue();
Long id = parent.getId();
Long id = parser.getLongValue();
return NodeDataService.INSTANCE.get(source, id);
}
}
But parser.getCurrentValue() always returns a null.
My best solution so far is, to write a cooperation pair of deserializers.
The first one is annotated to the getter of the attribute source and stores
the value as per-call attribute. The second looks like this:
public class MyDeserializer extends JsonDeserializer<Node>
{
#Override
public NodeData deserialize(...)
{
Long source (Long)context.getAttribute("SOURCE");
Long id = parser.getLongValue();
return NodeDataService.INSTANCE.get(source, id);
}
}
This works, but I am asking myself, if there is an easier way to achieve this.
This question looks like it is possible, like I did it in my first attempt - but only while serializing:
Jackson How to retrieve parent bean in a custom Serializer/Deserializer
getCurrentValue() will return null as you've entered into a new JSON object but not yet set the current value. You need to look at the stack of deserialized values in the parser context.
I answered something similar here, which is the deserialization equivalent of the serialization question you linked to: Jackson JSON gives exception on collection of nested class
In summary you can get the stream context:
JsonStreamContext ourContext = p.getParsingContext();
and then repeatedly call getParent() on contexts to walk up the chain, calling getCurrentValue(). The value is set into the stream context as soon as the standard bean deserializer constructs the object.

Unexpected duplicate key error using #JsonTypeInfo property

I have a simple hierarchy of data objects, which have to be converted to JSON format. Like this:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "documentType")
#JsonSubTypes({#Type(TranscriptionDocument.class), #Type(ArchiveDocument.class)})
public class Document{
private String documentType;
//other fields, getters/setters
}
#JsonTypeName("ARCHIVE")
public class ArchiveDocument extends Document { ... }
#JsonTypeName("TRANSCRIPTIONS")
public class TranscriptionDocument extends Document { ... }
Upon JSON parsing I encounter errors like this one:
Unexpected duplicate key:documentType at position 339. , because in the generated JSON there are actually two documentType fields.
What should be changed to make JsonTypeName value appear in documentType field, without an error (eg replacing the other value)?
Jackson version is 2.2
Your code doesn't show it, but I bet you have a getter in your Document class for the documentType property. You should annotate this getter with #JsonIgnore like so:
#JsonIgnore
public String getDocumentType() {
return documentType;
}
There is an implicit documentType property associated with each subclass, so having the same property in the parent class causes it to be serialized twice.
Another option would be to remove the getter altogether, but I assume you might need it for some business logic, so the #JsonIgnore annotation might be the best option.

Serializing JPA entities to JSON using Jackson

Question regarding combination of Jackson/JPA
If there are about 20 entities in current application and I have add Jackson dependency in POM, does it mean all entities are by default ready to convert to JSON object? I saw a sample project seems only class annotated as #JsonIgnored is skipped by JSON. If so, then how can this happen, what is behind such mechanism? how JACKSON handle those entities which don't have any Jackson annotation, by default ignored or not? I've been looking for resources online but not much luck.
If only one of the 20 entities need to be mapped to JSON object, does it mean I have to add #JsonIgnore to all other 19 entities? If not, how Jackson differentiate with entity to work on?
Thanks.
Jackson and JPA don't have anything to do with each other. Jackson is a JSON parsing library and JPA is a persistence framework. Jackson can serialize almost any object - the only requirement being that the object have some kind of recognizable properties (Javabean type properties, or bare fields annotated with #JsonProperty. There is an additional requirement for deserialization, that the target type have a default (no-arg) constructor. So, for example, this is an object that Jackson can serialize:
// Class with a single Javabean property, "name"
class Person {
private String name;
public String getName() { return name ; }
public String setName(String name) { this.name = name ; }
}
And here is another:
// Class with a single field annotated with #JsonProperty
class Account {
#JsonProperty("accountNumber")
private String accountNumber;
}
And here is yet another:
#Entity
public class User {
#Id
private Long id;
#Basic
private String userName;
#Basic
#JsonIgnore
private String password;
#Basic
#JsonIgnore
private Address address;
// Constructors, getters, setters
}
The last example shows a JPA entity class - as far as Jackson is concerned it can be serialized just like any other type. But, take note of its fields: when this object is serialized into JSON two of the fields will not be included - 'password' and 'address'. This is because they have been annotated with #JsonIgnore. The #JsonIgnore annotation allows a developer to say 'Hey, its ok to serialize this object, but when you do so don't include these fields in the output'. This exclusion only occurs for the fields of this object, so for example, if you included an Address field in another class, but did not mark the field as ignorable, it would be serialized.
To prevent serialization of a type in all cases, regardless of context, use the #JsonIgnoreType annotation. When used on a type it basically means 'I dont care where this type is used, never serialize it'.
No, you don't need to add #JsonIgnore on every class and if you had tried you would have gotten a compile error, since you can't put it there. Jackson will only work on objects you give to it, it's no magic.
The Jackson documentation is easily found online, such at its project page on github or on the codehaus website.