0

My generic method gives casting exception.

java.util.LinkedHashMap cannot be cast to com.example.model.Student

main class

public static void main(String[] args) {
    Map<String, Student> students = SampleClass.getStudents("students.json");

    System.out.println(students.get("student1").getName());

}

SampleClass

public static <T> Map<String, T> getStudents(String filename) {
    Map<String, T> studentMap = null;

    ObjectMapper mapper = new ObjectMapper();
    try {
        studentMap = mapper.readValue(new File(filename), 
                new TypeReference<Map<String, T>>() { });
    } catch (JsonParseException e) {
        System.out.println(e.getMessage());
    } catch (JsonMappingException e) {
        System.out.println(e.getMessage());
    } catch (IOException e) {
        System.out.println(e.getMessage());
    }

    return (Map<String, T>)studentMap;
}

what I'm doing wrong?

UPDATE json file

{
"student1" :
    {
        "name" : "name1",
        "age" : 22
    },
"student2" :
    {
        "name" : "name2",
        "age" : 22
    }
}

UPDATE

found this as solution.

Community
  • 1
  • 1
Darshana
  • 2,462
  • 6
  • 28
  • 54
  • Could you indicate which line it is on? Why are you casting `studentMap` at the end of `getStudents()`? I think you may have to specify the `T`: `SampleClass.getStudents("students.json")` – clcto May 08 '14 at 16:46
  • exception throws at `System.out.println(students.get("student1").getName());` line – Darshana May 08 '14 at 16:51
  • What is your ObjectMapper? mapper.readValue() might be returning a Map> instead of Map – Syam S May 08 '14 at 17:01
  • @SyamS `org.codehaus.jackson.map.ObjectMapper` – Darshana May 08 '14 at 17:03

2 Answers2

1

The problem is with your TypeReference<Map<String, T>>. When you give T here jackson will try to infer the type. Since at this point jackson doesnt know anything about Student class it infer the "name" : "name1" to be a LinkedHashMap with key "name" and value "name1". So in-turn it creates a LinkedHashMap<String, LinkedHashMap<String>>.

As a quick solution you in the object mapper method you could use TypeReference as

studentMap = mapper.readValue(new File(filename), new TypeReference<Map<String, Student>>() { });

Since the method is getStudents it make sense to use this TypeReference but then the whole point of generics in that method is wasted.

Another approach would be to use a custom deserializer. There are many ways of doing this. You could find the details in http://wiki.fasterxml.com/JacksonPolymorphicDeserialization

For example lets make a marker interface for all possible class use a custom deserializer. Say for you scenario lets say we have an interface StudentI which will be implemented by all possible classes you type T could have. Then use the @JsonTypeInfo to give additional details about the class

Student interface

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;


@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "type")
@JsonSubTypes({
    @Type(value = Student.class, name = "student") })
public interface StudentI {

}

This means if you give something like 'type : student' in your json the mapper would use Student.class for you T.

Sample Class

import java.io.File;
import java.io.IOException;
import java.util.Map;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class SampleClass {

    public static <T extends StudentI> Map<String, T> getStudents(String filename) {
        Map<String, T> studentMap = null;

        ObjectMapper mapper = new ObjectMapper();
        try {
        studentMap = mapper.readValue(new File(filename), 
            new TypeReference<Map<String, StudentI>>() { });
        } catch (JsonParseException e) {
        System.out.println(e.getMessage());
        } catch (JsonMappingException e) {
        System.out.println(e.getMessage());
        } catch (IOException e) {
        System.out.println(e.getMessage());
        }

        return (Map<String, T>)studentMap;
    }

}

Students.json

{
"student1" :
    {
    "name" : "name1",
    "age" : 22,
    "type" : "student"
    },
"student2" :
    {
    "name" : "name2",
    "age" : 22,
    "type" : "student"
    }
}

Now your Student class should implement this interface

public class Student implements StudentI {
    private String name;
    private int age;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
    }
}

Once this is done your code would work just as expected

public static void main(String[] args) {
    Map<String, Student> students = SampleClass.getStudents("students.json");
    System.out.println(students.get("student1").getName());
}

//Output : name1
Syam S
  • 8,421
  • 1
  • 26
  • 36
  • I did this. Also add `@JsonTypeName("student")` according to http://stackoverflow.com/questions/11798394/polymorphism-in-jackson-annotations-jsontypeinfo-usage. but give error `Could not resolve type id 'name' into a subtype of [simple type, class com.example.model.StudentI]` – Darshana May 09 '14 at 10:48
  • there is no big change from your code. Just add `@JsonTypeName("student")` – Darshana May 09 '14 at 11:31
  • I updated the changes. I did not change the json file – Darshana May 09 '14 at 11:38
  • That is strange the way you mentioned it is working fine for me. However if you dont give type info in json it should throw a different error. Which version of jackson are you using? – Syam S May 09 '14 at 11:45
  • I use `1.9.2` once and `2.2.3` also. same problem – Darshana May 09 '14 at 12:02
  • I also use 2.2.3. Ok try removing `@JsonSubTypes({@JsonSubTypes.Type(value = Student.class, name = "student") })` from StudentI and `@JsonTypeName("student")` from Student class and use registerSubtypes method of ObjectMapper in SampleClass as `mapper.registerSubtypes(Student.class);` and check whether this works? – Syam S May 09 '14 at 12:12
  • 1
    Thanks Syam. As I saw in several places, your method should work. But mine didn't work. Anyway I found answer [here](http://stackoverflow.com/a/19262843/1244383) – Darshana May 10 '14 at 16:10
0

Depends what's in your json file. Looks like the mapper is returning a Student object rather than a map.

Nicolas C
  • 944
  • 1
  • 10
  • 22
  • The exception tells different story: the mapper seems to be returning a map that contains a `LinkedHashMap` instead of the expected `Student`. – fabian May 08 '14 at 16:58