Jackson/Hibernate, Meta Get Methods and Serialization

Hibernate jackson when does serialization take place

First of all It is not recommended to call your DAO logic in the controller, you better have a service which calls the DAO logic to separate the concerns and be able to keep each business logic in a transaction.

The book object gets passed in to newBook(@RequestBody @Valid Book
book) is it already serialized (java.io.Serializable)? or
serialization takes place at bookDao.saveOrUpdate(book)

This is not serialization, this is the desirialzation, serialization convert java object to serialization format (like json) and deserialization does the opposite, the deserialization takes place after the request is mapped by the servlet mapper and before triggering the api, that is why if there is a problem with the deserialization the api will not be triggered.

Does the serialization takes place before the book is persistent

object?

Hibernate is an ORM, which maps java object model to relational model, so what will be persisted is a jave model, deserialization happens on the controller level

In above example when does Jackson serialize or deserialize take
place?

I already answered the deserialzation part , for serialization it takes place after your controller returns the result in case you have @ResponseBody on your api method or your controller is a RestController

Jackson serialization issue. Only first object of the same entity serializes well

You need to use com.fasterxml.jackson.annotation.JsonIdentityInfo annotation and declare it for Restaurant class:

@JsonIdentityInfo(generator = ObjectIdGenerators.None.class)
class Restaurant {

private int id;
...
}

See also:

  • Jackson/Hibernate, meta get methods and serialisation
  • Jackson JSON - Using @JsonIdentityReference to always serialise a POJO by id

Jackson custom serialization and deserialization

Idea with Unit annotation is really good. We need to only add custom com.fasterxml.jackson.databind.ser.BeanSerializerModifier and com.fasterxml.jackson.databind.ser.BeanPropertyWriter implementations. Let's create first our annotation class:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Unit {
String value();
}

POJO model could look like below:

class Pojo {

private User user = new User();
private Food food = new Food();
private House house = new House();

// getters, setters, toString
}

class User {

@Unit("m")
private int height = 10;

// getters, setters, toString
}

class Food {

@Unit("C")
private int temperature = 50;

// getters, setters, toString
}

class House {

@Unit("m")
private int width = 10;

// getters, setters, toString
}

Having all of that we need to customise property serialisation:

class UnitBeanSerializerModifier extends BeanSerializerModifier {

@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
for (int i = 0; i < beanProperties.size(); ++i) {
final BeanPropertyWriter writer = beanProperties.get(i);
AnnotatedMember member = writer.getMember();
Unit units = member.getAnnotation(Unit.class);
if (units != null) {
beanProperties.set(i, new UnitBeanPropertyWriter(writer, units.value()));
}
}
return beanProperties;
}
}

class UnitBeanPropertyWriter extends BeanPropertyWriter {

private final String unit;

protected UnitBeanPropertyWriter(BeanPropertyWriter base, String unit) {
super(base);
this.unit = unit;
}

@Override
public void serializeAsField(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception {
gen.writeFieldName(_name);
final Object value = (_accessorMethod == null) ? _field.get(bean) : _accessorMethod.invoke(bean, (Object[]) null);
gen.writeString(value + " " + unit);
}
}

Using SimpleModule we can register it and use with ObjectMapper:

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;

public class JsonApp {

public static void main(String[] args) throws Exception {
SimpleModule unitModule = new SimpleModule();
unitModule.setSerializerModifier(new UnitBeanSerializerModifier());

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(unitModule);

Pojo pojo = new Pojo();
System.out.println(mapper.writeValueAsString(pojo));
}
}

prints:

{
"user" : {
"height" : "10 m"
},
"food" : {
"temperature" : "50 C"
},
"house" : {
"width" : "10 m"
}
}

Of course, you need to test it and handle all corner cases but above example shows general idea. In the similar way we can handle deserialisation. We need to implement custom BeanDeserializerModifier and one custom UnitDeserialiser:

class UnitBeanDeserializerModifier extends BeanDeserializerModifier {

@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
JsonDeserializer<?> jsonDeserializer = super.modifyDeserializer(config, beanDesc, deserializer);
if (jsonDeserializer instanceof StdScalarDeserializer) {
StdScalarDeserializer scalarDeserializer = (StdScalarDeserializer) jsonDeserializer;
Class scalarClass = scalarDeserializer.handledType();
if (int.class == scalarClass) {
return new UnitIntStdScalarDeserializer(scalarDeserializer);
}
}
return jsonDeserializer;
}
}

