1

I am reading a Json string and make it to an object using scala code:

val myInstance = (new Gson()).fromJson(t, classOf[myClass])

Everything works fine until I added a hash map in "myClass" class. Then for the following Json input (friends is a hashMap)

{"pId":"P:12345","name":"Dan Brown","friends":{"{\"firstname\":\"John\",\"lastname\":\"Smith\"}":1.0}}

Then I got the following error at "firstname" attribute as below. Does anyone have any idea? Thank you very much!

15/07/29 08:43:05 ERROR Executor: Exception in task 0.0 in stage 0.0 (TID 0)
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 51
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:176)
    at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:40)
    at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:186)
    at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:145)

and here is myClass class:

public final class myClass extends implements Serializable {

    private static final long serialVersionUID = 11271827L;

    private String pId;
    private String name;
    private List<MyPet> pets = new ArrayList<MyPet>();
    private Map<MyName, Double> friends = new HashMap<MyName, Double>();
       :
   //some getter/setter here
}

and here is myName class:

public class MyName {

    private String firstname;
    private String lastname;

    /**
     * 
     * @return the Json string
     */
    public final String toJsonString() {
        return (new Gson()).toJson(this);
    }

    @Override
    public final String toString() {
        return toJsonString();
    }
}
Edamame
  • 23,718
  • 73
  • 186
  • 320
  • This `"{\"firstname\":\"John\",\"lastname\":\"Smith\"}"` is a JSON string. What are you trying to deserialize it into? Show us what `myClass` is. – Sotirios Delimanolis Jul 29 '15 at 16:01
  • Note the difference between the `name` field and the `firstname` field. See the backslash before the quotes around `firstname`? It is a string in the json input. My guess is that you are not specifying the type of the map, i.e., `HashMap`. – Chthonic Project Jul 29 '15 at 16:01
  • I have added myClass definition above. Thanks! – Edamame Jul 29 '15 at 16:07
  • Sorry, in myClass: {\"firstname\":\"John\",\"lastname\":\"Smith\"} is of MyName class in my project, which seems to cause the problem. However, the other one MyPet is a List, which is working fine. Why would List work but Map does not? – Edamame Jul 29 '15 at 16:11
  • You may have to build a custom deserializer using `GsonBuilder`: http://stackoverflow.com/a/6205384/1011791 – Chthonic Project Jul 29 '15 at 16:13

2 Answers2

0

in json, key of map should be string. in your case you are using MyName as key. so json parser using MyName#toString() to convert to string.

My suggestion is correct MyName#toString() implementation and provide a static method MyName valueOf(String) in MyName class

for example:

class MyName{
    private String firstName;
    private String lastName;

    // getters and setters

    @Override
    public String toString(){
        return firstName+" "+lastName;
    }

    public static MyName valueOf(String str){
        int space = str.lastIndexOf(' ');
        if(space==-1)
            throw new IllegalArgumentException();
        MyName myName = new MyName();
        myName.setFirstName(str.subString(0, space));
        myName.setLastName(str.subString(space+1));
        return myName;
    }
}

here valueOf and toString implementations should be compatible.

the above code assumes that there is no space in lastName

Santhosh Kumar Tekuri
  • 3,012
  • 22
  • 22
  • Thanks a lot! I added MyName class above. It already override the toString method, isn't it enough? Could you please elaborate a little more about "provide a static method MyName valueOf(String) in MyName class" ? Thank you! – Edamame Jul 29 '15 at 16:38
  • What will a `valueOf` method do? Why do you think so? – Sotirios Delimanolis Jul 29 '15 at 16:41
  • it is java convention to have static method `valueOf(String)` to construct from string representation. for example see `Integer.valueOf(String)`, `Float.valueOf(String)` etc. json parsers use this convention for converting string representation to particular object. – Santhosh Kumar Tekuri Jul 29 '15 at 16:47
  • Things don't just happen magically because of conventions. How is Gson going to use the `valueOf`? – Sotirios Delimanolis Jul 29 '15 at 16:48
  • Thanks all! I am wondering why can't I do the following if my toString() function is already output a Json string: public static MyName valueOf(String str) { return (new Gson()).fromJson(str, MyName.class); } – Edamame Jul 29 '15 at 17:59
  • yes. you can do, as long as toString and valueOf complement each other – Santhosh Kumar Tekuri Jul 29 '15 at 18:12
0

What you are doing is a bad idea. Don't provide complex object structures represented as a String in your JSON names.

Here's a solution nonetheless. Provide and register a custom JsonDeserializer

Gson gson = new GsonBuilder().registerTypeAdapter(MyName.class, new KeyDeserializer()).create();
...
class KeyDeserializer implements JsonDeserializer<MyName> {
    private Gson gson = new Gson();

    @Override
    public MyName deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        return gson.fromJson(json.getAsString(), MyName.class);
    }
}

The JsonElement will contain the JSON string (it'll be a JsonPrimitive). Calling getAsString on it will return the Java String value

{"firstname":"John","lastname":"Smith"}

We then use a separate Gson instance to deserialize that into a MyName instance with the default behavior. We need this second Gson instance, because the original one has already registered the KeyDeserializer for custom deserialization.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724