GSON:自定义对象反序列化

11

好的,我修改了问题,因为它不够清楚。

编辑2:更新了JSON文件。

我正在一个Android应用中使用GSON,并需要解析来自服务器的JSON文件,这些文件有点复杂。我不想让我的对象结构太过繁重,所以我希望简化内容:因此我的对象结构将不会与JSON文件的结构相同。

例如,如果在JSON中我有这个:

{
    "object1":{
        "attribute1" : "test1",
        "attribute40" : "test40",
        "user":{
            "id":1,
            "name":"foo"
        }
        ,"example":{
            "total":10,
            "list":[
            {
                "tag":"tag1",
                "name":"object name 1",
                "pos":1
            },
            {
                "tag":"tag10",
                "name":"object name 10",
                "pos":10
            }
        ]
        }
    }
    "object2":{
        "attribute1":"test..."
    }
}

我不想在当前对象结构中保留一个包含ArrayListint "total"的Example对象,而是想只保留一个简单字符串,其值为"object name 1;object name 2;..."

此外,我只想存储用户ID,而不是完整的用户信息,因为我已经在其他地方通过另一个服务器API调用将完整的用户信息存储。

因此,我的类应该是这样的:

class Foo{
    int userId;
    String example; //"object name 1;object name 2;..."
    ...
}

我想我们可以通过自定义反序列化器来实现这一点,但我不知道如何做。如果可能的话,我想尽量减少内存使用,因此我认为使用完整的对象示例来构建我的String example并不是正确的方法。

最坏情况下,如果太复杂了,我至少希望能够在解析示例对象时仅存储标签项列表:因此我需要一个自定义反序列化器来去掉 int total

那么我会有:

class Foo{
    int userId;
    ArrayList<Tag> example;
    ...
}

根据您的特殊要求,使用GSON可能过于臃肿。只需将JSON字符串传递到域模型的构造函数中,然后使用普通字符串操作拆分/提取所需字段即可。 - yorkw
当然,这可能是一个解决方案,但我有很多JSON文件,每个文件都有超过30个字段,并且结构可能会在未来发生变化。因此,我肯定更喜欢使用像GSON这样的库来最小化工作量和维护成本。特别是如果我已经在某些JSON文件上使用它。 - Chayy
“结构可能会在未来发生变化”,从面向对象的角度来看,强烈建议现在对完整的领域对象进行建模,即使您不使用其所有属性。 - yorkw
5个回答

20

我采用了回答中的方案来呈现完整的解决方案,并适应了更改后的JSON字符串。代码假设字符串json正好保存了问题中的(更新后的)JSON数据。要求是填充以下类(省略setter和toString函数):

class Object1
{
    private String attribute1;
    private String attribute40;
    private int userId;
    private String  nameList;
}

GSON支持(与大多数其他REST库相同)三种模式:

  • GSON_DOM
    通过JsonParser.parse()读取整个JSON并在内存中构建DOM树(对象模型访问)。因此,此解决方案适用于小型JSON文件。
  • GSON_STREAM
    仅通过JsonReader读取JSON的块。虽然代码更加复杂,但适用于大型JSON文件。从Android 3.0 Honeycomb开始,GSON的流解析器包含在android.util.JsonReader中。
  • GSON_BIND
    通过反射直接将数据绑定到类,显著减少了代码。 GSON允许混合模式,这意味着可以将GSON_DOM和GSON_BIND或GSON_STREAM和GSON_BIND组合在一起,本答案应展示此种组合方式。

使用GSON_DOM和GSON_BIND填充类Object1的实现如下:

private static void deserializeViaObjectAccess(final String json)
{
    Gson gson = new Gson();

    // Read the whole JSON into meomory via GSON_DOM
    JsonParser parser = new JsonParser();
    JsonObject object1 = parser.parse(json).getAsJsonObject().getAsJsonObject("object1");

    // map the Object1 class via GSON_BIND
    // (bind common attributes which exist in JSON and as properties in the class)
    // mapper acts as factory
    Object1 result = gson.fromJson(object1, Object1.class);

    // manually read the attribute from the user object
    int userId = object1.getAsJsonObject("user").getAsJsonPrimitive("id").getAsInt();
    result.setUserId(userId);

    // manually read the attributes from the example object
    String names = "";
    JsonArray list = object1.getAsJsonObject("example").getAsJsonArray("list");
    for (int i = 0; i < list.size(); ++i)
    {
        JsonObject entry = list.get(i).getAsJsonObject();
        String name = entry.getAsJsonPrimitive("name").getAsString();

        names = i == 0 ? name : names + "; " + name;
    }
    result.setNameList(names);

    // Output the result
    log.debug(result.toString());
}

使用GSON_STREAM和GSON_BIND填充Object1类的实现如下:

目前,仅当节点完全通过GSON_BIND或GSON_STREAM加载时才有可能。此示例需要将节点本身拆分。这只有在即将推出的2.2版本中才能实现。当GSON 2.2可用时,我稍后会提交代码。


