JSON Jackson - exception when serializing a polymorphic class with custom serializer - java

I'm currently migrating some code from Jackson 1.x to Jackson 2.5 json mapper and came a long a problem that wasn't there in 1.x.
This is the setup (see code below):
interface IPet
class Dog implements IPet
IPet is annotated with #JsonTypeInfo and #JsonSubTypes
class Human has a property of type IPet that is annotated with #JsonSerialize(using=CustomPetSerializer.class)
The problem:
If I serialize an instance of Dog it works as expected (also the type info is added to the json string by Jackson).
However when I serialize an instance of the Human class an exception is thrown saying:
com.fasterxml.jackson.databind.JsonMappingException: Type id handling
not implemented for type com.pet.Dog (through reference chain:
com.Human["pet"])
The serialize(...) method of the CustomPetSerializer class is not invoked (tested using a breakpoint).
The code:
IPet implementation:
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
#JsonSubTypes({
#JsonSubTypes.Type(value=Dog.class, name="dog")
//,#JsonSubTypes.Type(value=Cat.class, name="cat")
//more subtypes here...
})
public interface IPet
{
public Long getId();
public String getPetMakes();
}
Dog implementation:
public class Dog implements IPet
{
#Override
public String getPetMakes()
{
return "Wuff!";
}
#Override
public Long getId()
{
return 777L;
}
}
Human who owns a dog:
public class Human
{
private IPet pet = new Dog();
#JsonSerialize(using=CustomPetSerializer.class)
public IPet getPet()
{
return pet;
}
}
CustomPetSerializer implementation:
public class CustomPetSerializer extends JsonSerializer<IPet>
{
#Override
public void serialize(IPet value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException
{
if(value != null && value.getId() != null)
{
Map<String,Object> style = new HashMap<String,Object>();
style.put("age", "7");
gen.writeObject(style);
}
}
}
JUnit test method:
#Test
public void testPet() throws JsonProcessingException
{
ObjectMapper mapper = new ObjectMapper();
Human human = new Human();
//works as expcected
String json = mapper.writeValueAsString(human.getPet());
Assert.assertNotNull(json);
Assert.assertTrue(json.equals("{\"type\":\"dog\",\"id\":777,\"petMakes\":\"Wuff!\"}"));
//throws exception: Type id handling not implemented for type com.pet.Dog (through reference chain: com.Human["pet"])
json = mapper.writeValueAsString(human); //exception is thrown here
Assert.assertNotNull(json);
Assert.assertTrue(json.contains("\"age\":\"7\""));
}

You'll need to additionally override serializeWithType within you CustomPetSerializer because IPet is polymorphic. That's also the reason why serialize is not called. Check this related SO question that explains in detail when serializeWithType is called. For instance, your serializeWithType implementation might look something like this:
#Override
public void serializeWithType(IPet value, JsonGenerator gen,
SerializerProvider provider, TypeSerializer typeSer)
throws IOException, JsonProcessingException {
typeSer.writeTypePrefixForObject(value, gen);
serialize(value, gen, provider); // call your customized serialize method
typeSer.writeTypeSuffixForObject(value, gen);
}
which will print {"pet":{"type":"dog":{"age":"7"}}} for your Human instance.

Since Jackson 2.9 writeTypePrefixForObject() and writeTypeSuffixForObject() have been deprecated (I'm unclear why). It seems under the new approach it would now be:
#Override
public void serializeWithType(IPet value, JsonGenerator gen,
SerializerProvider provider, TypeSerializer typeSer)
throws IOException, JsonProcessingException {
WritableTypeId typeId = typeSer.typeId(value, START_OBJECT);
typeSer.writeTypePrefix(gen, typeId);
serialize(value, gen, provider); // call your customized serialize method
typeSer.writeTypeSuffix(gen, typeId);
}
So an extra line now, so not sure why it's a step forward, perhaps it's more efficient reusing the typeId object.
Source: Jackson's ObjectNode class currently in master. Not the best source but couldn't see any upgrade docs explaining what to do.

Related

How to create a customized Jackson annotation to append property on serialization

I would like to create a customized Jackson annotation like that:
#JacksonAnnotationsInside
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.TYPE})
//... Other Jackson annotations...
#interface SelfLink {
Class<? extends Entity> type();
String uriTemplate() default "";
}
I would like to use this annotation on classes or fields like this:
#Getter
#SelfLink(type = Alpha.class)
class Alpha extends Entity {
private String name;
public Alpha(Long id, String name) {
super(id);
this.name = name;
}
}
#Getter
class Beta {
private String uuid;
#SelfLink(type = Gamma.class)
private Entity data;
}
#Getter
class Gamma extends Entity {
private String stuff;
public Gamma(Long id, String stuff) {
super(id);
this.stuff = stuff;
}
}
The customized Jackson annotation would be used to append extra fields. I tried to use #JsonSerialize(using = SelfLinkSerializer.class) on the SelfLink annotation to define a Serializer:
#JsonComponent
class SelfLinkSerializer extends StdSerializer<Object> {
private Gson gson = new Gson();
private Mirror mirror = new Mirror();
#Autowired
private LinkResolver linkResolver;
#SuppressWarnings("unused")
private SelfLinkSerializer() {
this(Object.class,
new LinkResolver() {
#Override
public <T extends Entity> String resolve(Class<? extends Entity> type, T instance) {
return "always-null";
}
}
);
}
SelfLinkSerializer(Class<Object> t, LinkResolver linkResolver) {
super(t);
this.linkResolver = linkResolver;
}
#Autowired
public SelfLinkSerializer(LinkResolver linkResolver) {
this(Object.class, linkResolver);
}
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
this.serializeContentWithoutThisSerializer(value, gen, provider);
gen.writeEndObject();
}
private void serializeContentWithoutThisSerializer(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
String jsonContent = this.serializeContentViaGson(value, gen, provider);
gen.writeRaw(jsonContent);
}
private String serializeContentViaGson(Object value, JsonGenerator gen, SerializerProvider provider) {
JsonElement jsonElement = this.gson.toJsonTree(value);
SelfLink selfLink = this.mirror.on(value.getClass()).reflect().annotation(SelfLink.class).atClass();
if(selfLink != null) {
Class<? extends Entity> type = selfLink.type();
String link = value instanceof Entity ? this.linkResolver.resolve(type, (Entity) value) : null;
jsonElement.getAsJsonObject().addProperty("link", link);
}
String json = this.gson.toJson(jsonElement);
String trimmed = CharMatcher.is('{').trimFrom(json);
trimmed = CharMatcher.is('}').trimTrailingFrom(trimmed);
return trimmed;
}
}
The original idea was to start the JSON ("{"), use Jackson engine to generate the content, my serializer would resolve the link, append it on the JSON ("link":"..."), and close the JSON ("}"). But I hit a wall: I did not find a way reuse Jackson. My workaround so far is to use Gson, but that is a monstrosity. Gson do not honor Jackson annotations. I would need to create an algorithm to convert all Jackson annotations and that is a "NO-NO".
I am kinda locked on some versions of Spring Boot and Jackson (1.5.8.RELEASE and 2.8.10 respectively). I created a Gist with the whole example, and a pom.xml.
I have a secondary issue because even as a #JsonComponent, Spring is not injecting the #Autowired LinkResolver linkResolver. For now I also do not know how to make #SelfLink work on fields like on Beta class. Bur for now my question is:
How is it possible to use the Jackson API to generate the string with the JSON representation for the value param in the public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException method?

