【问题标题】:GSON can't serialize BufferedImagesGSON 无法序列化 BufferedImages
【发布时间】:2017-09-04 05:21:06
【问题描述】:

使用 GSON 在 JSON 中序列化 BufferedImage 似乎存在问题。我正在使用 Derby 来存储图像。当我查询数据库时,我构建了一个包含一些文本字段和一个 BufferedImage 字段的 JavaBean。然后我使用 GSON 将 JavaBean 转换为 JSON,这就是发生异常的地方。

异常消息如下:
java.lang.IllegalArgumentException: class sun.awt.image.ByteInterleavedRaster 声明了多个名为 maxX 的 JSON 字段

我确实在这里GSON java.lang.IllegalArgumentException: class 'xx' declares multiple JSON fields named 'XX' AND StackOverflowErrorclass A declares multiple JSON fields发现了类似的问题

但问题在于 Java 中包含的 awt 库。如果我可以访问 AWT 源代码,我可以按照其他 stackoverflow 答案中提供的答案进行操作,但我该怎么做?

【问题讨论】:

    标签: java json gson awt bufferedimage


    【解决方案1】:

    您必须知道,并非每个类都被设计为(反)序列化,特别是如果(反)序列化基于目标类二进制结构。您的方法至少有以下弱点:

    • sun.awt.image.ByteInterleavedRaster 类字段在另一个 JVM/JRE 上不一定相同,因此您可能会被供应商锁定;
    • 在 JSON 中保存二进制数据可能不是最佳选择(在(反)序列化期间可能会消耗巨大而可怕的内存消耗、存储消耗、性能)——也许通用 blob 存储更适合二进制数据?
    • 使用 Java AWT 读取图像并将其写回并不能保证相同的二进制输出:例如,我的测试图像 1.2K 被反序列化为另一个大小 0.9K 的图像;
    • 您必须选择目标持久图像格式或检测最有效的一种(如何?)。

    考虑以下简单类:

    final class ImageHolder {
    
        final RenderedImage image;
    
        ImageHolder(final RenderedImage image) {
            this.image = image;
        }
    
    }
    

    现在您必须创建一个类型适配器来告诉 Gson 如何存储和恢复特定类型实例:

    final class RenderedImageTypeAdapter
            extends TypeAdapter<RenderedImage> {
    
        private static final TypeAdapter<RenderedImage> renderedImageTypeAdapter = new RenderedImageTypeAdapter().nullSafe();
    
        private RenderedImageTypeAdapter() {
        }
    
        static TypeAdapter<RenderedImage> getRenderedImageTypeAdapter() {
            return renderedImageTypeAdapter;
        }
    
        @Override
        @SuppressWarnings("resource")
        public void write(final JsonWriter out, final RenderedImage image)
                throws IOException {
            // Intermediate buffer
            final ByteArrayOutputStream output = new ByteArrayOutputStream();
            // By the way, how to pick up the target image format? BMP takes more space, PNG takes more time, JPEG is lossy...
            ImageIO.write(image, "PNG", output);
            // Not sure about this, but converting to base64 is more JSON-friendly
            final Base64.Encoder encoder = Base64.getEncoder();
            // toByteArray() returns a copy, not the original array (x2 more memory)
            // + creating a string requires more memory to create the String internal buffer (x3 more memory)
            final String imageBase64 = encoder.encodeToString(output.toByteArray());
            out.value(imageBase64);
        }
    
        @Override
        public RenderedImage read(final JsonReader in)
                throws IOException {
            // The same in reverse order
            final String imageBase64 = in.nextString();
            final Base64.Decoder decoder = Base64.getDecoder();
            final byte[] input = decoder.decode(imageBase64);
            return ImageIO.read(new ByteArrayInputStream(input));
        }
    
    }
    

    请注意,Gson 目前还没有很好地支持字节转换,但如果修复它,将来可能会是 somewhat better

    使用示例:

    private static final Gson gson = new GsonBuilder()
            .registerTypeHierarchyAdapter(RenderedImage.class, getRenderedImageTypeAdapter())
            .create();
    
    public static void main(final String... args)
            throws IOException {
        try ( final InputStream inputStream = getPackageResourceInputStream(Q43301580.class, "sample.png") ) {
            final RenderedImage image = ImageIO.read(inputStream);
            final ImageHolder before = new ImageHolder(image);
            final String json = gson.toJson(before);
            System.out.println(json);
            final ImageHolder after = gson.fromJson(json, ImageHolder.class);
            ...
        }
    }
    

    示例输出(内部包含真正的微小 (32x32) PNG 文件):

    { “图像”:“iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADgklEQVR42t2XXUiTYRTHpxj4kSKShhgYGSihZGIXXYhU5J2BhBIhCH5cCF6oiWhG0k1BpHghgRgoJHiloBKEqFQ3frDNuemaOqdu0 + n8mFM3Nzf37z1n + JZUEPlOoQdetvd5L87vOed / Ph4ZznnJzsqQz + UFZ + M5HwBrezuUFy9CERoKY3U1jtzuwAFY29pgGxgQ350aDVSXLmFfLud9eVAQTHV1gQNYKi + HMiwM9uFhft / o6MBcTg6fWp + XB93duzhyOOA7POSwyAIR64UnTxhi9 + tXfhQhIdBlZ2P2wQM2Tmv11StY3rwJjAYIQl9QAGVUFPZGRzF7 / z7kwcGw9ffzt80PHzAZE4ODuTnpAQ50OjgmJ3HkcmE + N5chdr98wfzDh5DLZPyo4uOx + / mz9Bqg + B8b0d6 + zSecFeJPInSo1XAbjXAKvxR / yUW4Pz7uV / vEBJ9OffUqNNev49BiYeGp4uLg0usDUwdIUNNpaTDV1op7rqUljvNKYyMLb7G4GIdWa2AAbH19LDIy8vNaefmSBRiQUkynMtXUYLGkBO7lZWx2dTEEnVjURFnZL1CSASyWlmL6xg1okpIwdeUK3CYTNjo7WYCGoiLOeU1yMtxmc2AA1NeuscA829uYTk1lEIJYf / eOIcgzP6tdEgAyRicjtatiY8V9EhdDpKTw / 7XmZoYgGEkBzEITIQDzs2dsYPX1a / EbuZq8YG5o8GeG8E2dmIgjp / P0AJxGgku1GRnYVyh479jVdFrRE + Vrxgqpl3dvtxopeo12admz2abdqrt315qa / trv / wtgsdmw1d3njvsms + bmoqlyharxl1dusa / gwljg9fkgh / u72tgyq1bqecjvqtqpahy + fclox4 / + durzctoxvh3lxy1qoufq / cnvyaszn2 + egk1obwcur4cyigirl174xb + 1hdl79xi ERioqOFRSKf3sQ0MclvXWVmk8sN3b6 + 9UBsMvQwWtb3fuwD4ywpkwlZDAojNWVUk3lhsrK7Hw + PHJ + AudzKnVwrOzwwYP5ud50JhJT5cs9iLAxvv3UFy4wLVdn58P1eXLP4YKIfWor09GR0MZGYm1lhbpLyYUZ / Pz55i5dQu6rCwYnz4FhYXmNjJKKbYmiHG7p + fsb0aGwkIsC2PWuVzNaJ5j1Q8Oni0AVTkKCbmffs / 8cuoVlK9 / 9IjHrP / qdvyn9R0SEM4flWsmCwAAAABJRU5ErkJggg \ u003d \ u003d“} P>

    我认为存在太多缺陷,我强烈建议您尽可能重新设计二进制存储并按原样存储二进制内容。

    【讨论】:

    • 你的意思是把它作为一个二进制文件从客户端保存?即在服务器端根本不进行任何对话?
    • @kiwicomb123 这取决于你在你的情况下真正需要什么。使用缓冲图像将导致图像转换(可以接受吗?)。此外,通过 JSON using Gson 传递它可能不是最佳选择,因为 Gson 需要从 JSON 流读取/写入整个字符串,而不是按块(也许其他 JSON 库可以做到)更好吗?)。通过简单地保持二进制并按原样存储(当然,您可能会在上传之前应用一些验证),您可以通过直接 URL 公开二进制文件(当然,也可能应用一些授权)。
    • @kiwicomb123 或者,您也可以尝试使用fasterxml.github.io/jackson-core/javadoc/2.6/com/fasterxml/…fasterxml.github.io/jackson-core/javadoc/2.6/com/fasterxml/…——它们似乎支持原始数据,因此您可以尝试组合各种输入/输出流以生成/验证您的数据并将其直接写入 JSON。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-09
    • 2023-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多