122

I want to use jackson json library for a generic method as follows:

public MyRequest<T> tester() {
    TypeReference<MyWrapper<T>> typeRef = new TypeReference<MyWrapper<T>>();  
    MyWrapper<T> requestWrapper = (MyWrapper<T>) JsonConverter.fromJson(jsonRequest, typeRef);
    return requestWrapper.getRequest();
}
public class MyWrapper<T> {

    private MyRequest<T> request;

    public MyRequest<T> getRequest() {
        return request;
    }

    public void setRequest(MyRequest<T> request) {
        this.request = request;
    }
}
public class MyRequest<T> {
     private List<T> myobjects;
        
     public void setMyObjects(List<T> ets) {
         this.myobjects = ets;
     }

     @NotNull
     @JsonIgnore
     public T getMyObject() {
         return myobjects.get(0);
     }
}

Now the problem is that when I call getMyObject() which is inside the request object Jackson returns the nested custom object as a LinkedHashMap. Is there any way in which I specify that T object needs to be returned? For example: if I sent object of type Customer then Customer should be returned from that List?

Tom
  • 14,120
  • 16
  • 41
  • 47
techzen
  • 2,605
  • 2
  • 20
  • 21

3 Answers3

225

This is a well-known problem with Java type erasure: T is just a type variable, and you must indicate actual class, usually as Class argument. Without such information, best that can be done is to use bounds; and plain T is roughly same as 'T extends Object'. And Jackson will then bind JSON Objects as Maps.

In this case, tester method needs to have access to Class, and you can construct

JavaType type = mapper.getTypeFactory().
  constructCollectionType(List.class, Foo.class)

and then

List<Foo> list = mapper.readValue(new File("input.json"), type);
Jherico
  • 26,370
  • 8
  • 58
  • 82
StaxMan
  • 102,903
  • 28
  • 190
  • 229
  • 22
    It works : I did the following: JavaType topMost = mapper.getTypeFactory().constructParametricType(MyWrapper.class, ActualClassRuntime.class); and then did the readValue and it finally worked :) – techzen Jul 28 '11 at 03:04
  • Yes, that does work -- thanks for pointing out the method for creating generic type other than Map/Collection type! – StaxMan Jul 28 '11 at 15:47
  • 1
    @StaxMan would it better to use ClassMate for these kind of things from now? – husayt Jan 25 '14 at 13:50
  • 2
    @husayt yes, technically java-classmate lib is superior. But integrating it with Jackson is bit tricky just because Jackson's own type abstraction is integrated part of API. For long term it'd be great to figure out proper way to make Jackson use classmate code, either embedded or via dep. – StaxMan Jan 27 '14 at 23:06
  • Why isn't the object type of the object and any nested objects included in the json string somehow? – Martin Andersson Sep 26 '14 at 13:56
  • @MartinAndersson why would it? JSON is just data, and has neither type (beyond simple JSON type of Lists, Maps, number, string, boolean) nor identity. Any typing there is is interpreted by Java code; and although it could use convention to encode type (and will have to, for polymorphic types), it is not needed if caller can specify type to Map data into. – StaxMan Sep 30 '14 at 16:47
  • 1
    I feel like Jackson shouldn't be having to cover off what feel like gaps in generics, but either way, it does it very well. – Adrian Baker Jan 07 '18 at 04:36
6

'JavaType' works !! I was trying to unmarshall (deserialize) a List in json String to ArrayList java Objects and was struggling to find a solution since days.
Below is the code that finally gave me solution. Code:

