【问题标题】:Serializing Object to Json将对象序列化为 Json
【发布时间】:2017-03-30 17:32:24
【问题描述】:

我正在尝试使用 Gson 获取一些 Java 对象并将其序列化为 json 并获取代表该 Json 的字节数组。我需要一个字节数组,因为我将输出传递给要求它是字节数组的外部依赖项。

public byte[] serialize(Object object){
  return gson.toJson(object).getBytes();
}

我有两个问题:

  1. 如果输入是字符串,gson 似乎会按原样返回字符串。它不对输入进行任何验证。这是预期的吗?我想以一种可以验证输入对象实际上是 Json 的方式使用 Gson。我怎么能这样做?
  2. 我将在短时间内调用此序列化函数数千次。转换为String,然后转换为byte[] 可能是一些不必要的开销。有没有更优化的方法来获得byte[]

【问题讨论】:

  • 我想使用 Gson 来验证输入对象实际上是 Json。 -- 你问的是序列化,但现在听起来就像你要反序列化一样。将 JSON 传递给 Gson.toJson 是否有意义?
  • 好吧,我想如果我想验证,我需要同时进行序列化和反序列化,对吧?
  • 好吧,我想如果我想验证,我需要同时进行序列化和反序列化,对吧?
  • 我的意思是您的问题 #1 仍然不清楚:听起来您是在您提供的代码 sn-p 的上下文中提出的,但没有提及反序列化。能详细点吗?
  • 我使用Gson.toJson 希望它能验证对象是否可以实际表示为Json。这就是意图。我不想反序列化。我只是想验证然后序列化。在阅读Gson 文档时,我认为Gson.toJson 将提供验证能力。它恰好返回一个字符串,因此可以通过 getBytes() 来完成获取 byte[]。现在更清楚了吗?我想我应该添加更多的解释。

标签: java gson


【解决方案1】:

编辑:我对第 1 点的回答有误。

2) 如果你只使用香草 gson 转换器,反射会有很多不必要的开销。在您的情况下,编写自定义适配器将非常有利于性能。这是一篇包含更多信息的文章 https://open.blogs.nytimes.com/2016/02/11/improving-startup-time-in-the-nytimes-android-app/?_r=0

【讨论】:

    【解决方案2】:

    如果输入是字符串,gson 似乎会按原样返回字符串。它不对输入进行任何验证。这是预期的吗?

    是的,这很好。它只返回给定字符串的 JSON 字符串表示形式。

    我想以一种可以验证输入对象实际上是 Json 的方式使用 Gson。我怎么能这样做?

    本身不需要。 Gson.toJson() 方法接受要序列化的对象并始终生成有效的 JSON。如果你的意思是反序列化,那么 Gson 在读取/解析/反序列化(实际上是读取,这是 Gson 的最底层)期间对无效的 JSON 文档进行快速失败。

    我将在短时间内调用这个序列化函数数千次。转换为 String 然后转换为 byte[] 可能是一些不必要的开销。有没有更优化的方法来获取字节[]?

    是的,为了暴露其内部的char[] 克隆而累积一个 JSON 字符串当然是内存浪费。 Gson 基本上是一个面向流的工具,请注意有 Gson.toJson 方法重载接受 Appendable,它们基本上是 Gson 核心(只需快速了解 Gson.fromJson(Object) 的工作原理——它只是创建一个 StringWriter由于Appendable 接口,实例会累积一个字符串)。如果 Gson 可以通过 Reader 发出 JSON 令牌而不是写入 Appendable,那将是非常酷的,但是这个 idea 被拒绝了,很可能永远不会在 Gson 中实现,不幸的是。由于 Gson 在反序列化期间以读取语义方式(从您的代码角度来看)不发出 JSON 令牌,因此您必须缓冲整个结果:

    private static byte[] serializeToBytes(final Object object)
            throws IOException {
        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        final OutputStreamWriter writer = new OutputStreamWriter(outputStream);
        gson.toJson(object, writer);
        writer.flush();
        return outputStream.toByteArray();
    }
    

    这个不使用StringWriter,因此没有使用克隆数组乒乓累积中间字符串。我不知道是否有可以利用/重用现有字节数组的编写器/输出流,但我相信应该有一些,因为它为您在问题中提到的性能目的提供了很好的理由。

    如果可能,您还可以检查您的库接口/API 是否以某种方式公开/接受OutputStreams——然后您可以轻松地将此类输出流传递给serializeToBytes 方法,甚至删除该方法。如果它可以使用输入流,而不仅仅是字节数组,您还可以查看converting output streams to input streams,以便serializeToBytes 方法可以返回InputStreamReader(需要一些开销,但可以处理无限数据 - - 需要找到平衡点):

    private static InputStream serializeToByteStream(final Object object)
            throws IOException {
        final PipedInputStream inputStream = new PipedInputStream();
        final OutputStream outputStream = new PipedOutputStream(inputStream);
        new Thread(() -> {
            try {
                final OutputStreamWriter writer = new OutputStreamWriter(outputStream);
                gson.toJson(object, writer);
                writer.flush();
            } catch ( final IOException ex ) {
                throw new RuntimeException(ex);
            } finally {
                try {
                    outputStream.close();
                } catch ( final IOException ex ) {
                    throw new RuntimeException(ex);
                }
            }
        }).start();
        return inputStream;
    }
    

    使用示例:

    final String value = "foo";
    System.out.println(Arrays.toString(serializeToBytes(value)));
    try ( final InputStream inputStream = serializeToByteStream(value) ) {
        int b;
        while ( (b = inputStream.read()) != -1 ) {
            System.out.print(b);
            System.out.print(' ');
        }
        System.out.println();
    }
    

    输出:

    [34, 102, 111, 111, 34]
    34 102 111 111 34

    两者都表示一个 ASCII 码数组,字面意思是字符串 "foo"

    【讨论】:

      猜你喜欢
      • 2010-10-08
      • 2016-12-22
      • 2018-07-20
      • 2021-12-16
      • 2011-10-13
      相关资源
      最近更新 更多