简体   繁体   中英

Google Gson issue with deserializing

I have a Java class:

public class Object1 {
    private int field1;
    private String field2;
    private Object2 object2;
    private boolean field3;
}

I've saved some Object1 instance as JSON string using Gson:

    String jsonString = new Gson().toJson(object1, Object1.class);

And then I added new String field to Object1 class:

public class Object1 {
    private int field1;
    private String field2;
    private String field4;
    private Object2 object2;
    private boolean field3;
}

And now I can't to deserialize json string to Object1 instance using method:

Object1 obj1 = new Gson().fromJson(jsonString, Object1.class);

Because of Gson throws exception:

System.err: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 444 path $.c at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:224) at com.google.gson.Gson.fromJson(Gson.java:887) at com.google.gson.Gson.fromJson(Gson.java:852) at com.google.gson.Gson.fromJson(Gson.java:801) at com.google.gson.Gson.fromJson(Gson.java:773)

But why? I have JSON string without one field and it can't to be a problem. Why I can't deserialize it?

Expected a string but was BEGIN_OBJECT

field4 in your json string is not of type String, use a Json to POJO generator to create a proper object.

I like to use http://www.jsonschema2pojo.org/

@user523392 said:

the member variables have to match exactly what is given in the JSON response

This is not the case.

There are a few options for specifying how Java field names map to JSON element names.

One solution that would work for the case in the original question above is to annotate the Java class members with the @SerializedName to very explicitly declare what JSON element name it maps to.

// output: [MyObject: element=value1, elementTwo=value2]

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;

public class Foo
{
  static String jsonInput =
      "{" +
          "\"element\":\"value1\"," +
          "\"@element-two\":\"value2\"" +
      "}";

  public static void main(String[] args)
  {
    GsonBuilder gsonBuilder = new GsonBuilder();
    Gson gson = gsonBuilder.create();
    MyObject object = gson.fromJson(jsonInput, MyObject.class);
    System.out.println(object);
  }
}

class MyObject
{
  String element;

  @SerializedName("@element-two")
  String elementTwo;

  @Override
  public String toString()
  {
    return String.format(
        "[MyObject: element=%s, elementTwo=%s]",
        element, elementTwo);
  }
}

Another approach is to create a custom FieldNamingStrategy to specify how Java member names are translated to JSON element names. This example would apply the same name mapping to all Java member names. This approach would not work for the original example above, because not all of the JSON element names follow the same naming pattern -- they don't all start with '@' and some use camel case naming instead of separating name parts with '-'. An instance of this FieldNamingStrategy would be used when building the Gson instance (gsonBuilder.setFieldNamingStrategy(new MyFieldNamingStrategy());).

class MyFieldNamingStrategy implements FieldNamingStrategy
{
  // Translates the field name into its JSON field name representation.
  @Override
  public String translateName(Field field)
  {
    String name = field.getName();
    StringBuilder translation = new StringBuilder();
    translation.append('@');
    for (int i = 0, length = name.length(); i < length; i++)
    {
      char c = name.charAt(i);
      if (Character.isUpperCase(c))
      {
        translation.append('-');
        c = Character.toLowerCase(c);
      }
      translation.append(c);
    }
    return translation.toString();
  }
}

Another approach to manage how Java field names map to JSON element names is to specify a FieldNamingPolicy when building the Gson instance, eg, gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DASHES);. This also would not work with the original example, however, since it applies the same name mapping policy to all situations.

As it turned out problem was in obfuscation.

If you're not use @SerializedName annotation result JSON can be like that:

{"a":3436213,"b":"some string","c":{.............},"d":true}

We didn't use it because of it isn't DTO. In this case we using JSON just to store some unimportant internal data. But it was very funny lesson for me.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM