Gson [java] - 解析json字符串时,我只想添加一些字符

时间:2017-03-20 03:58:25

标签: gson deserialization

我定义“回复”,“人”,“课程”类:

class Response {
    private int countA; // record the length of data
    private List<Person> data;

    // getter & setter
}

class Person {
    private String name;
    private int age;
    private int countB; // record the length of list
    private List<Course> list;

    // getter & setter
}

class Course {
    private String name;

    // getter & setter
}

我有一个json字符串(从某个服务器返回),如下所示:

{
   "countA":1,
   "data":
   {
      "name":"peter",
      "age":18,
      "countB":1,
      "list":
      {
         "name":"math"
      }
   }
}

但是当“countA”&gt; 1或“countB”&gt; 1,它会是这样的:

{
   "countA":2,
   "data":
   [                                // when countA > 1, it have this one '['
      {
         "name":"amy",
         "age":17,
         "countB":1,
         "list":
         {
            "name":"music"
         }
      },
      {
         "name":"david",
         "age":16,
         "countB":2,
         "list":
         [                          // when countB > 1, it have this one '['
            {
               "name":"music"
            },
            {
               "name":"computer"
            }
         ]                          // when countB > 1, it have this one ']'
      }
   ]                                // when countA > 1, it have this one ']'
}

所以,我想自定义我的反序列化器,只需添加两个字符'['&amp;当countA或countB = 1时,']'到数据或列表:

class MyDeserializer implements JsonDeserializer<List>{

    public List deserialize(JsonElement json, Type typeOfT,
        JsonDeserializationContext context) throws JsonParseException {


        String jsonString = json.toString();

        // fix json string to my expected format
        if(!(jsonString.startsWith("[") && jsonString.endsWith("]")))
            jsonString = "["+jsonString+"]";

        // get the XXX type from List<XXX>
        Type valueType = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];

        // call default deserializer
        return context.deserialize(new Gson().fromJson(jsonString,JsonElement.class), valueType);
    }
}

在我的主要():

String jsonStrng = ...; // return from some server
Response bean = (Response) fromJson(jsonString, Response.class); // get the following error

方法fromJson():

@SuppressWarnings({ "unchecked", "rawtypes" })
public static Object fromJson(String data, Class clazz) {

    Gson gson = new GsonBuilder().registerTypeAdapter(List.class, new MyDeserializer()) .create();

    Object obj = gson.fromJson(data, clazz);

    gson = null;
    return obj;
}

但我有错误:

Expected BEGIN_OBJECT but was BEGIN_ARRAY

我知道我收到此错误的原因:

  1. json string有两个级别的List类型
  2. 在我的反序列化器中,最后我调用了默认的反序列化器
  3. 但是,如果我尝试再次调用我的反序列化器,而不是调用默认的反序列化器,则会导致无限循环。

    我的问题是如何修复它?

    我在网上搜索了很长时间。但没用。请帮助或尝试提供一些如何实现这一目标的想法。

    提前致谢。

1 个答案:

答案 0 :(得分:0)

Gson是一个很棒的库,以高效的方式提供了许多功能,我认为出于几个原因,我们不应该劝阻您当前的方法。让我们仔细看看。

String jsonString = json.toString();

toString()基本上用于调试目的,我们不鼓励使用它,除了像StringBuilder这样的类,其中toString是获取结果的唯一方法设计。结果字符串可能看起来像你真正想要的那样,但这个结果可能很昂贵而且并不总是准确的(甚至将来也会改变)。

if(!(jsonString.startsWith("[") && jsonString.endsWith("]")))
    jsonString = "["+jsonString+"]";

这是性能和内存消耗的另一个惩罚点:连接一个大字符串将导致更大的字符串,而第一个字符串仍将消耗内存。请注意,JsonDeserializer为您提供了一个随时可用的JsonElement,它有一个非常方便的API来检查JSON树(内存中的JSON对象表示)。

Type valueType = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];

这里可能是ClassCastException:给定的类型不一定要参数化,因此它可能不是ParameterizedType实例,因为泛型类型在Java中的工作方式。

return context.deserialize(new Gson().fromJson(jsonString,JsonElement.class), valueType);

从头开始创建新的Gson实例时,您在这里失去了Gson配置,因此它可能无法以预期的方式工作。此外,Gson实例化是相对昂贵的操作,通常不需要重新实例化。

如何重做:

// Use generic types for good
final class UnwrappedElementListDeserializer<T>
        implements JsonDeserializer<List<T>> {

    // This deserializer holds no state, therefore it can be instantiated once...
    private static final JsonDeserializer<?> unwrappedElementListDeserializer = new UnwrappedElementListDeserializer<>();

    private UnwrappedElementListDeserializer() {
    }

    // But any caller won't ever know it unless the caller checks instance references equality with ==. But any reason of doing that?
    static <T> JsonDeserializer<List<T>> getUnwrappedElementListDeserializer() {
        @SuppressWarnings({ "unchecked", "rawtypes" })
        final JsonDeserializer<List<T>> castUnwrappedElementListDeserializer = (JsonDeserializer) unwrappedElementListDeserializer;
        return castUnwrappedElementListDeserializer;
    }

    @Override
    public List<T> deserialize(final JsonElement rootJsonElement, final Type type, final JsonDeserializationContext context)
            throws JsonParseException {
        // Gson uses JsonNull to denote the JSON null value -- it has API, whilst Java null can never have its "API"...
        if ( rootJsonElement.isJsonNull() ) {
            // Empty lists are better than null
            // Note that Collections.emptyList() might be an option too, but Gson uses mutable lists (you can modify them), so we do either
            // emptyList() cannot be modified, so we just return a new modifiable list
            return new ArrayList<>();
        }
        // Is it just an object? It looks like a single element out of an array
        final Type elementType = tryGetTypeParameter0(type);
        if ( rootJsonElement.isJsonObject() ) {
            final T element = context.deserialize(rootJsonElement, elementType);
            // Collections.singletonList() is immutable too
            final List<T> list = new ArrayList<>();
            list.add(element);
            return list;
        }
        // If it's an array, then it must have two or more elements
        if ( rootJsonElement.isJsonArray() ) {
            // context.deserialize(jsonElement, type) cannot work due to infinite self-recursion
            final List<T> list = new ArrayList<>();
            for ( final JsonElement jsonElement : rootJsonElement.getAsJsonArray() ) {
                final T element = context.deserialize(jsonElement, elementType);
                list.add(element);
            }
            return list;
        }
        throw new JsonParseException("Cannot parse: " + rootJsonElement);
    }

    private static Type tryGetTypeParameter0(final Type type) {
        if ( type instanceof ParameterizedType ) {
            final ParameterizedType parameterizedType = (ParameterizedType) type;
            return parameterizedType.getActualTypeArguments()[0];
        }
        return Object.class;
    }

}

数据传输对象:

final class Response {

    // Gson can assign final fields
    // Primitive types like `int` cannot be `null`-ed, but simple `0` will be inlined by javac
    // So `Integer.valueOf(0)` is a kind of cheating to avoid constant inlining
    final int countA = Integer.valueOf(0);
    final List<Person> data = null;

    // Yep, just for debugging purposes
    @Override
    public String toString() {
        return new StringBuilder("Response{")
                .append("countA=").append(countA)
                .append(", data=").append(data)
                .append('}')
                .toString();
    }

}
final class Person {

    final String name = null;
    final int age = Integer.valueOf(0);
    final int countB = Integer.valueOf(0);
    final List<Course> list = null;

    @Override
    public String toString() {
        return new StringBuilder("Person{")
                .append("name='").append(name).append('\'')
                .append(", age=").append(age)
                .append(", countB=").append(countB)
                .append(", list=").append(list)
                .append('}')
                .toString();
    }

}
final class Course {

    final String name = null;

    @Override
    public String toString() {
        return new StringBuilder("Course{")
                .append("name='").append(name).append('\'')
                .append('}')
                .toString();
    }

}

以下演示,其中JSON_1JSON_2代表您的JSON文档

public static void main(final String... args) {
    final Gson gson = new GsonBuilder()
            .registerTypeAdapter(List.class, getUnwrappedElementListDeserializer())
            .create();
    System.out.println(gson.fromJson(JSON_1, Response.class));
    System.out.println(gson.fromJson(JSON_2, Response.class));
}

将具有以下输出:

  

回复{countA = 1,数据= [人{名=&#39;彼得&#39;,年龄= 18,countB = 1,列表= [课程{名称=&#39;数学&#39;}] }]}
  回复{countA = 2,数据= [人{姓名=&#39; amy&#39;,年龄= 17,countB = 1,列表= [课程{名称=&#39;音乐&#39;}]},人{name =&#39; david&#39;,age = 16,countB = 2,list = [课程{name =&#39;音乐&#39;},课程{name =&#39; computer&#39;} ]}]}

对您的代码提出更多评论。

  • Gson可以实例化一次,原因很多,包括线程安全:只需将其置于静态字段中(假设fromJson也是static)。
  • gson = null没有多大帮助 - 你可以删除这一行。
  • fromJson可以非常轻松地重新实施:public static <T> T fromJson(String data, Type type) { return gson.fromJson(data, type); } - 所以您不需要@SupressWarnings,您可以在呼叫网站上摆脱类型转换,以及通过传递Type,传递任何类型,而不仅仅是Class<T>实例唯一能够容纳的真实类型。为了简单起见,甚至可以删除这种方法?
  • 按类注册类型适配器会为所有参数化绑定(反)序列化程序。请注意,您可以绑定具体的参数化,例如,.registerTypeAdapter(TypeToken.getParameterized(List.class, Course.class).getType(), getUnwrappedElementListDeserializer())不触及其他类参数化(这是Class<T>通常无法做到的)。但是,即使这可能会影响您想要的(比如,您希望某些类具有此类行为,但不要让其他类具有此行为):请查看@JsonAdapter注释。
  • 将字符串视为保存JSON文档的最后手段。在大多数情况下,将Reader实例传递给gson.fromJson()方法更有效,而不是要求将整个文档存储在内存中。