Jackson JSON User Group

JSON in Java the Right Way -- Action, Jackson!

I haven't been able to figure out how to get the target generic type information in a custom deserializer.

 

I don't see anything in DeserializationContext or TypeDeserializer that provides this information.  It may well be there, and I just don't yet know how to get it, of course.  (I actually haven't yet figured out how to get deserializeWithType to be used instead of just deserialize in my custom deserializer.)

 

For example, with a data structure such as

 

class Foo<T>
{
  public String name;
  public Class<T> clazz;
}

 

I want the type of T available during deserialization.

 

Here's where I'm at, which isn't very far.

 

import java.io.IOException;

import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.TypeDeserializer;
import org.codehaus.jackson.map.deser.StdDeserializer;
import org.codehaus.jackson.map.module.SimpleModule;
import org.codehaus.jackson.type.TypeReference;

public class CustomClassDeserializing
{
  public static void main(String[] args) throws Exception
  {
    ClassDeserializer deserializer = new ClassDeserializer();

    SimpleModule module =
        new SimpleModule("ClassDeserializerModule",
            new Version(1, 0, 0, null));
    module.addDeserializer(Class.class, deserializer);

    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(module);
   
    Foo<String> fooOfString = new Foo<String>();
    fooOfString.name = "Foo of String";
    fooOfString.clazz = String.class;
   
    String fooOfStringJson = mapper.writeValueAsString(fooOfString);
    System.out.println(fooOfStringJson);
    // output: {"name":"Foo of String","clazz":"java.lang.String"}
   
    Foo<?> fooOfStringCopy1 = mapper.readValue(fooOfStringJson, Foo.class);
    System.out.println(mapper.writeValueAsString(fooOfStringCopy1));
    // current output: {"name":"Foo of String","clazz":null}
    // clazz is of course null since that's what the deserializer currently returns
    // want deserialization without generic type info to fail
   
    TypeReference<Foo<String>> fooOfStringType = new TypeReference<Foo<String>>() {};
    Foo<String> fooOfStringCopy2 = mapper.readValue(fooOfStringJson, fooOfStringType);
    System.out.println(mapper.writeValueAsString(fooOfStringCopy2));
    // current output: {"name":"Foo of String","clazz":null}
    // clazz is of course null since that's what the deserializer currently returns
    // want to use generic type info when deserializing to Foo
  }
}

class Foo<T>
{
  public String name;
  public Class<T> clazz;
}

class ClassDeserializer extends StdDeserializer<Class<?>>
{
  ClassDeserializer()
  {
    super(Class.class);
  }

  @Override
  public Class<?> deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer)
      throws IOException, JsonProcessingException
  {
    System.out.println("ClassDeserializer.deserializeWithType called");
    return null;
  }

  @Override
  public Class<?> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException
  {
    System.out.println("ClassDeserializer.deserialize called");
    return null;
  }
}

 

So, my question is: How is the type information provided to mapper.readValue() available during custom deserialization processing?

Views: 6808

Reply to This

Replies to This Discussion

OK.  I just discovered ContextualDeserializer.  In progress...

A working solution:

 

import java.io.IOException;

import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.map.BeanProperty;
import org.codehaus.jackson.map.ContextualDeserializer;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.JsonDeserializer;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.module.SimpleModule;
import org.codehaus.jackson.type.JavaType;
import org.codehaus.jackson.type.TypeReference;

public class CustomClassDeserializing
{
  public static void main(String[] args) throws Exception
  {
    ClassDeserializer deserializer = new ClassDeserializer();

    SimpleModule module =
        new SimpleModule("ClassDeserializerModule",
            new Version(1, 0, 0, null));
    module.addDeserializer(Class.class, deserializer);

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

    Foo<String> fooOfString = new Foo<String>();
    fooOfString.name = "Foo of String";
    fooOfString.clazz = String.class;

    String fooOfStringJson = mapper.writeValueAsString(fooOfString);
    System.out.println(fooOfStringJson);
    // output: {"name":"Foo of String","clazz":"java.lang.String"}

    Foo<?> fooOfStringCopy1 = mapper.readValue(fooOfStringJson, Foo.class);
    System.out.println(mapper.writeValueAsString(fooOfStringCopy1));
    // output: {"name":"Foo of String","clazz":"java.lang.Object"}
    // This is the desired result, since appropriate type info not provided.

    TypeReference<Foo<String>> fooOfStringType = new TypeReference<Foo<String>>() {};
    Foo<String> fooOfStringCopy2 = mapper.readValue(fooOfStringJson, fooOfStringType);
    System.out.println(mapper.writeValueAsString(fooOfStringCopy2));
    // output: {"name":"Foo of String","clazz":"java.lang.String"}
  }
}

class Foo<T>
{
  public String name;
  public Class<T> clazz;
}

class ClassDeserializer extends JsonDeserializer<Class<?>> implements ContextualDeserializer<Class<?>>
{
  private Class<?> targetClass;
 
  @Override
  public JsonDeserializer<Class<?>> createContextual(DeserializationConfig config, BeanProperty property)
      throws JsonMappingException
  {
    JavaType type = property.getType();
    JavaType ofType = type.containedType(0);
    targetClass = ofType.getRawClass();
    return this;
  }

  @Override
  public Class<?> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException
  {
    return targetClass;
  }
}

Apologies for slow reply; but yes, ContextDeserializer is the thing that allows one to create contextual instances, instead of just a single generic deserializer for all instances of same property type.

ContextualDeserializer actually has even more power: the original reason for addition was that it can also access annotations for property (ones field/method has), and even annotations of the class that contains the property. This basically allows defining custom annotations that can change behavior of, say, DateDeserializers (define @JsonDateFormat annotation to use).

Now; to access parametric type you can do it another way too. When defining Module, you can define Deserializers, which is container of custom deserializers. Its findBeanDeserializer() method gets called when locating deserializers; and it is passed JavaType which has full generic type information (although you may need to dig it, when this is for custom types). SimpleModule does not allow such fidelity (to keep it "simple" -- most types are non-generic), which is why it may not be obvious that more information may be available.

 

Very good.  Thank you.
Scratch that.  This solution isn't complete because it doesn't properly accommodate the caching of deserializers performed by StdDeserializerProvider.  I'll post a complete solution with further explanations at http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-3.html.
Very cool series btw... good reading; offers a few ideas for improvements.

RSS

© 2014   Created by Tatu Saloranta.   Powered by

Badges  |  Report an Issue  |  Terms of Service