9

I'm trying to write a custom deserializer for Jackson and I want to make it generic (generic in the sense of working on any type, not as in "generics").

However I cannot seem to figure out how to get a handle to the type of the field being deserialized.

Eg, I'm looking to do something like the following:

@Override
public MyObject deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {

         Class c = <get type of current field>
         // do something with that type
         return new SubclassOfC(somedata based on c);
}

It's specifically the get type of current field part that I have struggled with.

Edit: It is the type of the java field I am interested in.

Charles
  • 50,943
  • 13
  • 104
  • 142
sksamuel
  • 16,154
  • 8
  • 60
  • 108
  • By "type of current field", do you mean the type of the JSON value (object, array, string, int, etc)? Or the type of a Java field with the same name in `MyObject` that you are mapping the JSON value into? – Russell Zahniser Jan 20 '12 at 16:44
  • The latter, the type of the Java field. – sksamuel Jan 20 '12 at 17:17

4 Answers4

4

You don't -- deserializers are registered by type, so you need to construct deserializer to know what type it is expected to deserialize.

If you do want to registered a generic deserializer, you can however make things more dynamic by implementing ContextualDeserializer. Its createContextual() method is called with BeanProperty argument, and you can check things like name of the property (which may be null, in case of root values which are not referenced by a property) and type (which is the declared type). This method can then return a new instance (do NOT modify original deserializer, since it is shared by all properties), configured with all extra information you need.

StaxMan
  • 113,358
  • 34
  • 211
  • 239
  • This was a good answer, unfortunately it seems that if you register a Deserializer with the class Enum, then Jackson does not talk to your custom class, but its built in enum deserializer. This means that I would have to register my cusom enum deserializer with every enum type in my entities. – sksamuel Jan 23 '12 at 10:19
  • 3
    Right, when registering deserializers, they apply to specific type you register it for. If you do want/need to create something to handle all enum types, you would need to instead implement `Deserializers`, register that: its `findEnumDeserializer()` callback it called every time a new deserializer is needed for an enum type. – StaxMan Jan 23 '12 at 18:57
  • How do I create a BeanProperty Object for the class that I want to deserialize? – Pablo Jomer Aug 21 '13 at 09:53
  • You don't create it, it will get passed to you. It represents a logical property from a POJO. But if you must call a method that expects it, pass `null` -- this is what is done when locating handlers for root values, ones that are not referenced via property. – StaxMan Aug 21 '13 at 17:02
2

I have solved my particular problem by adding an implementation of Deserializers to the ObjectMapper. Eg

   Deserializers d = new Deserializers.Base() {

   @Override
   public JsonDeserializer<?> findEnumDeserializer(Class<?> type, DeserializationConfig config, BeanDescription beanDesc, BeanProperty property)
                  throws JsonMappingException {
                if (property.getType().getContentType() != null)
                    return new EnumDeserializer(property.getType().getContentType().getRawClass());
                return new EnumDeserializer(property.getType().getRawClass());
            }

        };
        mapper.setDeserializerProvider(mapper.getDeserializerProvider().withAdditionalDeserializers(d));

This will return my custom EnumDeserializer instantiated for each separate Enum type.

sksamuel
  • 16,154
  • 8
  • 60
  • 108
  • 1
    saved me some time but imo don't use their enum deserializer. All kinds of horrible deprecation, simpler to write your own. – MikePatel Dec 27 '13 at 14:21
  • I just wanted to add that setting this Deserializers on the new jackson api is oh so unholy. example provided via gist : https://gist.github.com/patelm5/9268866 – MikePatel Feb 28 '14 at 10:38
1

I solved it like this.

Get current field java type...

@Override
public Enum deserialize(JsonParser jsonparser, DeserializationContext context) throws IOException, JsonProcessingException {
    System.out.println("EnumDeserializer ....");
    Field field = findField(jsonparser.getCurrentName(), jsonparser.getCurrentValue().getClass());
    Class<?> javaType = field.getType();
    return null;
}

public Field findField(String name, Class<?> c) {
    for (; c != null; c = c.getSuperclass()) {
        for (Field field : c.getDeclaredFields()) {
            if (Modifier.isStatic(field.getModifiers())) {
                continue;
            }
            if (field.getName().equals(name)) {
                return field;
            }
        }
    }
    return null;
}

Pang
  • 9,564
  • 146
  • 81
  • 122
  • 1
    Good way to do it but you forget if field is annotated with JsonProperty, the field doesn't match the 'currentName'. you should test it in your loop... – rlm Jun 01 '21 at 08:41
-1

Roughly speaking, and without exception catching and error checking...

JsonToken tok = jp.nextValue();

Field field = findField(jp.getCurrentName());

Class<?> fc = field.getType();

if(fc == int.class) {
   field.setInt(this, jp.getIntValue());
} // handle all the primitive types and String in the same way, then...
} ... else if(tok == JsonToken.START_ARRAY) {
   if(fc.isArray()) {
      // Load into an array
   } else if(Collection.class.isAssignableFrom(fc)) {
      // Load into a collection
   } else {
      // throw
   }
} else if(tok == JsonToken.START_OBJECT) {
   // Recursively create that object from the JSON stream
}

... and loop until tok is END_OBJECT. To find a of the current class by name:

Field findField(String name) {
    for(Class<?> c = getClass(); c != null; c = c.getSuperclass()) {
        for(Field field : c.getDeclaredFields()) {
            if(field.getName().equals(name)) {
                return field;
            }
        }
    }
}
Russell Zahniser
  • 16,188
  • 39
  • 30
  • 2
    Won't findField look inside the class of the deserializer and not the class that the deserializer was created to unmarshall ? – sksamuel Jan 21 '12 at 23:54