Jackson - deserializing Map implementation as HashMap

So, I have configuration
.defaultTyping(NON_FINAL)
which is writing correctly serialized json as:
"headers": [
"org.springframework.messaging.MessageHeaders",
{
Now, the problem is that MessageHeaders class is implementing Map, but also overrides put method so it throws an exception. In order to properly deserialize this I would need eaither to be able to serialize this as HashMap, so:
"headers": [
"java.util.HashMap",
{
or to be able to explicitly deserialize MessageHeaders as HashMap (since it is actually just Map.
Once more question is: how to serialize Object implementing Map as HashMap, or how to have Object implementing Map deserialized as HashMap.
Ok, i found how to do this with custom serializer (the trick was to tell serializer to use java.util.HashMap instead of the real class name):
public class MessageHeadersJsonSerializer extends JsonSerializer<MessageHeaders>{
#Override
public void serializeWithType(MessageHeaders value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException {
typeSer.writeTypePrefixForObject(value, gen, HashMap.class);
serialize(value, gen, serializers);
typeSer.writeTypeSuffixForObject(value, gen);
}
#Override
public void serialize(MessageHeaders value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
for(Map.Entry<String, Object> entry : value.entrySet()){
gen.writeFieldName(entry.getKey());
gen.writeObject(entry.getValue());
}
}
}
And then just registered it as a MixIn:
#JsonSerialize(using = MessageHeadersJsonSerializer.class)
public abstract class MessageHeadersMixIn {}
And on parent object I'm deserializing it as HashMap:
public abstract class GenericMessageMixIn<T> {
#JsonCreator
public GenericMessageMixIn(
#JsonProperty("payload") T payload,
#JsonProperty("headers") Map<String, Object> headers
){}
}
And finally all works OK!

Jackson fails to serialize Joda DateTimeFormatter

I am trying to return a JSON in my Spring MVC 3 application, but its failing for Joda DateTimeFormatter
com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class org.joda.time.format.DateTimeFormat$StyleFormatter and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: java.util.HashMap["personDay"]->mypackage.PersonDay["dateTimeFormatter"]->org.joda.time.format.DateTimeFormatter["parser"])
It looks like i might need a custom serializer for this, but i am not sure where to begin.
You can take a look here for more details and options.
Basically, you need to create a Serializer, something like:
public class ItemSerializer extends StdSerializer<Item> {
public ItemSerializer() {
this(null);
}
public ItemSerializer(Class<Item> t) {
super(t);
}
#Override
public void serialize(Item value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeNumberField("id", value.id);
jgen.writeStringField("itemName", value.itemName);
jgen.writeNumberField("owner", value.owner.id);
jgen.writeEndObject();
}
}
Then you can annotate your class with: #JsonSerialize, something like:
#JsonSerialize(using = ItemSerializer.class)
public class Item {
public int id;
public String itemName;
public User owner;
}

how to get property or field name in a custom json serializer

I have a custom JsonSerializer for a field (simplified code):
#JsonSerialize(using=Text1Serializer.class)
#JsonProperty("text1") // I need this inside the custom serializer
#Override
public String getTextOne() {
return "foo";
}
// ...
public static class Text1Serializerextends JsonSerializer<String> {
#Override
public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
// how to get "text1" here?
provider.defaultSerializeValue(value, jgen);
}
}
Since I need to serialize about ten other fields with a similar logic, that just depends on the field name, it would help me very much if I could get the property name inside the custom serializer - instead of writing ten identical serializers.
I've seen that inside the serialize() method I can get the whole object with JsonGenerator.getCurrentValue() (see this answer), but I didnt' find a way to get the field name.
I'm using Jackson 2.6
You can get field name in a custom json serializer like this:
#JsonComponent
public class Text1Serializerextends extends JsonSerializer<String> {
#Override
public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
// will output "text1"
System.out.println(jgen.getOutputContext().getCurrentName());
provider.defaultSerializeValue(value, jgen);
}
}
If you implement ContextualSerializer, this will be used to produce a "contextual" version of your serializer, i.e. one that is configured using the BeanProperty:
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException;
This should return a new instance that is customised for the given property: it doesn't have to be the same class as the non-customised serializer (although the standard Jackson implementations all seem to work that way).
You can get the current property name by calling jgen.getOutputContext().getCurrentName()
You can achieve this through Customized SerzializerProvider
public class EmptyContentSerializerProvider extends DefaultSerializerProvider {
#Override
public JsonSerializer<Object> findNullValueSerializer(BeanProperty property) throws JsonMappingException {
property.getName(); //this can extract the filed name
}
}

Getting dynamic property names with jackson

I need to create json where the objects are all structured similarly, but can contain different object names, i.e.:
"obj1":{
"field1":1,
"field2":2
}
"obj2":{
"field1":4,
"field2":5
}
"obj3":{
"field1":7,
"field2":8
}
How can I use jackson to create dynanic field names? this would be done during run time depending on input taken
You could possibly refer to this answer: Jackson dynamic property names.
Basically you can use a custom JsonSerializer.
#JsonProperty("p")
#JsonSerialize(using = CustomSerializer.class)
private Object data;
// ...
public class CustomSerializer extends JsonSerializer<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeObjectField(value.getClass().getName(), value);
jgen.writeEndObject();
}
}

Categories

Resources