本文使用 Jackson 2,包括 jackson-annotations-2.4.0.jar、jackson-core-2.4.1.jar 和 jackson-databind-2.4.1.jar 这三个库。 html
貌似太理论的东西,看得人也比较少,都喜欢看实际功能的东西,不过啊,只关注功能、理论太弱的人,基本没前途~java
介绍高级的序列化配置和优化处理条件、各类数据类型以及自定义 Jackson 异常。node
如何使用 Jackson 只序列化一个符合指定的、自定义标准的字段。json
例如,我只想序列化一个正整数,不然,就忽略整个整数。数组
首先,咱们在实体上用 @JsonFilter 注解定义过滤器:缓存
@JsonFilter("myFilter")
public class MyDto {
private int intValue;
public MyDto() {
super();
}
public int getIntValue() {
return intValue;
}
public void setIntValue(int intValue) {
this.intValue = intValue;
}
}
而后,定义咱们本身的 PropertyFilter:并发
PropertyFilter theFilter = new SimpleBeanPropertyFilter() {
@Override
public void serializeAsField
(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer)
throws Exception {
if (include(writer)) {
if (!writer.getName().equals("intValue")) {
writer.serializeAsField(pojo, jgen, provider);
return;
}
int intValue = ((MyDtoWithFilter) pojo).getIntValue();
if (intValue >= 0) {
writer.serializeAsField(pojo, jgen, provider);
}
} else if (!jgen.canOmitFields()) { // since 2.3
writer.serializeAsOmittedField(pojo, jgen, provider);
}
}
@Override
protected boolean include(BeanPropertyWriter writer) {
return true;
}
@Override
protected boolean include(PropertyWriter writer) {
return true;
}
};
这个 filter 包含一个 intValue 字段是否被序列化的逻辑,这取决于 intValue 的值。app
如今,在 ObjectMapper 里调用 filter,序列化这个实体:ide
FilterProvider filters = new SimpleFilterProvider().addFilter("myFilter", theFilter);
MyDto dtoObject = new MyDto();
dtoObject.setIntValue(-1);
ObjectMapper mapper = new ObjectMapper();
String dtoAsString = mapper.writer(filters).writeValueAsString(dtoObject);
最后,测试 intValue 字段是否在 JSON 输出中:函数
assertThat(dtoAsString, not(containsString("intValue")));
filter 功能很强大,当用 Jackson 序列化复杂对象时,能够很是灵活地自定义 JSON。
如何用 Jackson 2 把 Java Enum 序列化。
定义下面枚举:
public enum Type {
TYPE1(1, "Type A"), TYPE2(2, "Type 2");
private Integer id;
private String name;
private Type(final Integer id, final String name) {
this.id = id;
this.name = name;
}
// standard getters and setters
}
默认状况,Jackson 会把 Java 枚举表示成一个字符串,例如:
new ObjectMapper().writeValueAsString(Type.TYPE1);
结果:
"TYPE1"
当把这个枚举序列化成 JSON 对象时,咱们想获得以下所示:
{"name":"Type A","id":1}
用 Jackson 2.1.2 – 下面的配置能够获得上面的表示 – 这是经过在枚举级别上使用 @JsonFormat 注解:
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum Type { ... }
当序列化上面枚举,就会获得正确的结果:
{"name":"Type A","id":1}
另一种控制方式是使用 @JsonValue 注解:
public enum TypeEnumWithValue {
TYPE1(1, "Type A"), TYPE2(2, "Type 2");
private Integer id;
private String name;
private TypeEnumWithValue(final Integer id, final String name) {
this.id = id;
this.name = name;
}
...
@JsonValue
public String getName() {
return name;
}
}
咱们在这里说的是,getName 是此枚举的实际表示;因此:
String enumAsString = mapper.writeValueAsString(TypeEnumWithValue.TYPE1);
assertThat(enumAsString, is("\"Type A\""));
在 Jackson 2.1.2 之前,或是对枚举须要更多的自定义 – 咱们能够使用一个自定义的 Jackson 序列化器 – 首先,咱们须要定义它:
public class TypeSerializer extends JsonSerializer<TypeEnum> {
public void serialize
(TypeEnumWithCustomSerializer value, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
generator.writeStartObject();
generator.writeFieldName("id");
generator.writeNumber(value.getId());
generator.writeFieldName("name");
generator.writeString(value.getName());
generator.writeEndObject();
}
}
咱们如今将绑定序列化器,在类上应用它:
@JsonSerialize(using = TypeSerializer.class)
public enum TypeEnum { ... }
分析序列化没有 getter 的实体,以及 Jackson JsonMappingException 异常的解决方案。
默认状况,Jackson 2 运行在 public 或具备 public getter 方法的字段上 – 序列化 private 或 private 包内全部字段的一个实体将会失败:
ublic class MyDtoNoAccessors {
String stringValue;
int intValue;
boolean booleanValue;
public MyDtoNoAccessors() {
super();
}
// no getters
}
@Test(expected = JsonMappingException.class)
public void givenObjectHasNoAccessors_whenSerializing_thenException()
throws JsonParseException, IOException {
String dtoAsString = new ObjectMapper().writeValueAsString(new MyDtoNoAccessors());
assertThat(dtoAsString, notNullValue());
}
异常信息以下所示:
com.fasterxml.jackson.databind.JsonMappingException:
No serializer found for class dtos.MyDtoNoAccessors
and no properties discovered to create BeanSerializer
(to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) )
显而易见的解决方案是为字段添加 getter ,固然,若是实体处于咱们控制之下时。若是不是这种状况,修改实体的源代码就是不可能的,那么 Jackson 为咱们提供了几个可选方案。
第一个解决方案是,全局配置 ObjectMapper 来检测全部字段,而无论它们是否可见:
objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
这将容许 private 和 private 包的字段被检测到,即使没有 getter,序列化就会正确地执行:
@Test
public void givenObjectHasNoAccessors_whenSerializingWithAllFieldsDetected_thenNoException()
throws JsonParseException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
String dtoAsString = objectMapper.writeValueAsString(new MyDtoNoAccessors());
assertThat(dtoAsString, containsString("intValue"));
assertThat(dtoAsString, containsString("stringValue"));
assertThat(dtoAsString, containsString("booleanValue"));
}
Jackson 2 的另外一个选择是,经过 @JsonAutoDetect 注解,在类的级别上控制字段的可见性,而不是进行全局配置:
@JsonAutoDetect(fieldVisibility = Visibility.ANY)
public class MyDtoNoAccessors { ... }
对这个注解,序列化不能正确执行:
@Test
public void givenObjectHasNoAccessorsButHasVisibleFields_whenSerializing_thenNoException()
throws JsonParseException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
String dtoAsString = objectMapper.writeValueAsString(new MyDtoNoAccessors());
assertThat(dtoAsString, containsString("intValue"));
assertThat(dtoAsString, containsString("stringValue"));
assertThat(dtoAsString, containsString("booleanValue"));
}
如何用 Jackson 2 经过自定义序列化器序列化一个 Java 实体。
定义两个简单实体,如何用 Jackson 序列化它们,不使用任何的自定义逻辑:
public class User {
public int id;
public String name;
}
public class Item {
public int id;
public String itemName;
public User owner;
}
如今,序列化 Item 实体(里边还包含 User 实体):
Item myItem = new Item(1, "theItem", new User(2, "theUser"));
String serialized = new ObjectMapper().writeValueAsString(myItem);
结果以下:
{
"id": 1,
"itemName": "theItem",
"owner": {
"id": 2,
"name": "theUser"
}
}
如今,让咱们简化上面的 JSON 输出,只序列化 User 的 id,而不是整个 User 对象;咱们但愿获得以下简单的JSON:
{
"id": 25,
"itemName": "FEDUfRgS",
"owner": 15
}
咱们必须为 Item 对象定义一个自定义序列化器:
public class ItemSerializer extends JsonSerializer<Item> {
@Override
public void serialize(Item value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeNumberField("id", value.id);
jgen.writeStringField("itemName", value.itemName);
jgen.writeNumberField("owner", value.owner.id);
jgen.writeEndObject();
}
}
如今,咱们须要为 Item 类在 ObjectMapper 注册这个自定义序列化器,而后,执行序列化:
Item myItem = new Item(1, "theItem", new User(2, "theUser"));
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Item.class, new ItemSerializer());
mapper.registerModule(module);
String serialized = mapper.writeValueAsString(myItem);
咱们也能够在类上直接注册序列化器,而不用在 ObjectMapper:
@JsonSerialize(using = ItemSerializer.class)
public class Item {
...
}
如今,当咱们执行标准的序列化时:
Item myItem = new Item(1, "theItem", new User(2, "theUser"));
String serialized = new ObjectMapper().writeValueAsString(myItem);
咱们会获得自定义的 JSON 输出,它是由 @JsonSerialize 指定的序列化器建立的:
{
"id": 25,
"itemName": "FEDUfRgS",
"owner": 15
}
如何用 Jackson 2 把一个 JSON 数组(Array )反序列化成一个 Java 数组或集合(Collection )。
Jackson 能够很容易地反序列化成一个 Java 数组:
@Test
public void givenJsonArray_whenDeserializingAsArray_thenCorrect()
throws JsonParseException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
List<MyDto> listOfDtos = Lists.newArrayList(
new MyDto("a", 1, true), new MyDto("bc", 3, false));
String jsonArray = mapper.writeValueAsString(listOfDtos);
// [{"stringValue":"a","intValue":1,"booleanValue":true},
// {"stringValue":"bc","intValue":3,"booleanValue":false}]
MyDto[] asArray = mapper.readValue(jsonArray, MyDto[].class);
assertThat(asArray[0], instanceOf(MyDto.class));
}
把同一个 JSON 数组读到一个 Java 集合,就有点困难了 – 默认状况,Jackson 没法获得完整的泛型类型信息,而是将建立一个 LinkedHashMap 实例的集合:
@Test
public void givenJsonArray_whenDeserializingAsListWithNoTypeInfo_thenNotCorrect()
throws JsonParseException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
List<MyDto> listOfDtos = Lists.newArrayList(
new MyDto("a", 1, true), new MyDto("bc", 3, false));
String jsonArray = mapper.writeValueAsString(listOfDtos);
List<MyDto> asList = mapper.readValue(jsonArray, List.class);
assertThat((Object) asList.get(0), instanceOf(LinkedHashMap.class));
}
有两种方式能够帮助 Jackson 理解正确的类型信息 – 咱们或是使用 TypeReference:
@Test
public void givenJsonArray_whenDeserializingAsListWithTypeReferenceHelp_thenCorrect()
throws JsonParseException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
List<MyDto> listOfDtos = Lists.newArrayList(
new MyDto("a", 1, true), new MyDto("bc", 3, false));
String jsonArray = mapper.writeValueAsString(listOfDtos);
List<MyDto> asList = mapper.readValue(jsonArray, new TypeReference<List<MyDto>>() { });
assertThat(asList.get(0), instanceOf(MyDto.class));
}
或是使用接受 JavaType 的 readValue 的重载方法:
@Test
public final void givenJsonArray_whenDeserializingAsListWithJavaTypeHelp_thenCorrect()
throws JsonParseException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
List<MyDto> listOfDtos = Lists.newArrayList(
new MyDto("a", 1, true), new MyDto("bc", 3, false));
String jsonArray = mapper.writeValueAsString(listOfDtos);
final CollectionType javaType =
mapper.getTypeFactory().constructCollectionType(List.class, MyDto.class);
List<MyDto> asList = mapper.readValue(jsonArray, javaType);
assertThat(asList.get(0), instanceOf(MyDto.class));
}
最后,须要注意的是,MyDto 类须要一个没有任何参数的默认构造函数,不然,Jackson 将不能实例化:
com.fasterxml.jackson.databind.JsonMappingException:
No suitable constructor found for type [simple type, class org.baeldung.jackson.ignore.MyDto]:
can not instantiate from JSON object (need to add/enable type information?)
如何用 Jackson 2 经过一个自定义反序列化器来反序列化 JSON。
定义两个实体,如何用 Jackson 2 反序列化成一个 JSON 表示,不用任何的自定义:
public class User {
public int id;
public String name;
}
public class Item {
public int id;
public String itemName;
public User owner;
}
如今,咱们定义反序列化后想要获得的 JSON 表示:
{
"id": 1,
"itemName": "theItem",
"owner": {
"id": 2,
"name": "theUser"
}
}
最后,把这个 JSON unmarshall 成 Java 实体:
Item itemWithOwner = new ObjectMapper().readValue(json, Item.class);
在前面的示例中,JSON 表示与 Java 实体彻底匹配。下面,咱们简化 JSON:
{
"id": 1,
"itemName": "theItem",
"createdBy": 2
}
当把这个 JSON unmarshalling 成相同的实体时,默认状况,将会失败:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "createdBy" (class org.baeldung.jackson.dtos.Item),
not marked as ignorable (3 known properties: "id", "owner", "itemName"])
at [Source: java.io.StringReader@53c7a917; line: 1, column: 43]
(through reference chain: org.baeldung.jackson.dtos.Item["createdBy"])
如今,咱们用自定义的反序列化器来反序列化:
public class ItemDeserializer extends JsonDeserializer<Item> {
@Override
public Item deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
int id = (Integer) ((IntNode) node.get("id")).numberValue();
String itemName = node.get("itemName").asText();
int userId = (Integer) ((IntNode) node.get("createdBy")).numberValue();
return new Item(id, itemName, new User(userId, null));
}
}
反序列化使用 JSON 的标准 Jackson 表示 – JsonNode。一旦输入的 JSON 被表示成一个 JsonNode,就能够从它提取相关的信息,并构造本身的 Item 实体。
咱们须要注册这个自定义的反序列化器,再正常地反序列化 JSON:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Item.class, new ItemDeserializer());
mapper.registerModule(module);
Item readValue = mapper.readValue(json, Item.class);
另外一个方法是,也能够直接在类上注册反序列化器:
@JsonDeserialize(using = ItemDeserializer.class)
public class Item {
...
}
用这个定义在类级别上的反序列化器,就不须要在 ObjectMapper 注册它,那么一个默认的 mapper 就能够反序列化:
Item itemWithOwner = new ObjectMapper().readValue(json, Item.class);
图 1 项目结构