JSON 框架系列之 Jackson 总结
https://github.com/FasterXML/jackson
https://github.com/FasterXML/jackson-docs
Core modules
Core modules are the foundation on which extensions (modules) build upon. There are 3 such modules currently (as of Jackson 2.x):
- Streaming (docs) (“jackson-core”) defines low-level streaming API, and includes JSON-specific implementations
- Annotations (docs) (“jackson-annotations”) contains standard Jackson annotations
- Databind (docs) (“jackson-databind”) implements data-binding (and object serialization) support on
streaming
package; it depends both onstreaming
andannotations
packages
Annotations
https://github.com/FasterXML/jackson-annotations
这个页面列出了所有 Jackson 2.0 通用注解,按功能分组。
常用注解如下:
@JsonProperty
@JsonIgnore
@JsonIgnoreProperties
@JsonInclude
@JsonFormat
@JsonSerialize
@JsonDeserialize
- …
Databind
For all data-binding, we need a com.fasterxml.jackson.databind.ObjectMapper
instance:
1 | ObjectMapper mapper = new ObjectMapper(); // create once, reuse |
参考:
《ObjectMapper,别再像个二货一样一直 new 了!》
Configuration
https://github.com/FasterXML/jackson-databind/wiki/#reference-manual
POJO
The most common usage is to take piece of JSON, and construct a Plain Old Java Object (“POJO”) out of it.
Serialization:
1 | MyValue value = mapper.readValue(new File("data.json"), MyValue.class); |
Deserialization:
1 | mapper.writeValue(new File("result.json"), myResultObject); |
Generic Collections
You can also handle JDK List
s, Map
s.
Serialization:
1 | Map<String, Integer> scoreByName = mapper.readValue(jsonSource, Map.class); |
Deserialization:
1 | // and can obviously write out as well |
Generic Type
如果需要将 JSON 字符串反序列化为泛型,有两种方式:
方式一:TypeReference
方式一:使用 com.fasterxml.jackson.core.type.TypeReference<T>
:
This generic abstract class is used for obtaining full generics type information by sub-classing; it must be converted to
ResolvedType
implementation (implemented byJavaType
from “databind” bundle) to be used. Class is based on ideas from http://gafter.blogspot.com/2006/12/super-type-tokens.html, Additional idea (from a suggestion made in comments of the article) is to require bogus implementation ofComparable
(any such generic interface would do, as long as it forces a method with generic type to be implemented). to ensure that a Type argument is indeed given.Usage is by sub-classing: here is one way to instantiate reference to generic type
List<Integer>
:
1 TypeReference ref = new TypeReference<List<Integer>>() { };which can be passed to methods that accept TypeReference, or resolved using
TypeFactory
to obtainResolvedType
.
代码如下:
1 | TypeReference<RespDTO<XxxRespDTO>> typeRef = new TypeReference<RespDTO<XxxRespDTO>>() {}; |
方式二:JavaType
方式二:使用 com.fasterxml.jackson.databind.JavaType
:
Base class for type token classes used both to contain information and as keys for deserializers.
Instances can (only) be constructed by
com.fasterxml.jackson.databind.type.TypeFactory
.
JavaType
的继承结构如下图:
代码如下:
1 | JavaType valueType = mapper.getTypeFactory().constructParametricType(RespDTO.class, XxxRespDTO.class); |
JavaType
的调试结果如下图,其值为 JavaType
的子类 CollectionType
:
Tree Model (JsonNode)
Tree Model can be more convenient than data-binding, especially in cases where structure is highly dynamic, or does not map nicely to Java classes.
com.fasterxml.jackson.databind.JsonNode
表示一个 JSON 树节点,可以通过 ObjectMapper#readTree
方法反序列化出来,也可以通过 JsonNode
的子类 API 自定义构建:
构建代码如下:
1 | JsonNode jsonNode = |
JsonNode
构建完成后,可以灵活的读取其值,例如:
1 | // [value0, value1] |
也可以修改其值:
1 | ((ObjectNode) jsonNode).put("key", "value"); |
使用场景
一、对接口响应的 JSON 原文进行验签:
1 | { |
1 | JsonNode jsonNode = objectMapper.readTree(responseJson); |
二、<Compare Two JSON Objects with Jackson>
using the JsonNode.equals method. The
equals()
method performs a full (deep) comparison.
例子
本例中,我们需要获取以下两个方法的泛型返回值中的实际类型参数 XxxRespDTO
的 Class
类型,以用于 JSON 转换:
1 | public interface ApiService { |
定义一个方法,用于转换 JSON:
1 | private Object getObject(Method method, String json) { |
这种用法常常出现在框架之中。下面来看下调试效果:
接口一
下图展示了变量 returnType
为参数化类型 ParameterizedType
,其实际类型参数 type
为 Class
类型,值为 XxxRespDTO
:
接口二
下图展示了变量 returnType
的实际类型参数 type
与接口一为 Class
类型不同,接口二为 ParameterizedType
参数化类型,值为 List<XxxRespDTO>
:
常见报错
Unrecognized field, not marked as ignorable
该错误的意思是说,不能够识别的字段没有标示为可忽略。出现该问题的原因就是 JSON 中包含了目标 Java 对象没有的属性。
解决方案:
保证传入的 JSON 串不包含目标对象的没有的属性。
On deserialization,
@JsonIgnoreProperties(ignoreUnknown=true)
ignores properties that don’t have getter/settersDeserialization Features
全局配置:1
2// 配置该 `objectMapper` 在反序列化时,忽略目标对象没有的属性。
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
缺少默认构造方法
问题:
1 | com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.***.RespBody` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator) |
解决方案:
- POJO 加上
@NoArgsConstructor
Using Java inner classes for Jackson serialization
https://dev.to/pavel_polivka/using-java-inner-classes-for-jackson-serialization-4ef8
Google Gson
https://github.com/google/gson
1 | JsonObject jsonObject = new JsonObject(); |
Deserialization to Generic Type: com.google.gson.reflect.TypeToken
1 | RespDTO<XxxRespDTO> data = new Gson().fromJson(json, new TypeToken<RespDTO<XxxRespDTO>>() {}.getType()); |
参考
https://www.baeldung.com/category/json/jackson/
https://github.com/qidawu/java-api-test/tree/master/src/main/java/json