/*******************************************************************************
* Copyright (c) 2005, 2014 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.examples.showcase.demos.utilities.json;
import static org.assertj.core.api.Assertions.*;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.junit.Test;
import org.springside.modules.mapper.JsonMapper;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
/**
* 演示Jackson的基本使用方式及大量的特殊Feature.
*
* @author calvin
*/
public class JsonDemo {
private static JsonMapper mapper = JsonMapper.nonDefaultMapper();
// // 基本操作 演示 ////
/**
* 序列化对象/集合到Json字符串.
*/
@Test
public void toJson() throws Exception {
// Bean
TestBean bean = new TestBean("A");
String beanString = mapper.toJson(bean);
System.out.println("Bean:" + beanString);
assertThat(beanString).isEqualTo("{\"name\":\"A\"}");
// Map
Map<String, Object> map = Maps.newLinkedHashMap();
map.put("name", "A");
map.put("age", 2);
String mapString = mapper.toJson(map);
System.out.println("Map:" + mapString);
assertThat(mapString).isEqualTo("{\"name\":\"A\",\"age\":2}");
// List<String>
List<String> stringList = Lists.newArrayList("A", "B", "C");
String listString = mapper.toJson(stringList);
System.out.println("String List:" + listString);
assertThat(listString).isEqualTo("[\"A\",\"B\",\"C\"]");
// List<Bean>
List<TestBean> beanList = Lists.newArrayList(new TestBean("A"), new TestBean("B"));
String beanListString = mapper.toJson(beanList);
System.out.println("Bean List:" + beanListString);
assertThat(beanListString).isEqualTo("[{\"name\":\"A\"},{\"name\":\"B\"}]");
// Bean[]
TestBean[] beanArray = new TestBean[] { new TestBean("A"), new TestBean("B") };
String beanArrayString = mapper.toJson(beanArray);
System.out.println("Array List:" + beanArrayString);
assertThat(beanArrayString).isEqualTo("[{\"name\":\"A\"},{\"name\":\"B\"}]");
}
/**
* 从Json字符串反序列化对象/集合.
*/
@Test
public void fromJson() throws Exception {
// Bean
String beanString = "{\"name\":\"A\"}";
TestBean bean = mapper.fromJson(beanString, TestBean.class);
System.out.println("Bean:" + bean);
// Map
String mapString = "{\"name\":\"A\",\"age\":2}";
Map<String, Object> map = mapper.fromJson(mapString, HashMap.class);
System.out.println("Map:");
for (Entry<String, Object> entry : map.entrySet()) {
System.out.println(entry.getKey() + " " + entry.getValue());
}
// List<String>
String listString = "[\"A\",\"B\",\"C\"]";
List<String> stringList = mapper.fromJson(listString, List.class);
System.out.println("String List:");
for (String element : stringList) {
System.out.println(element);
}
// List<Bean>
String beanListString = "[{\"name\":\"A\"},{\"name\":\"B\"}]";
JavaType beanListType = mapper.contructCollectionType(List.class, TestBean.class);
List<TestBean> beanList = mapper.fromJson(beanListString, beanListType);
System.out.println("Bean List:");
for (TestBean element : beanList) {
System.out.println(element);
}
}
/**
* 测试三种不同的Inclusion风格.
*/
@Test
public void threeTypeInclusion() {
TestBean bean = new TestBean("A");
// 打印全部属性
JsonMapper normalMapper = new JsonMapper();
assertThat(normalMapper.toJson(bean)).isEqualTo(
"{\"name\":\"A\",\"defaultValue\":\"hello\",\"nullValue\":null}");
// 不打印nullValue属性
JsonMapper nonEmptyMapper = JsonMapper.nonEmptyMapper();
assertThat(nonEmptyMapper.toJson(bean)).isEqualTo("{\"name\":\"A\",\"defaultValue\":\"hello\"}");
// 不打印默认值未改变的nullValue与defaultValue属性
JsonMapper nonDefaultMaper = JsonMapper.nonDefaultMapper();
assertThat(nonDefaultMaper.toJson(bean)).isEqualTo("{\"name\":\"A\"}");
}
/*
* 测试类似Jaxb的常用annotaion,如properName,ignore,propertyOrder
*/
@Test
public void jacksonAnnoation() {
TestBean2 testBean = new TestBean2(1, "foo", 18);
// 结果name属性输出在前,且被改名为productName,且age属性被ignore
assertThat(mapper.toJson(testBean)).isEqualTo("{\"productName\":\"foo\",\"id\":1}");
}
/*
* 测试直接使用Jaxb的annotaion
*/
@Test
public void jaxbAnnoation() {
JsonMapper newMapper = new JsonMapper();
newMapper.enableJaxbAnnotation();
TestBean3 testBean = new TestBean3(1, "foo", 18);
// 结果name属性输出在前,且被改名为productName,且age属性被ignore
assertThat(newMapper.toJson(testBean)).isEqualTo("{\"productName\":\"foo\",\"id\":1}");
}
// 调转顺序
@JsonPropertyOrder({ "name", "id" })
public static class TestBean2 {
public long id;
@JsonProperty("productName")
public String name;
@JsonIgnore
public int age;
public TestBean2() {
}
public TestBean2(long id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
}
// 调转顺序
@XmlType(propOrder = { "name", "id" })
public static class TestBean3 {
public long id;
@XmlElement(name = "productName")
public String name;
@XmlTransient
public int age;
public TestBean3() {
}
public TestBean3(long id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
}
/**
* 更新一個已存在Bean,JSON字符串裡只含有Bean的部分屬性,只覆蓋这部分的屬性.
*/
@Test
public void updateBean() {
String jsonString = "{\"name\":\"A\"}";
TestBean bean = new TestBean();
bean.setDefaultValue("Foobar");
mapper.update(jsonString, bean);
// name被赋值
assertThat(bean.getName()).isEqualTo("A");
// DefaultValue不在Json串中,依然保留。
assertThat(bean.getDefaultValue()).isEqualTo("Foobar");
}
/**
* 測試輸出jsonp格式內容.
*/
@Test
public void jsonp() {
TestBean bean = new TestBean("foo");
assertThat(mapper.toJsonP("callback", bean)).isEqualTo("callback({\"name\":\"foo\"})");
}
/**
* 演示用的Bean, 主要演示不同風格的Mapper對Null值,初始化後沒改變過的屬性值的處理.
*/
public static class TestBean {
private String name;
private String defaultValue = "hello"; // 默认值没被修改过的属性,可能会不序列化
private String nullValue = null; // 空值的据行,可能会不序列化
public TestBean() {
}
public TestBean(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDefaultValue() {
return defaultValue;
}
public void setDefaultValue(String defaultValue) {
this.defaultValue = defaultValue;
}
public String getNullValue() {
return nullValue;
}
public void setNullValue(String nullValue) {
this.nullValue = nullValue;
}
@Override
public String toString() {
return "TestBean [defaultValue=" + defaultValue + ", name=" + name + ", nullValue=" + nullValue + "]";
}
}
// //特殊数据类型演示////
/**
* 测试对枚举的序列化.
*/
@Test
public void enumType() {
// toJSon默認使用enum.name()
assertThat(mapper.toJson(TestEnum.One)).isEqualTo("\"One\"");
// fromJson使用enum.name()或enum.order()
assertThat(mapper.fromJson("\"One\"", TestEnum.class)).isEqualTo(TestEnum.One);
assertThat(mapper.fromJson("0", TestEnum.class)).isEqualTo(TestEnum.One);
// 使用enum.toString(), 注意配置必須在所有讀寫動作之前調用.
// 建议toString()使用index数字属性,比enum.name()节约了空间,比enum.order()则不会有顺序随时改变不确定的问题。
JsonMapper newMapper = new JsonMapper();
newMapper.enableEnumUseToString();
assertThat(newMapper.toJson(TestEnum.One)).isEqualTo("\"1\"");
assertThat(newMapper.fromJson("\"1\"", TestEnum.class)).isEqualTo(TestEnum.One);
}
/**
* 枚舉類型的演示Bean.
*/
public static enum TestEnum {
One(1), Two(2), Three(3);
private final int index;
TestEnum(int index) {
this.index = index;
}
@Override
public String toString() {
return String.valueOf(index);
}
}
/**
* 测试对日期的序列化,日期默认以Timestamp方式存储,也可以用2.0后也可以用@JsonFormat在属性上格式化.
* 但JodaTime仍然只支持Timestamp形式, 或调用JodaTime.toString().
*/
@Test
public void dateType() {
mapper.getMapper().registerModule(new JodaModule());
Date date = new Date();
DateTime dateTime = new DateTime(date);
String timestampString = String.valueOf(date.getTime());
String format = "yyyy-MM-dd HH:mm:ss";
String formatedString = new DateTime(date).toString(format);
DateBean dateBean = new DateBean();
dateBean.startDate = date;
dateBean.endDate = date;
dateBean.dateTime = dateTime;
// to json
String expectedJson = "{\"startDate\":" + timestampString + ",\"endDate\":\"" + formatedString
+ "\",\"dateTime\":" + timestampString + "}";
assertThat(mapper.toJson(dateBean)).isEqualTo(expectedJson);
// from json
Date expectedEndDate = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss").parseDateTime(formatedString).toDate();
DateBean resultBean = mapper.fromJson(expectedJson, DateBean.class);
assertThat(resultBean.startDate).isEqualTo(date);
assertThat(resultBean.endDate).isEqualTo(expectedEndDate);
}
public static class DateBean {
// 默认timestamp存储
public Date startDate;
// 按annotation中的日期格式存储。
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+08:00")
public Date endDate;
public DateTime dateTime;
}
/**
* 测试传入空对象,空字符串,Empty的集合,"null"字符串的结果.
*/
@Test
public void nullAndEmpty() {
// toJson测试 //
// Null Bean
TestBean nullBean = null;
String nullBeanString = mapper.toJson(nullBean);
assertThat(nullBeanString).isEqualTo("null");
// Empty List
List<String> emptyList = Lists.newArrayList();
String emptyListString = mapper.toJson(emptyList);
assertThat(emptyListString).isEqualTo("[]");
// fromJson测试 //
// Null String for Bean
TestBean nullBeanResult = mapper.fromJson(null, TestBean.class);
assertThat(nullBeanResult).isNull();
nullBeanResult = mapper.fromJson("null", TestBean.class);
assertThat(nullBeanResult).isNull();
// Null/Empty String for List
List nullListResult = mapper.fromJson(null, List.class);
assertThat(nullListResult).isNull();
nullListResult = mapper.fromJson("null", List.class);
assertThat(nullListResult).isNull();
nullListResult = mapper.fromJson("[]", List.class);
assertThat(nullListResult).isEmpty();
}
// // 高级应用 ////
/**
* 測試父子POJO間的循環引用.
*/
@Test
public void cycleReferenceBean() {
// 初始化对象关系,parent的children里含有 child1,child2, child1/child2的parent均指向parent.
CycleReferenceBean parent = new CycleReferenceBean("parent");
CycleReferenceBean child1 = new CycleReferenceBean("child1");
child1.setParent(parent);
parent.getChildren().add(child1);
CycleReferenceBean child2 = new CycleReferenceBean("child2");
child2.setParent(parent);
parent.getChildren().add(child2);
// 序列化是, json字符串裡children中的child1/child2都不包含到parent的屬性
String jsonString = "{\"name\":\"parent\",\"children\":[{\"name\":\"child1\"},{\"name\":\"child2\"}]}";
assertThat(mapper.toJson(parent)).isEqualTo(jsonString);
// 注意此時如果單獨序列化child1,也不會打印parent,信息將丟失。
assertThat(mapper.toJson(child1)).isEqualTo("{\"name\":\"child1\"}");
// 反向序列化时,Json已很聪明的把parent填入child1/child2中.
CycleReferenceBean parentResult = mapper.fromJson(jsonString, CycleReferenceBean.class);
assertThat(parentResult.getChildren().get(0).getParent().getName()).isEqualTo("parent");
// 单独反序列化child1,当然parent也是空
CycleReferenceBean child1Result = mapper.fromJson("{\"name\":\"child1\"}", CycleReferenceBean.class);
assertThat(child1Result.parent).isNull();
assertThat(child1Result.getName()).isEqualTo("child1");
}
/**
* 父子POJO間的循環引用的演示Bean,@JsonBackReference 与 @JsonManagedReference 是关键.
*/
public static class CycleReferenceBean {
private String name;
private CycleReferenceBean parent;
private List<CycleReferenceBean> children = Lists.newArrayList();
public CycleReferenceBean() {
}
public CycleReferenceBean(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// 注意getter與setter都要添加annotation
@JsonBackReference
public CycleReferenceBean getParent() {
return parent;
}
@JsonBackReference
public void setParent(CycleReferenceBean parent) {
this.parent = parent;
}
@JsonManagedReference
public List<CycleReferenceBean> getChildren() {
return children;
}
@JsonManagedReference
public void setChildren(List<CycleReferenceBean> children) {
this.children = children;
}
}
/**
* 測試可擴展Bean.
* 可扩展Bean的设计会混合一些的固定属性和用一个Map<String,object>存放的扩展属性。
* 通常,哪那些是固定属性,哪些是扩展属性,在应用不断演进中是不断变化的。
* Jackson支持将所有属性都序列化成平行的属性列表,没有固定属性与Map中属性的区别,然后智能的将不在固定列的属性都丢到被@JsonAnyGetter/Setter注释的Map里面去。
*/
@Test
public void extensibleBean() {
// 一个没有区分是变量还是Map的普通JSON字符串.
String jsonString = "{\"name\" : \"Foobar\",\"age\" : 37,\"occupation\" : \"coder man\"}";
ExtensibleBean extensibleBean = mapper.fromJson(jsonString, ExtensibleBean.class);
// 固定属性
assertThat(extensibleBean.getName()).isEqualTo("Foobar");
assertThat(extensibleBean.getProperties().get("name")).isNull();
// 可扩展属性
assertThat(extensibleBean.getProperties().get("occupation")).isEqualTo("coder man");
}
/**
* 演示用的可擴展Bean.@JsonAnySetter与@JsonAnyGetter是关键.
*/
public static class ExtensibleBean {
// 固定属性
private String name;
// 扩展属性
private final Map<String, String> properties = Maps.newHashMap();
public ExtensibleBean() {
}
@JsonAnySetter
public void add(String key, String value) {
properties.put(key, value);
}
@JsonAnyGetter
public Map<String, String> getProperties() {
return properties;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* 同一种POJO,在不同场景下可能需要序列化不同的属性组,Jackson支持使用View来定义.
*/
@Test
public void multiViewBean() throws IOException {
MultiViewBean multiViewBean = new MultiViewBean();
multiViewBean.setName("Foo");
multiViewBean.setAge(16);
multiViewBean.setOtherValue("others");
// public view
ObjectWriter publicWriter = mapper.getMapper().writerWithView(Views.Public.class);
assertThat(publicWriter.writeValueAsString(multiViewBean)).isEqualTo(
"{\"name\":\"Foo\",\"otherValue\":\"others\"}");
// internal view
ObjectWriter internalWriter = mapper.getMapper().writerWithView(Views.Internal.class);
assertThat(internalWriter.writeValueAsString(multiViewBean))
.isEqualTo("{\"age\":16,\"otherValue\":\"others\"}");
}
public static class Views {
static class Public {
}
static class Internal {
}
}
/**
* 演示序列化不同View不同属性的Bean.
*/
public static class MultiViewBean {
private String name;
private int age;
private String otherValue;
@JsonView(Views.Public.class)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@JsonView(Views.Internal.class)
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getOtherValue() {
return otherValue;
}
public void setOtherValue(String otherValue) {
this.otherValue = otherValue;
}
}
// //自定制行为////
/**
* 测试自定义转换器,整体感觉稍显复杂。这里是将Money和Long互转.
*/
@Test
public void customConverter() {
JsonMapper newMapper = JsonMapper.nonEmptyMapper();
SimpleModule moneyModule = new SimpleModule("MoneyModule");
moneyModule.addSerializer(new MoneySerializer());
moneyModule.addDeserializer(Money.class, new MoneyDeserializer());
newMapper.getMapper().registerModule(moneyModule);
// tojson
User user = new User();
user.setName("foo");
user.setSalary(new Money(1.2));
String jsonString = newMapper.toJson(user);
assertThat(jsonString).isEqualTo("{\"name\":\"foo\",\"salary\":\"1.2\"}");
// from
User resultUser = newMapper.fromJson(jsonString, User.class);
assertThat(resultUser.getSalary().value).isEqualTo(1.2);
}
public class MoneySerializer extends StdSerializer<Money> {
public MoneySerializer() {
super(Money.class);
}
@Override
public void serialize(Money value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeString(value.toString());
}
}
public class MoneyDeserializer extends StdDeserializer<Money> {
public MoneyDeserializer() {
super(Money.class);
}
@Override
public Money deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
return Money.valueOf(jp.getText());
}
}
public static class Money {
private final Double value;
public Money(Double value) {
this.value = value;
}
public static Money valueOf(String value) {
Double srcValue = Double.valueOf(value);
return new Money(srcValue);
}
@Override
public String toString() {
return value.toString();
}
}
/**
* 包含Money属性的对象.
*/
public static class User {
private String name;
private Money salary;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Money getSalary() {
return salary;
}
public void setSalary(Money salary) {
this.salary = salary;
}
}
/**
* 测试修改 属性名策略。
*/
@Test
public void customPropertyNaming() throws JsonMappingException {
TestBean bean = new TestBean("foo");
bean.setDefaultValue("bar");
JsonMapper newMapper = JsonMapper.nonEmptyMapper();
newMapper.getMapper().setPropertyNamingStrategy(new LowerCaseNaming());
assertThat(newMapper.toJson(bean)).isEqualTo("{\"name\":\"foo\",\"defaultvalue\":\"bar\"}");
}
public static class LowerCaseNaming extends PropertyNamingStrategy {
@Override
public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
return defaultName.toLowerCase();
}
}
}