Jackson - deserialization fails on circular dependencies - java

Ok, so I'm trying to test some stuffs with jackson json converter.
I'm trying to simulate a graph behaviour, so these are my POJO entities
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class ParentEntity implements java.io.Serializable
{
private String id;
private String description;
private ParentEntity parentEntity;
private List<ParentEntity> parentEntities = new ArrayList<ParentEntity>(0);
private List<ChildEntity> children = new ArrayList<ChildEntity>(0);
// ... getters and setters
}
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class ChildEntity implements java.io.Serializable
{
private String id;
private String description;
private ParentEntity parent;
// ... getters and setters
}
The tags are required in order to avoid exception on serialization.
When I try to serialize an object (both on a file or on a simple string) all works fine. However, when I try to deserialize the object, it throws an exception. This is the simple test method (try/catch omitted for simplicity)
{
// create some entities, assigning them some values
ParentEntity pe = new ParentEntity();
pe.setId("1");
pe.setDescription("first parent");
ChildEntity ce1 = new ChildEntity();
ce1.setId("1");
ce1.setDescription("first child");
ce1.setParent(pe);
ChildEntity ce2 = new ChildEntity();
ce2.setId("2");
ce2.setDescription("second child");
ce2.setParent(pe);
pe.getChildren().add(ce1);
pe.getChildren().add(ce2);
ParentEntity pe2 = new ParentEntity();
pe2.setId("2");
pe2.setDescription("second parent");
pe2.setParentEntity(pe);
pe.getParentEntities().add(pe2);
// serialization
ObjectMapper mapper = new ObjectMapper();
File f = new File("parent_entity.json");
// write to file
mapper.writeValue(f, pe);
// write to string
String s = mapper.writeValueAsString(pe);
// deserialization
// read from file
ParentEntity pe3 = mapper.readValue(f,ParentEntity.class);
// read from string
ParentEntity pe4 = mapper.readValue(s, ParentEntity.class);
}
and this is the exception thrown (of course, repeated twice)
com.fasterxml.jackson.databind.JsonMappingException: Already had POJO for id (java.lang.String) [com.fasterxml.jackson.annotation.ObjectIdGenerator$IdKey#3372bb3f] (through reference chain: ParentEntity["children"]->java.util.ArrayList[0]->ChildEntity["id"])
...stacktrace...
Caused by: java.lang.IllegalStateException: Already had POJO for id (java.lang.String) [com.fasterxml.jackson.annotation.ObjectIdGenerator$IdKey#3372bb3f]
...stacktrace...
So, what is the cause of the problem? How can I fix it? Do I need some other annotation?

Actually, it seems that the problem was with the "id" property. Because the name of the property is equal for the two different entities, there were some problems during deserialization. Don't know if it makes sense at all, but I solved the problem changing the JsonIdentityInfo tag of ParentEntity to
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = ParentEntity.class))
Of course, I also changed the scope of ChildEntity with scope=ChildEntity.class
as suggested here
I'm open to new answer and suggestions by the way.

Related

Spring JPA exception while inserting Json value (using hibernate-types-52)

