2017-04-08 120 views
1

使用GSON序列化JSON中的BufferedImages似乎存在問題。我正在使用Derby來存儲圖像。當我查詢數據庫時,我構建了一個包含一些文本字段和一個BufferedImage字段的JavaBean。然後我使用GSON將JavaBean轉換爲JSON,這就是發生異常的地方。GSON無法序列化BufferedImages

異常信息如下:
java.lang.IllegalArgumentException異常:類sun.awt.image.ByteInterleavedRaster聲明瞭一個名爲maxX的

多個JSON場我發現這裏類似的問題GSON java.lang.IllegalArgumentException: class 'xx' declares multiple JSON fields named 'XX' AND StackOverflowError這裏class A declares multiple JSON fields

但問題出在Java附帶的awt庫中。如果我可以訪問AWT源代碼,我可以按照其他stackoverflow答案中提供的答案,但我該怎麼做?

回答

2

您必須知道,並非每個類都被設計爲(反)序列化,特別是如果(反)序列化基於目標類二進制結構。你的方法至少有以下弱點:

  • sun.awt.image.ByteInterleavedRaster類字段不一定在其他JVM/JRE是相同的,因此你可以是廠商鎖定;
  • 在JSON中保留二進制數據可能不是最好的選擇(可能在(de)序列化,存儲消耗和性能期間內存消耗巨大和可怕) - 也許通用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); 
     ... 
    } 
} 

實施例輸出(與真正的微小的(32×32)PNG內部文件):

{ 「圖像」:「iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADgklEQVR42t2XXUiTYRTHpxj4kSKShhgYGSihZGIXXYhU5J2BhBIhCH5cCF6oiWhG0k1BpHghgRgoJHiloBKEqFQ3frDNuemaOqdu0 + n8mFM3Nzf37z1n + JZUEPlOoQdetvd5L87vOed/Ph4ZznnJzsqQz + UFZ + M5HwBrezuUFy9CERoKY3U1jtzuwAFY29pgGxgQ350aDVSXLmFfLud9eVAQTHV1gQNYKi + HMiwM9uFhft/o6MBcTg6fWp + XB93duzhyOOA7POSwyAIR64UnTxhi9 + tXfhQhIdBlZ2P2wQM2Tmv11StY3rwJjAYIQl9QAGVUFPZGRzF7/z7kwcGw9ffzt80PHzAZE4ODu TnpAQ50OjgmJ3HkcmE + N5chdr98wfzDh5DLZPyo4uOx +/mz9Bqg + B8b0d6 + zSecFeJPInSo1XAbjXAKvxR/yUW4Pz7uV/vEBJ9OffUqNNev49BiYeGp4uLg0usDUwdIUNNpaTDV1op7rqUljvNKYyMLb7G4GIdWa2AAbH19LDIy8vNaefmSBRiQUkynMtXUYLGkBO7lZWx2dTEEnVjURFnZL1CSASyWlmL6xg1okpIwdeUK3CYTNjo7WYCGoiLOeU1yMtxmc2AA1NeuscA829uYTk1lEIJYf/eOIcgzP6tdEgAyRicjtatiY8V9EhdDpKTw/7XmZoYgGEkBzEITIQDzs2dsYPX1a/EbuZq8YG5o8GeG8E2dmIgjp/P0AJxGgku1GRnYVyh479jVdFrRE + vrXGqPl3dvTxoPeO12aDMz2aBDqRT315qa/TRV/wTgsdmw1d3NJVSMs + BmOqlYhARXL1dUSA/gWljg9FKGh/u72tgYQ1BqEcjvqtqpAHY + fcLOx4/+ durzcTOxvH3LXY1qOUFQ/CnVyAszN2 + eGK1OBWCur4cyIgIrL174Xb + 1hdl79xiERioqOFRSKf3sQ0MclvXWVmk8sN3b6 + 9UBsMvQwWtb3fuwD4ywpkwlZDAojNWVUk3lhsrK7Hw + PHJ + AudzKnVwrOzwwYP5ud50JhJT5cs9iLAxvv3UFy4wLVdn58P1eXLP4YKIfWor09GR0MZGYm1lhbpLyYUZ/Pz55i5dQu6rCwYnz4FhYXmNjJKKbYmiHG7p + fsb0aGwkIsC2PWuVzNaJ5j1Q8Oni0AVTkKCbmffs/8cuoVlK9/9IjHrP/qdvyn9R0SEM4flWsmCwAAAABJRU5ErkJggg \ u003d \ u003d「}

我認爲有太多的缺陷,我強烈建議您儘可能重新設計您的二進制存儲並按原樣存儲二進制內容。

+0

你的意思是把它作爲客戶端的二進制權利嗎?即不要在服務器端進行任何對話? – kiwicomb123

+0

@ kiwicomb123這取決於你真正需要的情況。使用緩衝圖像會導致圖像轉換(可以接受嗎?)。另外,通過JSON傳遞它_using_ Gson可能不是最好的選擇,因爲Gson要求整個字符串從JSON流中讀取/寫入,而不是塊(也許其他JSON庫可以做得更好?)。通過簡單地將它保存爲二進制文件並將其保存(當然,您可以在上傳之前應用一些驗證),您可以通過直接URL公開二進制文件(當然,也可以應用一些授權)。 –

+0

@ kiwicomb123或者,您也可以嘗試使用https://fasterxml.github.io/jackson-core/javadoc/2.6/com/fasterxml/jackson/core/JsonGenerator.html和http://fasterxml.github.io/jackson -core/javadoc/2.6/com/fasterxml/jackson/core/JsonParser.html - 它們似乎支持原始數據,因此您可以嘗試組合各種輸入/輸出流以生成/驗證數據並直接寫入JSON。 –