差異處

這裏顯示兩個版本的差異處。

連向這個比對檢視

java:jackson:annotation:jsonserialize:module_annotation [2018/06/03 22:54]
tony
java:jackson:annotation:jsonserialize:module_annotation [2023/06/25 09:48]
行 1: 行 1:
-{{tag>​java jackson}} 
-====== Jackson - Convert the value fields with special char ====== 
-===== Problem ===== 
-這陣子在擴充Rest API的功能時,聽同事說某功能輸出xml格式下,因內容使用unicode,導致無法正常在瀏覽器上顯示。xml並非不支援unicode,而是因為瀏覽器只支援xml 1.0;我們資料剛好不在1.0所規範的範圍內,因此瀏覽器才把它認為不合法。 
-===== Research ===== 
-為什麼Json沒這問題呢?​ 對於Json來說,unicode內容有兩種選擇:​ 
-  - 以代碼顯示:​ \u0001\u0002\u0003。 
-  - 以原始樣式顯示:​ 即欄位定義為unicode type。 
-而xml在瀏覽器接收到代碼後,會做轉換;無法轉換時,就會報錯。在花些時間研究後,得知Amazon的Simple DB針對client傳遞或是要回應給client的資料,如果有這種情況,會將內容做base64的encoding。雖然對user來說是個小effort,但也不失為一個解法。 
-===== How to? ===== 
-假設我們的Event Bean物件是這樣子:​ 
-<code java> 
-public class Event { 
- private Date date; 
-  
- private String message; 
-  
- public Date getDate(){ 
- return date; 
- } 
-  
- public void setDate(Date date){ 
- this.date = date; 
- } 
-  
- public String getMessage(){ 
- return message; 
- } 
-  
- public void setMessage(String message){ 
- this.message = message; 
- } 
-} 
-</​code>​ 
-當message中包含對xml不合法的字元,就會發生我所敘述的問題。 
-==== 第一個方法 - 直接操作message ==== 
-最簡單的方法,當然就是直接將message做base64的encoding。但如果直接把值塞到物件內,可能會讓使用到的client必須自行去decoding。因此,如果選擇使用這種方式,建議使用@JsonSerialize與@JsonDeserialize:​ 
-<code java> 
-    @JsonSerialize(using=Base64StringSerializer.class) 
-    @JsonDeserialize(using=Base64StringDeserializer.class) 
-    private String message; 
-</​code>​ 
-這方法可以讓物件在輸出為xml或json格式時,才被做encoding;如果user將值給塞回來時,也會透過descerializer還原為好操作的內容。通常使用這個方法message所輸出的xml會長這樣子:​ 
-<code xml> 
-<​message>​dGVzdAECAw==</​message>​ 
-</​code>​ 
-對於沒讀使用手冊的user來說,可能無法知道它是經過encoding;因此比較好的顯示方式為:​ 
-<code xml> 
-<message encoding="​base64">​dGVzdAECAw==</​message>​ 
-</​code>​ 
-這時在做序列化時,就要區分xml與json的做法:​ (Base64StringSerializer) 
-<code java> 
-@Override 
-public class Base64StringSerializer extends StdSerializer<​String>​ { 
- private static final long serialVersionUID = 1L; 
  
- public Base64StringSerializer() { 
- super(String.class);​ 
- } 
-  
- @Override 
- public void serialize(String value, JsonGenerator jgen, 
- SerializerProvider provider) throws IOException { 
- String encodingVal = new String(Base64.getEncoder().encode(value.getBytes()));​ 
- if( jgen instanceof ToXmlGenerator ) { 
- ToXmlGenerator xgen = (ToXmlGenerator) jgen; 
-  
- xgen.writeStartObject();​ 
- xgen.setNextIsAttribute(true);​ 
- xgen.writeFieldName("​type"​);​ 
- xgen.writeString("​base64"​);​ 
- xgen.setNextIsAttribute(false);​ 
-  
- xgen.setNextIsUnwrapped(true);​ 
- xgen.writeFieldName("​value"​);​ 
- xgen.writeObject(encodingVal);​ 
- xgen.setNextIsUnwrapped(false);​ 
-  
- xgen.writeEndObject();​ 
- } else { 
- jgen.writeString(encodingVal);​  
- } 
- } 
-} 
-</​code>​ 
-反序列化時,就是把輸入的base64轉回去,這部分看需求決定需不需要做:​ (Base64StringDeserializer) 
-<code java> 
-public class Base64StringDeserializer extends StdDeserializer<​String>​ { 
- 
- private static final long serialVersionUID = 1L; 
- 
- public Base64StringDeserializer() { 
- super(String.class);​ 
- } 
- 
- @Override 
- public String deserialize(JsonParser parser, DeserializationContext context) 
- throws IOException,​ JsonProcessingException { 
- String text = parser.getValueAsString();​ 
- return new String(Base64.getDecoder().decode(text));​ 
- } 
-} 
-</​code>​ 
-第一個方法在欄位上宣告了序列化與反序列化的物件,這也代表著這些物件同時身負處理不同格式的責任;因此第二個方法是希望讓client可以選擇序列化與反序列化的方法。 
-==== 第二個做法 - 宣告一個EncodingText物件 ==== 
-=== EncodingText物件 === 
-首先我將原本message中使用的String特別宣告一個Base64Text物件,這可以讓Jackson區別與String的不同,且有相同需求的欄位也可以重複使用;另外在xml格式時,透過JacksonXmlProperty讓它帶上encoding的屬性:​ 
-<code java> 
-public class Base64Text { 
- @JacksonXmlText 
- private String value; 
- 
- public Base64Text(String value) { 
- this.value = value; 
- } 
- @JacksonXmlProperty(isAttribute=true,​localName="​encoding"​) 
- public String getEncoding(){ 
- return "​base64";​ 
- } 
-  
- public String getValue(){ 
- return value; 
- } 
-  
- @JsonIgnore 
- public String getEncodingValue(){ 
- return new String(Base64.getEncoder().encode(value.getBytes()));​ 
- } 
-  
- public static Base64Text decode(String aEncryptValue){ 
- return new Base64Text(new String(Base64.getDecoder().decode(aEncryptValue)));​ 
- } 
-} 
-</​code>​ 
-針對Event物件的調整,只要改為Base64Text並移除原本的@JsonSerialize與@JsonDeserialize。setMessage部分接受Base64Text與String型態以便於Client操作,這裡需要特別宣告@JsonSetter(value="​message"​)以避免Jackson找錯set method: 
-<code java> 
-public class Event { 
- private Date date; 
-  
- private Base64Text message; 
-  
- public Date getDate(){ 
- return date; 
- } 
-  
- public void setDate(Date date){ 
- this.date = date; 
- } 
-  
- public Base64Text getMessage(){ 
- return message; 
- } 
- 
- @JsonSetter(value="​message"​) 
- public void setMessage(Base64Text message){ 
- this.message = message; 
- } 
-  
- public void setMessage(String message){ 
- this.message = new Base64Text(message);​ 
- } 
-} 
-</​code>​ 
-=== Json === 
-針對Json部分,我希望顯示原始內容且不需要顯示encoding種類,我會需要寫一個Serializer:​ 
-<code java> 
-public class JsonBase64TextSerializer extends StdSerializer<​Base64Text>​ { 
- private static final long serialVersionUID = 1L; 
- protected JsonBase64TextSerializer() { 
- super(Base64Text.class);​ 
- } 
- 
- @Override 
- public void serialize(Base64Text value, JsonGenerator gen, 
- SerializerProvider provider) throws IOException { 
- gen.writeString(value.getValue());​ 
- } 
-} 
-</​code>​ 
-接著我們可以對ObjectMapper註冊module,告訴它針對Base64Text所要使用的Serializer:​ 
-<code java> 
-@Test 
-public void testUnicodeJson() throws Exception { 
- ObjectMapper mapper = new ObjectMapper();​ 
- 
- SimpleModule m = new SimpleModule("​test"​);​ 
- m.addSerializer(Base64Text.class,​ new JsonBase64TextSerializer());​ 
- mapper.registerModule(m);​ 
-  
- Event e = new Event(); 
- String msg = "​test"​ + (char) 1 + (char) 2 + (char) 3; 
- e.setMessage(msg);​ 
- 
- String ret = mapper.writeValueAsString(e);​ 
- System.out.println(ret);​ 
- Event newEvent = mapper.readValue(ret,​ Event.class);​ 
- assertEquals(msg,​ newEvent.getMessage().getValue());​ 
-} 
-</​code>​ 
-這個測試輸出如下: ​ 
-<​code>​ 
-{"​date":​null,"​message":"​test\u0001\u0002\u0003"​} 
-</​code>​ 
-=== Xml === 
-xml部分需要Serializer與Deserializer,我們先看看test code: 
-<code java> 
-@Test 
-public void testUnicodeXml() throws Exception { 
- XmlMapper mapper = new XmlMapper();​ 
-  
- SimpleModule m = new SimpleModule();​ 
- m.addSerializer(Base64Text.class,​ new Base64TextSerializer());​ 
- m.addDeserializer(Base64Text.class,​ new Base64TextDeserializer());​ 
- mapper.registerModule(m);​ 
- 
- Event e = new Event(); 
- String msg = "​test"​ + (char) 1 + (char) 2 + (char) 3; 
- e.setMessage(msg);​ 
- 
- tring ret = mapper.writeValueAsString(e);​ 
- System.out.println(ret);​ 
- Event newEvent = mapper.readValue(ret,​ Event.class);​ 
- assertEquals(msg,​ newEvent.getMessage().getValue());​ 
-} 
-</​code>​ 
-流程與json部分類似,只差在多註冊Deserializer,而輸出會是:​ 
-<​code>​ 
-<​Event><​date/><​message type="​base64">​dGVzdAECAw==</​message></​Event>​ 
-</​code>​ 
-Serializer如下,處理type與value的部分:​ 
-<code java> 
-public class Base64TextSerializer extends StdSerializer<​Base64Text>​ { 
- private static final long serialVersionUID = 1L; 
- 
- public Base64TextSerializer() { 
- super(Base64Text.class);​ 
- } 
-  
- @Override 
- public void serialize(Base64Text value, JsonGenerator jgen, 
- SerializerProvider provider) throws IOException { 
- ToXmlGenerator xgen = (ToXmlGenerator) jgen; 
-  
- xgen.writeStartObject();​ 
- xgen.setNextIsAttribute(true);​ 
- xgen.writeFieldName("​type"​);​ 
- xgen.writeString(value.getEncoding());​ 
- xgen.setNextIsAttribute(false);​ 
-  
- xgen.setNextIsUnwrapped(true);​ 
- xgen.writeFieldName("​value"​);​ 
- xgen.writeObject(value.getEncodingValue());​ 
- xgen.setNextIsUnwrapped(false);​ 
-  
- xgen.writeEndObject();​ 
- } 
-} 
-</​code>​ 
-Deserializer部分會將encode的內容decode再塞回Base64Text物件中,做法如下:​ 
-<code java> 
-public class Base64TextDeserializer extends StdDeserializer<​Base64Text>​ { 
- 
- private static final long serialVersionUID = 1L; 
- 
- public Base64TextDeserializer() { 
-         super(Base64Text.class);​ 
-     } 
- 
- @Override 
- public Base64Text deserialize(JsonParser parser, DeserializationContext context) 
- throws IOException,​ JsonProcessingException { 
- String text = parser.getValueAsString();​ 
- Base64Text encodingValue = Base64Text.decode(text);​ 
- return encodingValue;​ 
- } 
-} 
-</​code>​ 
-目前提供這兩種方式給大家參考。 
-===== Reference ===== 
-  * [[https://​en.wikipedia.org/​wiki/​Valid_characters_in_XML|Valid_characters_in_XML]] 
-  * [[https://​zh.wikipedia.org/​wiki/​Unicode#​XML.E5.92.8CUnicode|XML & Unicode]] 
-  * [[http://​docs.aws.amazon.com/​AmazonSimpleDB/​latest/​DeveloperGuide/​InvalidCharacters.html|Amazon Simple DB - InvalidCharacters]] 
-  * [[http://​stackoverflow.com/​questions/​19847094/​jackson-xml-annotations-string-element-with-attribute|jackson-xml-annotations-string-element-with-attribute]] 
-=====    ===== 
----- 
-\\ 
-~~DISQUS~~