Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gson Serializing HashMap<Teacher, List<Student>>

I have a map where the a key's value is a list of objects. Am able to serialize the keys through builder.enableComplexMapKeySerialization(); but the values are not serialized as expected because they return a string on deserialization instead of object.

Below is the output of serialization

[{"id":31001,"name":Teacher"]}, //This is the key

[{"id":33033,"name":"student1"}, {"id":34001,"name":"student2"}]], //This is the list of values

I used the relevant TypeToken which is TypeToken<HashMap<Teacher, List<Student>>> but still the list values is returned a string on deserialization instead of object.

like image 950
bobo Avatar asked Feb 03 '13 17:02

bobo


1 Answers

JSON is made up of name/value pairs (where the value side can be a list of things). The name part of that is a string (See: http://json.org)

What you're trying to do is use an object as a name; you can't do that directly. A JSON object can't be a name for a name/value pair.

If you read the documentation for enableComplexMapKeySerialization it explains what the resulting JSON is going to be.

The JSON it produces (a Map as a JSON array) will deserialize perfectly back to your map. The following is a complete, working example (Java 7).

Note that once I deserialize from JSON back to Java, I'm iterating over the map to get the keys. This is because without equals() and hashCode() being overridden in Teacher there's no way to create a new instance of Teacher and have it work as a key (only the reference values are compared).

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;


public class App 
{
    public static void main( String[] args )
    {
        HashMap<Teacher, List<Student>> map = new HashMap<>();
        Teacher t = new Teacher("12345", "Teacher");
        Teacher t2 = new Teacher("23456", "Teacher2");
        ArrayList<Student> list = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            list.add(new Student(String.valueOf(i), "Student" + String.valueOf(i)));
        }

        map.put(t, list);
        map.put(t2, list);

        GsonBuilder builder = new GsonBuilder();

        Gson gson =
            builder.enableComplexMapKeySerialization().setPrettyPrinting().create();
        Type type = new TypeToken<HashMap<Teacher,List<Student>>>(){}.getType();
        String json = gson.toJson(map, type);
        System.out.println(json);

        System.out.println("\nAfter deserialization:");
        HashMap<Teacher, List<Student>> map2 = gson.fromJson(json, type);

        for (Teacher t3 : map2.keySet()) {
            System.out.println(t3.name);
            for (Student s2 : map2.get(t3)) {
                System.out.println("\t" + s2.name);
            }
        }
    }
}

class Teacher {
    public String id;
    public String name;

    public Teacher(String id, String name) {
        this.id = id;
        this.name = name;
    }
}

class Student {
    public String id;
    public String name;

    public Student(String id, String name) {
        this.id = id;
        this.name = name;
    }

}

Output:

[
  [
    {
      "id": "12345",
      "name": "Teacher"
    },
    [
      {
        "id": "0",
        "name": "Student0"
      },
      {
        "id": "1",
        "name": "Student1"
      },
      {
        "id": "2",
        "name": "Student2"
      }
    ]
  ],
  [
    {
      "id": "23456",
      "name": "Teacher2"
    },
    [
      {
        "id": "0",
        "name": "Student0"
      },
      {
        "id": "1",
        "name": "Student1"
      },
      {
        "id": "2",
        "name": "Student2"
      }
    ]
  ]
]

After deserialization:
Teacher2
    Student0
    Student1
    Student2
Teacher
    Student0
    Student1
    Student2

If you implement equals() and hashCode() in your Teacher class, you would then be able to use a new instance of Teacher to retrieve things from the map:

class Teacher {

    public String id;
    public String name;

    public Teacher(String id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public int hashCode()
    {
        int hash = 3;
        hash = 37 * hash + Objects.hashCode(this.id);
        hash = 37 * hash + Objects.hashCode(this.name);
        return hash;
    }

    @Override
    public boolean equals(Object obj)
    {
        if (obj == null)
        {
            return false;
        }
        if (getClass() != obj.getClass())
        {
            return false;
        }
        final Teacher other = (Teacher) obj;
        if (!Objects.equals(this.id, other.id))
        {
            return false;
        }
        if (!Objects.equals(this.name, other.name))
        {
            return false;
        }
        return true;
    }

}

Once you have that, you could do:

...
HashMap<Teacher, List<Student>> map2 = gson.fromJson(json, type);
Teacher t = new Teacher("23456", "Teacher2");
List<Student> list = map2.get(t);
...
like image 137
Brian Roach Avatar answered Oct 20 '22 07:10

Brian Roach