JsonMarshallerUnmarshaller<T> {
    T targetClass;

    public ArrayList<T> unmarshal(String jsonString) {
        ObjectMapper mapper = new ObjectMapper();

        AnnotationIntrospector introspector = new JacksonAnnotationIntrospector();
        mapper.getDeserializationConfig()
            .withAnnotationIntrospector(introspector);

        mapper.getSerializationConfig()
            .withAnnotationIntrospector(introspector);
        JavaType type = mapper.getTypeFactory().
            constructCollectionType(
                ArrayList.class, 
                targetclass.getClass());

        try {
            Class c1 = this.targetclass.getClass();
            Class c2 = this.targetclass1.getClass();
            ArrayList<T> temp = (ArrayList<T>) 
                mapper.readValue(jsonString,  type);
            return temp ;
        } catch (JsonParseException e) {
            e.printStackTrace();
        } catch (JsonMappingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null ;
    }  
}
Wrench
  • 3,060
  • 1
  • 28
  • 46
rushidesai1
  • 123
  • 1
  • 8
  • How to initialize TargetClass ? – AZ_ Oct 09 '13 at 03:23
  • Please show me a small example. I am passing Class> target and then getting target.getClassName(). – AZ_ Oct 10 '13 at 08:08
  • 1
    Add a constructor as follows : JsonMarshallerUnmarshaller{ private Class targetClass ; JsonMarshallerUnmarshaller(Class c){ targetClass = c ; } } Make appropriate changes now to the 'unmarshal' function to use this class instead of doing getClass everywhere. – rushidesai1 Oct 11 '13 at 19:02
  • Couple of notes: code can be simplified a lot by noting that all exceptions are subtypes of `IOException` (need just one catch), and that the default annotation introspector is already `JacksonAnnotationIntrospector` -- so no need to do anything to `ObjectMapper`, just construct it and it works. – StaxMan Nov 04 '15 at 17:34
  • So this code I don't even get to compile. Got any live example to paste in instead? – Wrench Nov 22 '16 at 23:43
  • I created a [working example](https://stackoverflow.com/a/54577261/1762224) below. – Mr. Polywhirl Feb 07 '19 at 15:56
0

I modified rushidesai1's answer to include a working example.

JsonMarshaller.java

import java.io.*;
import java.util.*;

public class JsonMarshaller<T> {
    private static ClassLoader loader = JsonMarshaller.class.getClassLoader();

    public static void main(String[] args) {
        try {
            JsonMarshallerUnmarshaller<Station> marshaller = new JsonMarshallerUnmarshaller<>(Station.class);
            String jsonString = read(loader.getResourceAsStream("data.json"));
            List<Station> stations = marshaller.unmarshal(jsonString);
            stations.forEach(System.out::println);
            System.out.println(marshaller.marshal(stations));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @SuppressWarnings("resource")
    public static String read(InputStream ios) {
        return new Scanner(ios).useDelimiter("\\A").next(); // Read the entire file
    }
}

Output

Station [id=123, title=my title, name=my name]
Station [id=456, title=my title 2, name=my name 2]
[{"id":123,"title":"my title","name":"my name"},{"id":456,"title":"my title 2","name":"my name 2"}]

JsonMarshallerUnmarshaller.java

import java.io.*;
import java.util.List;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;

public class JsonMarshallerUnmarshaller<T> {
    private ObjectMapper mapper;
    private Class<T> targetClass;

    public JsonMarshallerUnmarshaller(Class<T> targetClass) {
        AnnotationIntrospector introspector = new JacksonAnnotationIntrospector();

        mapper = new ObjectMapper();
        mapper.getDeserializationConfig().with(introspector);
        mapper.getSerializationConfig().with(introspector);

        this.targetClass = targetClass;
    }

    public List<T> unmarshal(String jsonString) throws JsonParseException, JsonMappingException, IOException {
        return parseList(jsonString, mapper, targetClass);
    }

    public String marshal(List<T> list) throws JsonProcessingException {
        return mapper.writeValueAsString(list);
    }

    public static <E> List<E> parseList(String str, ObjectMapper mapper, Class<E> clazz)
            throws JsonParseException, JsonMappingException, IOException {
        return mapper.readValue(str, listType(mapper, clazz));
    }

    public static <E> List<E> parseList(InputStream is, ObjectMapper mapper, Class<E> clazz)
            throws JsonParseException, JsonMappingException, IOException {
        return mapper.readValue(is, listType(mapper, clazz));
    }

    public static <E> JavaType listType(ObjectMapper mapper, Class<E> clazz) {
        return mapper.getTypeFactory().constructCollectionType(List.class, clazz);
    }
}

Station.java

public class Station {
    private long id;
    private String title;
    private String name;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return String.format("Station [id=%s, title=%s, name=%s]", id, title, name);
    }
}

data.json

[{
  "id": 123,
  "title": "my title",
  "name": "my name"
}, {
  "id": 456,
  "title": "my title 2",
  "name": "my name 2"
}]
Mr. Polywhirl
  • 31,606
  • 11
  • 65
  • 114