and example deserialiser for int:

class UnitIntStdScalarDeserializer extends StdScalarDeserializer<Integer> {

private StdScalarDeserializer<Integer> src;

public UnitIntStdScalarDeserializer(StdScalarDeserializer<Integer> src) {
super(src);
this.src = src;
}

@Override
public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String value = p.getValueAsString();
String[] parts = value.split("\\s+");
if (parts.length == 2) {
return Integer.valueOf(parts[0]);
}
return src.deserialize(p, ctxt);
}
}

Above implementation is just an example and should be improved for other primitive types. We can register it in the same way using simple module. Reuse the same as for serialisation:

unitModule.setDeserializerModifier(new UnitBeanDeserializerModifier());

Stakover flow error with Jackson applied on JPA Entities to generate JSON

It looks like you use Yasson project not Jackson. In that case you should use @JsonbTransient annotation. See documentation:

By default, JSONB ignores properties with a non public access. All
public properties - either public fields or non public fields with
public getters are serialized into JSON text.

Excluding properties can be done with a @JsonbTransient annotation.
Class properties annotated with @JsonbTransient annotation are ignored
by JSON Binding engine. The behavior is different depending on where
@JsonbTransient annotation is placed.

See also:

  • Circular reference issue with JSON-B

Infinite recursion with Jackson on intermediate table

When unhandled bidirectional relationship occurs, Jackson faces infinite recursion.

I tried to use @JsonIgnore, @JsonManagedReference and @JsonBackReference on the Written class

You need to use @JsonManagedReference and @JsonBackReference annotations separately to prevent these cycles between Book and Written. A side note, transient has nothing to do with the persistence but the serialization. JPA works with the @Transient annotation.

public class Book implements Serializable {

@OneToMany(mappedBy = "book", cascade = CascadeType.ALL)
@JsonBackReference
private Set<Written> written= new HashSet<>();

...
}
public class Written implements Serializable {

@Id
@ManyToOne
@JoinColumn(name = "idBook")
@JsonManagedReference
private Book book;

...
}

Important: Don't send database entities through REST (probably what you are up to do). Better create a DAO object without bidirectional relationship and map entities into DAOs. There are several libraries able to do that: I highly recommend MapStruct, however ModelMapper is also an option. If there is a lower number of such entities, using constructors/getters/setters would be enough.

Avoid Jackson serialization on non fetched lazy objects

I finally found the solution! thanks to indybee for giving me a clue.

The tutorial Spring 3.1, Hibernate 4 and Jackson-Module-Hibernate have a good solution for Spring 3.1 and earlier versions. But since version 3.1.2 Spring have his own MappingJackson2HttpMessageConverter with almost the same functionality as the one in the tutorial, so we don't need to create this custom HTTPMessageConverter.

With javaconfig we don't need to create a HibernateAwareObjectMapper too, we just need to add the Hibernate4Module to the default MappingJackson2HttpMessageConverter that Spring already have and add it to the HttpMessageConverters of the application, so we need to:

  1. Extend our spring config class from WebMvcConfigurerAdapter and override the method configureMessageConverters.

  2. On that method add the MappingJackson2HttpMessageConverter with the Hibernate4Module registered in a previus method.

Our config class should look like this:

@Configuration
@EnableWebMvc
public class MyConfigClass extends WebMvcConfigurerAdapter{

//More configuration....

/* Here we register the Hibernate4Module into an ObjectMapper, then set this custom-configured ObjectMapper
* to the MessageConverter and return it to be added to the HttpMessageConverters of our application*/
public MappingJackson2HttpMessageConverter jacksonMessageConverter(){
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();

ObjectMapper mapper = new ObjectMapper();
//Registering Hibernate4Module to support lazy objects
mapper.registerModule(new Hibernate4Module());

messageConverter.setObjectMapper(mapper);
return messageConverter;

}

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//Here we add our custom-configured HttpMessageConverter
converters.add(jacksonMessageConverter());
super.configureMessageConverters(converters);
}

//More configuration....
}

If you have an xml configuration, you don't need to create your own MappingJackson2HttpMessageConverter either, but you do need to create the personalized mapper that appears in the tutorial (HibernateAwareObjectMapper), so your xml config should look like this:

<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="com.pastelstudios.json.HibernateAwareObjectMapper" />
</property>
</bean>
</mvc:message-converters>

Hope this answer be understandable and helps someone find the solution for this problem, any questions feel free to ask!



Related Topics



Leave a reply



Submit