I have developed this webapp using Spring MVC + Hibernate.
I retrieve all my objects in a Service then return them directly to the controller. These objects generally are lazily initialized so collections are empty.
So for object User:
User
{
int idUser;
City city;
String name;
List<User> friends;
}
I return an object with just idUser and name, City and Friends are not initialized.
I want to take advantage of all my services methods (without modifying them) to provide a REST api, so if from my ApiController I request to get user with id 1, I retrieve all useful information about this user in JSON.
I tried using GSON but as soon as it tries to jsonize the city object it crashes because it has been lazily initialized. Same goes for the friends collection.
For collections it's not much of a big deal since in my api I would have another request url where you can get all friends given a user Id, but in the case of relationships with a single object (like the city in this example), I would like to return the id of the City which by definition of Lazy loading is indeed set.
How can I tell GSON to jsonize just the cityId attribute of City instead of the whole object?
Will nulling the rest of collections be a good solution so they're not converted into JSON? Is there any other way to explicitly tell GSON to ignore these attributes?
I believe you need to put your gson.toJson(...) within a transaction, in springMVC typically #Transactional at controller method where you are doing the actual serialization will do.
If you really want to skip fields or selectively serialize fields of using Gson, you can check https://stackoverflow.com/a/3341439 for gson exclusion strategy. You can skip based on Field Annotation or field name or the entire referenced class.
Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
public boolean shouldSkipClass(Class<?> clazz) {
return <class exclusion logic, return true for exclusion else false>;
}
public boolean shouldSkipField(FieldAttributes f) {
return <field exclusion logic>;
}
}).create();
Related
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.
Assuming I have a student Object
public class Student{
private long id;
private string name;
private List<string> courses
}
In a typical Get request, to get a student I send the Student object to the client.
In the case of a PUT request to modify the Student object by adding or removing a course or a POST request to create a Student record, I only need to receive from the client the student.id and the list of courses.
My question is, Can I send back the same Student object from the client in the PUT or POST request without including the name or maybe have name=null?
Or should I create a separate domain object that will be sent by the client for example:
public class StudentReponse
{
private long id;
private List<string> courses;
}
I guess my generic question is, should we separate the Request and response objects in Rest API? or for code re usability try to use the same domain objects for both directions?
should we separate the Request and response objects in Rest API?
Yes - it allows to evolve both the request and response independently.
If you follow REST practices when a create is issued, you should return 201 - created and the ID of the newly created object.
If the client requires details about it, the client can use the ID + GET to get the full resource representation.
Also consider not exposing the domain objects directly through REST.
For example having a domain entity object - it will probably have some fields related to persistence layer like database Id, createdOn, createdBy - etc. Those fields should not be sent to the client. Use a simple StudentDto (StudentResponse, StudentResponseDto whatever you want to call it) representation, which holds only those fields, which are of interest to the client.
Keeping domain and response objects separate also gives you the ability to evolve them separately or change the data representation.
Imagine you have a JPA Entity and you use both JPA and Jackson annotations in the same class - it's very messy and hard to read and maintain.
Update:
If you're using the same object to be modified by the client and sent back to the server, I guess you could reuse it and model it like this:
get
#GetMapping("/students/{id}")
public StudentDto getStudent(#PathVariable long id) {
return studentService.get(id);
}
update (replace)
#PutMapping("/students/{id}/")
public ResponseEntity updateStudent(#PathVariable long id, #RequestBody StudentDto student) {
return new ResponseEntity(studentService.replaceStudent(id, student), HttpStatus.OK);
}
OR
update (partial update)
#PostMapping("/students/{id}/")
public ResponseEntity updateStudent(#PathVariable long id, #RequestBody StudentDto student) {
return new ResponseEntity(studentService.updateStudent(id, student), HttpStatus.OK);
}
I am facing this in a project of my own, but i am sure this should be a generic issue.
I am trying to build rest services using java, spring and hibernate.
My typical entity object is modeled like below
#Entity
public class Company implements Serializable{
private Long companyId;
private String name;
private String shortDescription;
private Long logoId;
private Set<ActivityType> activityTypeList;
private String address;
private RegistrationInfo regInfo;
}
Where one object has associations with other objects, so my model object contains references to the associated objects.
I use jackson configured with spring for json-object and object-json conversion, it works perfectly, so I am good till this point.
Now I want my POST calls(which I am using for object creation) to create Company objects, only catch here is that I do not want to pass complete json for contained objects(like ActivityType and RegistrationInfo in above example), rather an id for each where the object with provided id already exists in the database. For example see this JSON snippet:
{
"companyId": 5,
"name": "test company created by rest call",
"shortDescription": "dummy description",
"logoId": 0,
"activityTypeList": [
{"activityTypeId": 1}
],
"address": "202, kardoga lane, lonavala, Maharashtra",
"regInfo": {
"registrationInfoId": 1
}
}
My rest service just needs to associate the newly created company object with the existing contained object with the provided id(inside POST call's body).
Jackson serialization creates unpopulated objects in this case, which hibernate fails to persist (for obvious reasons, there is no data just an id)
Sure i can use a DTO over here, i can get REST -> DTO and then DTO -> entity, but that seems an overhead and moreover in order to populate the Company object defined above, I will need to fetch the associated objects first from DB, attach them to my company object and then save it back. That will be an unnecessary DB trip.
Anyone has better idea on this one? Any design pattern, framework or simple tips/tricks in which i dont require a dto as well as save DB calls.
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.
I'm having issues using Jackson to map a Javascript posted JSON array of hashes (Tag).
Here is the data received by the controller #RequestBody (It is send with correct json requestheader):
[{name=tag1}, {name=tag2}, {name=tag3}]
Here is the controller:
#RequestMapping(value = "purchases/{purchaseId}/tags", method = RequestMethod.POST, params = "manyTags")
#ResponseStatus(HttpStatus.CREATED)
public void createAll(#PathVariable("purchaseId") final Long purchaseId, #RequestBody final List<Tag> entities)
{
Purchase purchase = purchaseService.getById(purchaseId);
Set<Tag> tags = purchase.getTags();
purchaseService.updatePurchase(purchase);
}
When I debug and view the 'entities' value it shows as an ArrayList of generic objects, not as a list of objects of type 'Tag' as I would expect.
How can I get jackson to map a passed array of objects to a list of obejcts of type 'Tag'?
Thanks
It sounds like Spring is not passing full type information for some reason, but rather a type-erased version, as if declaration was something like List<?> tag. I don't know what can be done to fully resolve this (may need something from Spring integration team), but one work-around is to define your own type like:
static class TagList extends ArrayList<Tag> { }
and use that instead. This will retain generic parameterization through super-type declarations so that even if Spring only passes equivalent of TagList.class, Jackson can figure out the Tag parameter.
Another way to do this is to rather obtain an array than a List, as follows:
#RequestBody Tag[] entities
Jackson requires a default constructor with no parameters on custom Objects, so you'll need to simply add a default constructor to your Tag class.
In your case simply add to your Tag class:
public Tag(){}