轻量级的数据交换格式,完全独立于编程语言的文本格式来存储和表示数据,简洁,清晰,容易解析,提高网络传输效率
json常用的解析库,jackson(SpringMVC),fastjson(阿里),gson(Google)
json的高级应用
https://www.cnblogs.com/EasonJim/p/8098921.html
springboot中使用jackson
https://blog.csdn.net/swordcenter/article/details/72368905
jackson-core
):定义底层处理流的API:JsonPaser和JsonGenerator等,并包含特定于json的实现jackson-annotations
):包含标准的Jackson注解jackson-databind
):在streaming包上实现数据绑定(和对象序列化)支持;它依赖于上面的两个模块,也是Jackson的高层API(如ObjectMapper)所在的模块jackson-databind 依赖 jackson-core 和 jackson-annotations,添加jackson-databind即可
jackson和fastjson 配置基本差不多,但是jackson的没有module的配置
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.5.3version>
dependency>
springboot中不需要导入包,web启动器里面包含了
ObjectMapper objectMapper = new ObjectMapper();
String carJson =
"{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";
Car car = objectMapper.readValue(carJson, Car.class);
ObjectMapper objectMapper = new ObjectMapper();
String carJson =
"{ \"brand\" : \"Mercedes\", \"doors\" : 4 }";
Reader reader = new StringReader(carJson);
Car car = objectMapper.readValue(reader, Car.class);
ObjectMapper objectMapper = new ObjectMapper();
File file = new File("data/car.json");
Car car = objectMapper.readValue(file, Car.class);
可以通过URL(java.net.URL)从JSON读取对象,如下所示:
ObjectMapper objectMapper = new ObjectMapper();
URL url = new URL("file:data/car.json");
Car car = objectMapper.readValue(url, Car.class);
ObjectMapper objectMapper = new ObjectMapper();
InputStream input = new FileInputStream("data/car.json");
Car car = objectMapper.readValue(input, Car.class);
ObjectMapper objectMapper = new ObjectMapper();
String carJson =
"{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";
byte[] bytes = carJson.getBytes("UTF-8");
Car car = objectMapper.readValue(bytes, Car.class);
String jsonArray = "[{\"brand\":\"ford\"}, {\"brand\":\"Fiat\"}]";
ObjectMapper objectMapper = new ObjectMapper();
Car[] cars2 = objectMapper.readValue(jsonArray, Car[].class);
String jsonArray = "[{\"brand\":\"ford\"}, {\"brand\":\"Fiat\"}]";
ObjectMapper objectMapper = new ObjectMapper();
List<Car> cars1 = objectMapper.readValue(jsonArray, new TypeReference<List<Car>>(){});
String jsonObject = "{\"brand\":\"ford\", \"doors\":5}";
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> jsonMap = objectMapper.readValue(jsonObject,
new TypeReference<Map<String,Object>>(){});
https://www.hiczp.com/spring/zai-springboot-zhong-zheng-que-zhu-ce-jacksonmodule.html
https://www.cnblogs.com/scar1et/articles/14134024.html
忽略未知的JSON字段
objectMapper.configure(
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
不允许基本类型为null
objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, true);
详细配置请看 SerializerFeature配置
需要注意的是对于第二种通过配置SerializationConfig和DeserializationConfig方式只能启动/禁止自动检测,无法修改我们所需的可见级别
有时候对每个实例进行可见级别的注解可能会非常麻烦,这时候我们需要配置一个全局的可见级别,通过objectMapper.setVisibilityChecker()来实现,默认的VisibilityChecker实现类为VisibilityChecker.Std,这样可以满足实现复杂场景下的基础配置。
也有一些实用简单的可见级别配置,比如:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) // auto-detect all member fields
.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE) // but only public getters
.setVisibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.NONE) // and none of "is-setters"
;
源码:
public ObjectMapper setVisibility(PropertyAccessor forMethod, Visibility visibility)
public enum PropertyAccessor {
GETTER,
SETTER,
CREATOR,
FIELD,
IS_GETTER,
NONE,
ALL;
// ...
}
public static enum Visibility {
ANY,
NON_PRIVATE,
PROTECTED_AND_PUBLIC,
PUBLIC_ONLY,
NONE,
DEFAULT;
// ...
}
你也可以通过下面方式来禁止所有的自动检测功能
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibilityChecker(objectMapper.getVisibilityChecker().with(JsonAutoDetect.Visibility.NONE));
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
public static enum DefaultTyping {
JAVA_LANG_OBJECT,
OBJECT_AND_NON_CONCRETE,
NON_CONCRETE_AND_ARRAYS,
NON_FINAL,
EVERYTHING;
private DefaultTyping() {
}
}
JAVA_LANG_OBJECT: 对象属性类型为Object时生效;
OBJECT_AND_NON_CONCRETE: 当对象属性类型为==Object或者非具体类型(抽象类和接口)==时生效;
NON_CONCRETE_AND_ARRAYS: 同上, 另外所有的数组元素的类型都是非具体类型或者对象类型;
NON_FINAL: 对所有非final类型或者非final类型元素的数组。
因此,当开启DefaultTyping后,会开发者在反序列化时指定要还原的类,过程中调用其构造方法,setter方法或某些特殊的getter方法,当这些方法中存在一些危险操作时就造成了代码执行。
NON_FINAL,包含即将被序列化的类里的全部、非final的属性,就是相当于整个类、除final外的的属性信息都需要被序列化和反序列化。
enableDefaultTyping的NON_FINAL这个功能涉及到java著名的反序列化漏洞。各位系统之间调用数据的项目还是慎重使用.
影响范围
jackson-databind < 2.9.10.4
JDK < 6u201、7u191、8u182、11.0.1(LDAP)
@Getter
@Setter
@ToString
public class Money {
private double remain;
}
@Getter
@Setter
@ToString
public class PersonInfo {
private String name;
private int id;
}
@Getter
@Setter
@ToString
public class Account {
private Money money;
private PersonInfo personInfo;
}
序列化之后的 json
{
"money": {
"remain": 1030.0
},
"personInfo": {
"name": "tangbaobao",
"id": 1
}
}
使用@JsonUnwrapped 来 扁平对象
@Getter
@Setter
@ToString
public class Account {
@JsonUnwrapped
private Money money;
@JsonUnwrapped
private PersonInfo personInfo;
}
结果
{
"remain": 1030.0,
"name": "tangbaobao",
"id": 1
}
@JsonIgnore
private String fullName;
序列化和反序列化都忽略该属性
@JsonIgnoreProperties(value = {"fullName", "comment"})
get为true的时候说明字段允许序列化,反序列的时候忽略该字段
@JsonIgnoreProperties(value = {"usname","password"}, allowSetters = true)
set为true说明字段允许反序列化,序列化的时候忽略该字段
@JsonIgnoreProperties(value = {"usname","password"}, allowGetters = true)
@JsonProperty("bookCategory")
private String category;
@JsonProperty("bookCategory")
public String getCategory() {
return category;
}
@JsonProperty("bookCategory")
public void setCategory(String category) {
this.category = category;
}
// 只能序列化。所以反序列化,json对象变成对象的时候 对应的属性不会有值
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private String name;
// 反序列化,json对象变成对象的时候,会将对应的属性赋值
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
Access.WRITE_ONLY:逻辑属性的可见性仅在我们将JSON数据设置为Java对象时即在反序列化时才可用
Access.READ_ONLY:逻辑属性的可见性仅在我们从Java对象获取JSON数据时才可用,即在序列化时
Access.READ_WRITE:逻辑属性的可见性在序列化和反序列化时都可用。
Access.AUTO:将自动确定逻辑属性的可见性,这是access元素的默认值。
枚举
public enum Gender {
@JsonProperty("male") GENDER_MALE,
@JsonProperty("female") GENDER_FEMALE;
}
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(Gender.GENDER_FEMALE)); // "female"
此注解的作用很大,用于反序列的时候,指定json别名,特别是在对接不同的api的时候,有的接入方的字段是sex,有的是xingbie,有的是gender但是又表示的同一个意思的时候,非常有用
但是在序列化时,即从Java对象获取JSON时,只使用实际的逻辑属性名而不是别名
class Person{
int age;
String name;
@JsonAlias({"xingbie","gender","sex"})
String sex;
}
测试代码如下
@Test
public void JsonAliasTest() throws Exception{
CombineJacksonAnnotation.Person person1 = om.readValue("{\n" +
" \"name\" : \"tom\",\n" +
" \"age\" : 12,\n" +
" \"sex\" : \"female\"\n" +
"}",CombineJacksonAnnotation.Person.class);
CombineJacksonAnnotation.Person person2 = om.readValue("{\n" +
" \"name\" : \"tom\",\n" +
" \"age\" : 12,\n" +
" \"xingbie\" : \"female\"\n" +
"}",CombineJacksonAnnotation.Person.class);
CombineJacksonAnnotation.Person person3 = om.readValue("{\n" +
" \"name\" : \"tom\",\n" +
" \"age\" : 12,\n" +
" \"gender\" : \"female\"\n" +
"}",CombineJacksonAnnotation.Person.class);
System.out.println(om.writeValueAsString(person1));
System.out.println(om.writeValueAsString(person2));
System.out.println(om.writeValueAsString(person3));
}
ut结果如下
{
"name" : "tom",
"age" : 12,
"sex" : "female"
}
{
"name" : "tom",
"age" : 12,
"sex" : "female"
}
{
"name" : "tom",
"age" : 12,
"sex" : "female"
}
@JsonFormat(pattern="yyyy-MM-dd",timezone = "GMT+8")
@JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime gmtCreate;
前端和数据库都是
"gmtCreate": "2021-10-26 23:15:04"
如果出现
java.sql.SQLFeatureNotSupportedException: null
将druid的版本提升到1.2.6
即可
全局设置
/**
* 统一json输出风格
*/
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (int i = 0; i < converters.size(); i++) {
if (converters.get(i) instanceof StringHttpMessageConverter) {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(Charsets.UTF_8);
stringHttpMessageConverter.setWriteAcceptCharset(false);
converters.set(i, stringHttpMessageConverter);
}
if (converters.get(i) instanceof MappingJackson2HttpMessageConverter) {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule("JsonMapSerializer", Version.unknownVersion());
// 对LocalDateTime类,提供统一的序列化方式
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
simpleModule.addSerializer(new LocalDateTimeSerializer(dateTimeFormatter));
objectMapper.registerModule(simpleModule);
// 统一返回数据的输出风格
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper);
converters.set(i, converter);
break;
}
}
}
jackson-module-parameter-names
:此模块能够访问构造函数和方法参数的名称,从而允许省略@JsonProperty
jackson-datatype-jsr310
:支持Java8新增的JSR310时间APIjackson-datatype-jdk8
:除了Java8的时间API外其它的API的支持,如Optional
// 为null的属性不会被序列化
@JsonInclude(JsonInclude.Include.NON_NULL)
public static enum Include {
ALWAYS,
NON_NULL,
NON_ABSENT,
NON_EMPTY,
NON_DEFAULT,
CUSTOM,
USE_DEFAULTS;
private Include() {
}
}
@JsonDeserialize
@JsonSerialize
使用场景一:
前端显示是万元,后端存的时候是元
@JsonSerialize(using = BudgetSerializer.class) // 对应get
@JsonDeserialize(using = BudgetDeserializer.class) // 对应set
private BigDecimal applyBudget;
@Slf4j
public class BudgetSerializer extends JsonSerializer<BigDecimal> {
@Override
public void serialize(BigDecimal s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
BigDecimal format = s;
if (format != null) {
// 元转万元
format = format.divide(new BigDecimal("10000"), 4, BigDecimal.ROUND_HALF_DOWN);
log.debug("元格式化万元:前 {}, 后 {}", s, format);
}
jsonGenerator.writeNumber(format);
}
}
后端需要元
@Slf4j
public class BudgetDeserializer extends JsonDeserializer<BigDecimal> {
@Override
public BigDecimal deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
try {
if (jsonParser == null || jsonParser.getText() == null) {
return null;
}
String s = jsonParser.getText();
BigDecimal format = new BigDecimal(StringUtils.isBlank(s) ? "0" : s);
// 万元转元
format = format.multiply(new BigDecimal("10000"));
log.debug("万元格式化元:前 {}, 后 {}", s, format);
return format;
} catch (Exception e) {
log.error(e.getMessage());
throw new RuntimeException(e);
}
}
}
json变成对象时,即反序列化时,json缺少了一些实体类的属性,此时反序列化该属性会变成null,若不想这样,想要有默认值的时候,则可以使用@JacksonInject
//value 相当于id,之后会用到,
//userInput为FALSE的时候是属性值不覆盖默认值,相当于定死了这个字段的值。默认为true是会覆盖
//@JacksonInject("defaultUsername")如果我们就是为了值为空的时候注入默认的值,这么写就行了
@JacksonInject(value= "defaultUsername", useInput= OptBoolean.FALSE)
private String username;
是否环绕根元素,默认false,如果为true,则默认以类名作为根元素,你也可以通过@JsonRootName来自定义根元素名称
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE,true);
@JsonRootName("myPojo")
public static class TestPOJO{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
该类在序列化成json后类似如下:
{"myPojo":{"name":"aaaa"}}
缩放排列,全局配置不生效
objectMapper.configure(SerializationFeature.INDENT_OUTPUT,true);
{
"a" : "aaa",
"b" : "bbb",
"c" : "ccc",
"d" : "ddd"
}
序列化日期时以timestamps输出,默认true
比如如果一个类中有private Date date;这种日期属性,序列化后为:
{"date" : 1413800730456}
// 若不为true,则为
{"date" : "2014-10-20T10:26:06.604+0000"}
序列化枚举是以toString()来输出,默认false,即默认以name()来输出
objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING,true);
序列化枚举是以ordinal()来输出,默认false
objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_INDEX,true);
举例:
@Test
public void enumTest() throws Exception {
TestPOJO testPOJO = new TestPOJO();
testPOJO.setName("myName");
testPOJO.setMyEnum(TestEnum.ENUM01);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING,false);
objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_INDEX,false);
String jsonStr1 = objectMapper.writeValueAsString(testPOJO);
Assert.assertEquals("{\"myEnum\":\"ENUM01\",\"name\":\"myName\"}",jsonStr1);
ObjectMapper objectMapper2 = new ObjectMapper();
objectMapper2.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING,true);
String jsonStr2 = objectMapper2.writeValueAsString(testPOJO);
Assert.assertEquals("{\"myEnum\":\"enum_01\",\"name\":\"myName\"}",jsonStr2);
ObjectMapper objectMapper3 = new ObjectMapper();
objectMapper3.configure(SerializationFeature.WRITE_ENUMS_USING_INDEX,true);
String jsonStr3 = objectMapper3.writeValueAsString(testPOJO);
Assert.assertEquals("{\"myEnum\":0,\"name\":\"myName\"}",jsonStr3);
}
public static class TestPOJO{
TestPOJO(){}
private TestEnum myEnum;
private String name;
//getters、setters省略
}
public static enum TestEnum{
ENUM01("enum_01"),ENUM02("enum_01"),ENUM03("enum_01");
private String title;
TestEnum(String title) {
this.title = title;
}
@Override
public String toString() {
return title;
}
}
序列化单元素数组时不以数组来输出,默认false
objectMapper.configure(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED,true);
@Test
public void singleElemArraysUnwrap() throws Exception {
TestPOJO testPOJO = new TestPOJO();
testPOJO.setName("myName");
List<Integer> counts = new ArrayList<>();
counts.add(1);
testPOJO.setCounts(counts);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED,false);
String jsonStr1 = objectMapper.writeValueAsString(testPOJO);
Assert.assertEquals("{\"name\":\"myName\",\"counts\":[1]}",jsonStr1);
ObjectMapper objectMapper2 = new ObjectMapper();
objectMapper2.configure(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED,true);
String jsonStr2 = objectMapper2.writeValueAsString(testPOJO);
Assert.assertEquals("{\"name\":\"myName\",\"counts\":1}",jsonStr2);
}
public static class TestPOJO{
private String name;
private List<Integer> counts;
//getters、setters省略
}
序列化Map时对key进行排序操作,默认false
objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS,true);
@Test
public void orderMapBykey() throws Exception {
TestPOJO testPOJO = new TestPOJO();
testPOJO.setName("myName");
Map<String,Integer> counts = new HashMap<>();
counts.put("a",1);
counts.put("d",4);
counts.put("c",3);
counts.put("b",2);
counts.put("e",5);
testPOJO.setCounts(counts);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS,false);
String jsonStr1 = objectMapper.writeValueAsString(testPOJO);
Assert.assertEquals("{\"name\":\"myName\",\"counts\":{\"d\":4,\"e\":5,\"b\":2,\"c\":3,\"a\":1}}",jsonStr1);
ObjectMapper objectMapper2 = new ObjectMapper();
objectMapper2.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS,true);
String jsonStr2 = objectMapper2.writeValueAsString(testPOJO);
Assert.assertEquals("{\"name\":\"myName\",\"counts\":{\"a\":1,\"b\":2,\"c\":3,\"d\":4,\"e\":5}}",jsonStr2);
}
public static class TestPOJO{
private String name;
private Map<String,Integer> counts;
//getters、setters省略
}
序列化char[]时以json数组输出,默认false
objectMapper.configure(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS,true);
@Test
public void charArraysAsJsonArrays() throws Exception {
TestPOJO testPOJO = new TestPOJO();
testPOJO.setName("myName");
char[] counts = new char[]{'a','b','c','d'};
testPOJO.setCounts(counts);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS,false);
String jsonStr1 = objectMapper.writeValueAsString(testPOJO);
Assert.assertEquals("{\"name\":\"myName\",\"counts\":\"abcd\"}",jsonStr1);
ObjectMapper objectMapper2 = new ObjectMapper();
objectMapper2.configure(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS,true);
String jsonStr2 = objectMapper2.writeValueAsString(testPOJO);
Assert.assertEquals("{\"name\":\"myName\",\"counts\":[\"a\",\"b\",\"c\",\"d\"]}",jsonStr2);
}
public static class TestPOJO{
private String name;
private char[] counts;
//getters、setters省略
}
jsonStr1 {"name":"myName","counts":"abcd"}
jsonStr2 {"name":"myName","counts":["a","b","c","d"]}
序列化BigDecimal时之间输出原始数字还是科学计数,默认false,即是否以==toPlainString()==科学计数方式来输出
objectMapper.configure(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS,true);
@Test
public void bigDecimalAsPlain() throws Exception {
TestPOJO testPOJO = new TestPOJO();
testPOJO.setName("myName");
testPOJO.setCount(new BigDecimal("1e20"));
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN,false);
String jsonStr1 = objectMapper.writeValueAsString(testPOJO);
Assert.assertEquals("{\"name\":\"myName\",\"count\":1E+20}",jsonStr1);
ObjectMapper objectMapper2 = new ObjectMapper();
objectMapper2.configure(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN,true);
String jsonStr2 = objectMapper2.writeValueAsString(testPOJO);
Assert.assertEquals("{\"name\":\"myName\",\"count\":100000000000000000000}",jsonStr2);
}
@Target({ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface JacksonAnnotation
{
// for now, a pure tag annotation, no parameters
}}
从注释信息可以看出,此注解是其他所有jackson注解的元注解,打上了此注解的注解表明是jackson注解的一部分
@Target({ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface JacksonAnnotationsInside{
}
@Target({ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface JacksonAnnotation {
}
可以看到,JacksonAnnotation注解也打到了这个元注解上面,此注解也是一个元注解,一般用于将其他的注解一起打包成"组合"注解,虽然说jackson提供了很多的非常实用的注解给我们来用,但是产品的需求是无限的,很多时候,我们需要定义自己的注解,来满足我们的需求。
如下,我们定义了一个@CombineJacksonAnnotation注解,可以打在class上面,实现的功能是
null
的属性@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({"name","age","sex"})
@JacksonAnnotationsInside
public @interface CombineJacksonAnnotation {
@Data
@AllArgsConstructor(staticName = "of")
@CombineJacksonAnnotation
class Person{
int age;
String name;
String sex;
}
}
public static ObjectMapper om = new ObjectMapper();
static {
// 缩放排列输出
om.enable(SerializationFeature.INDENT_OUTPUT);
}
@Test
public void JacksonAnnotationsInsideTest() throws Exception{ System.out.println(om.writeValueAsString(CombineJacksonAnnotation.Person.of(12,"tom",null)));
}
// 输出
{
"name" : "tom",
"age" : 12
}