2.2.4是最新版本,请告诉我们如何使用GSON_BIND。 - coding_idiot

2

一个选项是使用Gson内置的解析器解析JSON字符串,如此处所述:这里。您可以像这样进行操作:

com.google.gson.JsonParser parser = new JsonParser();
JsonObject object = parser.parse(data).getAsJsonObject();
JsonObject example = object.getAsJsonObject("example");
JsonArray list = example.getAsJsonArray("list");

JsonObjectJsonArray 是 Gson 自身的一部分。

使用它们后,您可以使用像getAsInt这样的函数来解析单个字段并构建并返回任何对象。

编辑 1

似乎您也可以在自定义类上使用gson.fromJson,而不仅仅是在此示例中给出的通用Java类型。因此,您必须使用parse解析JSON字符串,并在其中一个内部对象或数组上调用fromJson。


好的,那么这是我使用经典的org.json类所采用的第一种方法。这种技术的缺点是我必须手动匹配我的对象字段和JSON字段,当我有超过30-40个字段时,这是很多工作和维护。 - Chayy
似乎你可以在其中一个内部类中也使用fromJson。这可能适合你的需求。 - Abhinav

1
将示例JSON反序列化为完整的Example对象,使用Example对象的名称属性构建您想要的字符串,忘记Example对象。
我并没有完全理解第二个问题,但是如果您有一个包含所有字段/属性的完整Test1对象,则可以创建一个Test2对象,该对象从Test1中获取它需要的字段。例如,您的Test2对象可以在其构造函数中接受Test1作为参数,并仅获取它需要的属性,而忽略其余部分。

好的,我的问题更多是:如何在不将完整对象存储在结构中的情况下实现这一点。例如,我可以使用gson.fromJson(),它将获取除了我的连接字符串之外的所有字段,然后手动执行连接操作。同样的事情也适用于仅保留ID。但我不想在内存中拥有这些无用的字段,我想在序列化过程中完成这个操作。对于第二个问题,不,两个对象Test1Test2是独立的:对象Test2可以在对象Test1之前创建。 - Chayy

1

当您通过http流式传输Json时,可以简单地丢弃文本并仅存储自定义对象。在这种情况下,您将不断丢弃不需要的信息。

While stream not empty  

    Read the next block into a new string

    Deserialize the string to the your object

    Store the object in a myArrayList

注意:如果您希望应用程序具有鲁棒性,最好阅读整个JSON并将其作为一个整体进行消耗。除非您想将JSON作为原始字符流读取(我怀疑,除非您的JSON真的非常大且难以处理,否则不需要这种快捷方式)。
然而,读取输入流而不强制执行JSON格式要求可以在不必将不必要的数据结构写入内存的情况下完成。如果您只需要数据的一小部分-例如,您只需要JSON中的人名或URL,则可以使用此方法。但是,如果您需要更复杂的数据结构,则会出现问题。尽管如此:
//示例:从JSON行解析URL而无需存储整个数据结构
While input stream is not empty

     String x = nextLine

     If x contains "http"

         myArrayList.add(parseUrl(x)

最后的想法:

但是,JSON的REST请求不像SQL-您不能通常地和任意地忽略某些字段。也许,如果您真的想要更轻量级的JSON,更自然的方法是告诉或检查您的REST服务提供商是否可以简单地扩展REST请求参数的类型以适应您的用例。


好的,谢谢你的回答,这是一种我还没有尝试过的方法。但是,就像我尝试的对象模型访问一样,使用这种技术需要手动测试每个字段与字段名称是否匹配。当然,这样可以丢弃不需要的信息,但也需要更多的工作和更难的维护。即使你的方法在性能方面比我的第一个方法更好,它仍然不能完全满足我的需求。我正在寻找一些更自动化的东西,在其中我只需对一个对象执行特定的任务,而其他字段则由解析器管理。 - Chayy

0

我建议使用Jackson库。它是根据Apache许可证提供的,因此您可以免费将其用于商业用途。 在tutorial中查看大写字母“Streaming API Example”。它非常简单,您完全控制流程。因此,您可以获取所需内容并忽略所有其他内容。 Jackson库分为几个JAR文件。支持流API的JAR文件最小,不使用任何其他文件。 这就是答案,我想。

Jackson甚至可以提供更多功能。在this article中,您可以找到以更高级别的方式读取JSON文件的方法,作为元素,但是需要先设置所需对象和不需要的对象。因此,解析的结果可以立即仅包含您需要的元素。


看起来GSON库也提供了流模式,即使它似乎比Jackson效率低。无论如何,正如我对其他答案所说的那样,它不适合我的需求。 - Chayy
你是说GSON而不是JSON,对吧?无论如何,你想要重新构建JSON对象,但只保留必需的字段?也许你应该重新编辑你的问题。问题不应该被埋没在评论中。 - Gangnus
你可以重新构建JSON对象,但只包含所需的字段 - 这正是我在答案的第二段中所说的。 - Gangnus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接