I'm using hibernate-types-52 by Vlad Mihalcea together with Spring JPA to insert a POJO as a Json value into my Postgresql database.
My entity is defined this way:
#Entity
#Table(name = "hoshin_kanri")
#TypeDef(
name = "jsonb",
typeClass = JsonBinaryType.class
)
public class HKEntity {
#Id
#Column(name = "id_ai", columnDefinition = "bigint")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id_ai;
#Column(name = "id_hk", columnDefinition = "bigint")
private Integer id_hk;
#Type(type = "jsonb")
#Column(name = "hk_data", columnDefinition = "jsonb")
private HKData hk_data;
public HKEntity(Integer id_hk, HKData hk_data) {
this.id_hk = id_hk;
this.hk_data = hk_data;
}
And this is the POJO:
public class HKData {
private String name;
private Year targetYear;
private String description;
public HKData(String name, Year targetYear, String description) {
this.name = name;
this.targetYear = targetYear;
this.description = description;
}
I've defined a Repository interface to query the objects into the database:
public interface HKRepository extends CrudRepository<HKEntity, Integer> {
#Query(value = "INSERT INTO 'hk_data' VALUES :Entity", nativeQuery = true)
void test_json(#Param("Entity") HKEntity e);
}
and a test Service just to see if it's working properly:
#Service
public class HKService {
#Autowired
HKRepository hk_repository;
public String json_test() {
HKData d = new HKData("Prova", Year.now(), "Descrizione");
HKEntity e = new HKEntity(1,d);
hk_repository.test_json(e);
return "Value created";
}
}
However, i keep getting the following exception:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.ehk.rest.entity.HKEntity
I've tried many fixes suggested for this error, but i cannot understand the nature of the error itself. What is wrong with this approach? Beside a tip for fixing this, i would like to understand why this error is originated.
The error means that there's an instance of the HKEntity entity which is referenced from somewhere in the current Hibernate session, and you've neither explicitly persisted this instance, nor instructed Hibernate to persist it cascadly. It's hard to say what exactly is going on, but there are some issues with your code that might have confused either Spring Data JPA framework, or the Hibernate itself.
First, the Spring's CrudRepository interface already has a save() method, so you could use it instead of your test_json() method.
I also see no reason in inserting a Hibernate entity with a native query, and I don't even think this is a valid query. Your test_json() method tries to natively insert an HKEntity entity into the hk_data table, but the HKEntity entity should be saved into the hoshin_kanri table, according to your mapping.
So I would change your service code as follows:
public String json_test() {
HKData d = new HKData("Prova", Year.now(), "Descrizione");
HKEntity e = new HKEntity(1,d);
hk_repository.save(e);
return "Value created";
}

Parent-child relation between two objects causes JSON StackOverflowError

I am trying to achieve a parent-child relation between some objects and I ran into a bit of trouble.
In my case, I am trying to store objects within other objects (e.g. container stores multiple items or other containers with items). The tricky part is that every object in the storage should be able to tell what it's outermost parent object is. While this seems to work in my in-memory database (using h2 at the moment), trying to get a JSON representation of all my storage items gives this (I return a List<StorageUnit> ):
Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: java.util.ArrayList[0]->com.warehousing.storage.FixedContentsCase["contents"]->java.util.ArrayList[0]->com.warehousing.storage.FixedContentsCase["contents"]->...
Here are the classes:
StorageUnit
#Entity
#Inheritance
public abstract class StorageUnit {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#ManyToOne
private Location location;
protected Long parentContainerId;
// <getters & setters>
public abstract List<StorageUnit> getContents();
}
FixedContentCase
#Entity
public class FixedContentsCase extends StorageUnit {
#OneToMany
private List<Item> items;
public FixedContentsCase() {
super();
items = new ArrayList<>();
}
// <getters & setters>
#Override
public List<StorageUnit> getContents() {
// Return the case itself and its contents
List<StorageUnit> contents = new ArrayList<>(Arrays.asList(this));
for (StorageUnit item : items)
contents.addAll(item.getContents());
return contents;
}
}
Item
#Entity
public class Item extends StorageUnit {
private String description;
public Item() {
super();
this.description = "";
}
// <getters & setters>
#Override
public List<StorageUnit> getContents() {
return Arrays.asList(this);
}
}
I have tried to annotate the StorageUnit class with #JsonIgnoreProperties("parentContainerId") but it didn't work. Annotating parentContainerId with #JsonIgnore didn't help either. I also tried annotating the getters instead of the attributes themselves (as per following). Is there a way to work around this or is some kind of design change necessary? Thanks!
Using Jackson this is definitely possible by annotations like #JsonIgnore or the DTO approach BugsForBreakfast mentioned.
I created a jackson MixIn handler to allow dynamic filtering which i use to avoid the boilerplate of DTOs
https://github.com/Antibrumm/jackson-antpathfilter
The examples in the readme should show how it works and if it‘s a possible solution for you.
Your problem is that you add the storage unit itself to its list of contents, leading to infinite recursion if you traverse the tree downwards. The solution: Use a reference and only serialize the object once, using #JsonIdentityInfo and #JsonIdentityReference:
public class MyTest {
#Test
public void myTest() throws JsonProcessingException {
final FixedContentsCase fcc = new FixedContentsCase();
fcc.setId(Long.valueOf(1));
final Item item = new Item();
item.setId(Long.valueOf(2));
item.setDescription("item 1");
fcc.getItems().add(item);
final ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(fcc));
}
}
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
#JsonIdentityReference(alwaysAsId = false)
class Item extends StorageUnit {
...
}
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
#JsonIdentityReference(alwaysAsId = false)
class FixedContentsCase extends StorageUnit {
...
}
abstract class StorageUnit {
...
}

convertToDatabaseColumn when data is not being persisted?

I have implemented method AttributeConverter.convertToEntityAttribute to load json data from the db. I am not trying to persist data, but for some reason convertToDatabaseColumn is being called.
This is what happens:
1. I call a repository method
2. then a call to AttributeConverter.convertToEntityAttribute follows -> returns a list of entity Cx. Till this point everything is normal.
3. But for some reason AttributeConverter.convertToDatabaseColumn is called right after, with that same list of entity Cx as argument -> returns stringV
4.Now convertToEntityAttribute is called again with stringV as argument, which is also strange.
Could it be that a #OneToOne relation is causing this? Why is this executing convertToDatabaseColumn if I'm not persisting an entity, at least explicitly?
All of this happens just by calling a single method in one of my repository classes:
Here is the code
public interface RSTRepository extends CrudRepository<RST, Long> {
List<RST> findByDuctNameIgnoreCase(String ductName);
}
#Entity
#Table(name="r_s_t")
public class RST {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#OneToOne
#JoinColumn(name = "r_s_id")
private Rs rs;
#Column(name = "channel")
private String channelName;
...
}
#Entity
#Table(name="r_s")
public class RS {
#Id
#Column(name = "rs_id", columnDefinition = "json")
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(name = "c_x", columnDefinition = "json")
#Convert(converter = JsonToCxConverter.class)
private List<Cx> cxs;
...
}
public class Cx {
private Long someId;
private List<Long> values;
...
}
#Converter
public class JsonToCxConverterimplements AttributeConverter<List<Cx>, String>{
//this gets executed
#Override
public String convertToDatabaseColumn(List<Cx> entityAttribute) {
ObjectMapper objectMapper = new ObjectMapper();
log.info("--------------------");
return "";
}
#Override
public List<Cs> convertToEntityAttribute(String dbData) {
if (dbData == null || dbData.isEmpty()) return Collections.emptyList();
//... uses the object mapper to parse the json and return a simple object.
...
}
Like I said, this happens when calling RSTRepository.findByDuctNameIgnoreCase
Yes its really behaving like you are saying. Also when persisting RST, Converter is also called 3x.
It also called 3x when reading just RS entity, i.e. it is not caused by #OneToOne relation.
I think it is how hibernate works. It should not be a problem, you get the right data without error.
From stacktrace I see that second and third call is from AbstractRowReader.performTwoPhaseLoad().
at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.performTwoPhaseLoad(AbstractRowReader.java:241)
at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishUp(AbstractRowReader.java:209)
at org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSetProcessorImpl.java:133)
I think its something that cannot be disabled. From hibernate sources I see that entity is registered to "hydrating". I found more about it here https://stackoverflow.com/a/29538797/2044957
Another thing: This is happening only when using converter on a collection. Converter is called once if it used on single type, for example AttributeConverter<String, String>.

JSON Deserialization from ID into Object

To make sure the data being sent back and forth isn't redundant in my RESTful web service, every nested object only has it's ID serialized (A Message's User creator only has userId serialized since both the client and server will already know all the details of the user).
Serialization works perfectly, producing this:
{"messageCreatorUser":"60d01602-c04d-4a3f-bbf2-132eb0ebbfc6","messageBody":"Body", ...}
Problem: Deserialization does not produce a nested object with only its ID. The resulting deserialized nested object is null.
Here are the previously mentioned Message and User objects. Serialization "strategy" was used from the 3rd option specified here: How to serialize only the ID of a child with Jackson.
Message.java
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "messageId")
public class Message implements Serializable {
// -- Hibernate definitions omitted --
private UUID messageId;
// -----------------------------------------------------------------
// Here is what makes the serializer print out the ID specified on the class definition
#JsonIdentityReference(alwaysAsId = true)
// Here is my attempt to get the User class back when I deserialize
#JsonDeserialize(as = User.class)
#JsonSerialize(as = User.class)
private User messageCreatorUser;
// ------------------------------------------------------------------
// -- more arbitrary properties --
public Message() {
}
public Message(UUID messageId) {
this.messageId = messageId;
}
public Message(String messageId) {
this.messageId = UUID.fromString(messageId);
}
// -- getters and setters --
User.java
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "userId")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class User implements Serializable {
private UUID userId;
// -- other arbitrary properties --
public User() {
}
public User(UUID userId) {
this.userId = userId;
}
public User(String userId) {
this.userId = UUID.fromString(userId);
}
// -- getters and setters --
Expected deserialized object:
Message object =
String messageBody = "Body";
User messageCreatorUser =
UUID userId = 60d01602-c04d-4a3f-bbf2-132eb0ebbfc6;
Actual deserialized object:
Message object =
String messageBody = "Body";
User messageCreatorUser = null;
Like I said, I was hoping for a nested User object to be created with only the ID of 60d01602-c04d-4a3f-bbf2-132eb0ebbfc6
Using:
Wildfly 10.0.Final:
RESTEasy 3.0.15.Final
RESTEasy Jackson 2 Provider 3.0.15.Final
Jackson 2.6.3 (annotations, core, databind, etc)
Why do the results differ?
As the answer here (Jackson deserialize JsonIdentityReference (alwaysAsId = true)) states, using a setter deserializer works well and does not require an obnoxious custom deserializer:
Message.java
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "messageId")
public class Message implements Serializable {
private UUID messageId;
#JsonIdentityReference(alwaysAsId = true)
private User messageCreatorUser;
// -- other fields and such --
public User getMessageCreatorUser() {
return messageCreatorUser;
}
public void setMessageCreatorUser(User messageCreatorUser) {
this.messageCreatorUser = messageCreatorUser;
}
#JsonProperty("messageCreatorUser")
public void setMessageCreatorUser(String userId) {
this.messageCreatorUser = new User(userId);
}
User.java
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "userId")
public class User implements Serializable {
private UUID userId;
// -- other fields --
public User(String userId) {
this.userId = UUID.fromString(userId);
}
}
Do note that in order to use #JsonIdentityReference(alwaysAsId = true), you need to have the #JsonIdentityInfo(...) somewhere.
Not sure I understand the whole situation, but if you just want to force serialization as an id, you can use:
#JsonIdentityReference(alwaysAsId=true)
keeping in mind that deserialization does require that there is some way to resolve that id back to an instance and usually that means the actual full Object should be serialized via some other property reference.

Save entity containing gson atribute to ElasticSearch in Spring

My Spring application uses Spring-data-elasticsearch (https://github.com/spring-projects/spring-data-elasticsearch).
I would like to save following document to the elasticsearch database:
#Document(indexName = "documents", type = "customEntity", replicas = 0, shards = 5)
public class CustomEntity implements Serializable{
#Id
private String Id;
#Field(type = FieldType.String)
private String Field1;
#Field(type = FieldType.Integer)
private int Field2;
#Field(type = FieldType.Object) //not sure which annotation I should use
private JsonObject exportJSON; //gson object
...getters and setters...
}
using this way:
public class CustomEntityDao {
#Resource
ElasticsearchTemplate elasticsearchTemplate;
public void insertCustomEntity(CustomEntity entity){
IndexQuery indexQuery = new IndexQuery();
indexQuery.setId(entity.getCustomEntityId());
indexQuery.setObject(entity);
elasticsearchTemplate.index(indexQuery); //exception thrown
}
}
but I'm getting this error:
com.fasterxml.jackson.databind.JsonMappingException: JsonObject
(through reference chain: data.nosql.entities.CustomEntity
["exportJSON"]->com.google.gson.JsonObject["asString"])
I understand the problem but I don't have clue how to solve it. Any ideas please?
My guess is that jackson tries to covert your gson object to a string to be indexable by ES and it doesn't know how to do that. If you post your full stack trace it'll be more helpful, but if i have to take a guess you need to use a #JsonSerializer annotation on your "exportJSON" object.

Categories