Jackson是Java最受欢迎的JSON类库之一,包含两个不同的解析器:
Jackson还包含两个不同的生成器:
在spring项目中如何实现自定义类型的转换,例如如何将数字类型的属性转换为特定的枚举类型。一般来说,项目都会使用不同的数字来代表属性的不同含义,如果在代码中直接用数字类型表示可读性比较差,因此都会定义一个枚举来表示。但是如果每次都需要手工将数字转换为枚举那就太麻烦了,而且估计没人喜欢这样做。那解决方法是什么呢?
那就是让Jackson来替我们完成这个工作。来看下如何做,有如下代码:
@RestController
@RequestMapping("/json/exam")
public class ExamController {
@PostMapping("/getExamList")
public Result<List<GetExamListResVo>> getExamList(@Validated @RequestBody GetExamListReqVo reqVo,
@AuthenticationPrincipal UserDetails userDetails)
throws IOException {
//......
}
}
GetExamListReqVo类:
public class GetExamListReqVo {
private ExamTypeEnum examType;
// get set......
}
ExamStatusEnum枚举类:
public enum ExamTypeEnum implements EnumBase {
UNKNOWN(0, ""),
EXAM_INDEPENDENT(1, "独立考试"),
EXAM_COURSE_SUITE(2, "关联课程");
private final int code;
private final String msg;
ExamTypeEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
// get......
}
/**
* 枚举类的公共接口,所有枚举均实现了此接口
*/
public interface EnumBase {
int getCode();
String getMsg();
static <E extends Enum<?> & EnumBase> E codeOf(Class<E> enumClass, Integer code) {
E[] enumConstants = enumClass.getEnumConstants();
for (E e : enumConstants) {
if (e.getCode() == code) {
return e;
}
}
throw new IllegalArgumentException("the code didn't match any enum,code:" + code + ",enum class:" + enumClass.getName());
}
}
目标:将入参:{“examType”:1} 正确转换为 GetExamListReqVo 对象
为了完成这个目标,只需要编写三个类:
/**
* 实现了EnumBase接口的枚举类对象序列化和反序列化
*/
public class EnumBaseModule extends SimpleModule {
public EnumBaseModule() {
super(PackageVersion.VERSION);
// 找到EnumBase接口所在的包下所有实现该接口的枚举类
Set<Class> set = ClassUtils.getAllClassesFromPackage(EnumBase.class.getPackage().getName())
.stream()
.filter(clz -> clz.isEnum() && EnumBase.class.isAssignableFrom(clz))
.collect(Collectors.toSet());
// 动态注册所有实现了EnumBase接口枚举类的序列化器和反序列化器到Jackson
set.forEach(enumClass -> {
addDeserializer(enumClass, new EnumBaseDeserializer(enumClass));
addSerializer(enumClass, new EnumBaseSerializer());
});
}
}
/**
* 用来序列化实现了EnumBase接口的枚举类
*/
public class EnumBaseSerializer extends JsonSerializer<EnumBase> {
@Override
public void serialize(EnumBase value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
if (value != null) {
gen.writeNumber(value.getCode());
} else {
gen.writeNull();
}
}
}
/**
* 将前端传过来的数字转换为实现了EnumBase接口的枚举类对象
*/
public class EnumBaseDeserializer<E extends Enum<?> & EnumBase>
extends JsonDeserializer<EnumBase> implements ContextualDeserializer {
private Class<E> targetClass;
public EnumBaseDeserializer() {
}
public EnumBaseDeserializer(Class<E> targetClass) {
this.targetClass = targetClass;
}
@Override
public E deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return EnumBase.codeOf(targetClass, p.getIntValue());
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException {
targetClass = (Class<E>) ctxt.getContextualType().getRawClass();
return new EnumBaseDeserializer<>(targetClass);
}
}
然后配置一下springMVC:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof StringHttpMessageConverter) {
((StringHttpMessageConverter) converter).setDefaultCharset(StandardCharsets.UTF_8);
} else if (converter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter messageConverter =
(MappingJackson2HttpMessageConverter) converter;
messageConverter.setObjectMapper(objectMapper());
}
}
}
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
// 注册所有实现了EnumBase接口的枚举类处理器
objectMapper.registerModule(new EnumBaseModule());
return objectMapper;
}
}
默认情况下,Jackson通过Java bean的get,set方法,通过去掉get,set再把首字母小写得到的名字去和JSON的属性进行匹配。例如对于getBrand()和setBrand()经过处理得到brand,就会匹配到JSON的brand属性,从而把JSON brand属性的值赋给bean的brand字段。通过一系列这样的处理,就将JSON转换成了Java bean。如果需要以不同的方式来匹配,那么就得使用定制的serializer和deserializer,或者使用Jackson提供的众多的注解。
Car类定义:
public class Car {
private String brand = null;
private Integer doors = 0;
// get set......
}
测试代码:
String carJson = "{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";
// 从字符串创建
Car car = objectMapper.readValue(carJson, Car.class);
System.out.println(objectMapper.writeValueAsString(car));
// 从Reader创建
Reader reader = new StringReader(carJson);
car = objectMapper.readValue(reader, Car.class);
System.out.println(objectMapper.writeValueAsString(car));
File file = ResourceUtils.getFile("classpath:car.json");
// 从文件创建
car = objectMapper.readValue(file, Car.class);
System.out.println(objectMapper.writeValueAsString(car));
URL url = ResourceUtils.getFile("classpath:car.json").toURI().toURL();
// 从URL创建
car = objectMapper.readValue(url, Car.class);
System.out.println(objectMapper.writeValueAsString(car));
InputStream input = new FileInputStream(file);
// 从InputStream创建
car = objectMapper.readValue(input, Car.class);
System.out.println(objectMapper.writeValueAsString(car));
String jsonArray = "[{\"brand\":\"ford\"}, {\"brand\":\"Fiat\"}]";
// 解析为数组
Car[] cars = objectMapper.readValue(jsonArray, Car[].class);
System.out.println(objectMapper.writeValueAsString(cars));
// 解析为list
List<Car> carList = objectMapper.readValue(jsonArray, new TypeReference<List<Car>>() {
});
System.out.println(objectMapper.writeValueAsString(carList));
String jsonObjectStr = "{\"brand\":\"ford\", \"doors\":5}";
// 解析为Map
Map<String, Object> map = objectMapper.readValue(jsonObjectStr,
new TypeReference<Map<String, Object>>() {
});
System.out.println(objectMapper.writeValueAsString(map));
有时JSON会拥有比Java对象更多的属性,这种情况下Jackson默认会抛出异常,大概意思是说JSON某个属性未知因为在Java bean中未找到该属性。
然而,有时我们的确会遇到JSON属性比Java对象多的情况。例如我们从REST service获取的JSON会包含更多不需要的属性,这种情况下,Jackson允许通过配置来忽略未知的属性,像这样:
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
原始类型的null处理
public class Car {
private String brand = null;
private int doors = 0;// 原始类型int
// get set......
}
对于{ “brand”:“Toyota”, “doors”:null }
Car类的doors是原始类型int,不能被赋值为null,因此Jackson对于原始类型的null值默认会忽略,然而我们也可以配置Jackson在这种情况下做失败处理:
objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, true);
设为true后对于试图解析原始类型的null就会抛出异常。还有其他更多的配置等等,例如:
// 遇到无效的子类型也不会解析失败
objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
// 对于空的bean也不会失败
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
更多配置请查阅API说明
默认情况下Date类型属性会被序列化long的毫秒数,然而Jackson也支持将Date序列化成特定格式的日期字符串
public class Transaction {
/**
* 单独指定序列化后的格式和反序列化时以此格式来解析日期字符串
*/
@JsonFormat(pattern = "yyyyMMdd HH:mm:ssSSS", locale = "zh_CN", timezone = "GMT+8")
private Date date = null;
private Date create = null;
// get set......
}
测试代码:
Transaction transaction = new Transaction();
transaction.setCreate(new Date());
transaction.setDate(new Date());
String resJson = objectMapper.writeValueAsString(transaction);
// 默认把时间序列化为long
// {"date":"20190618 11:33:42845","create":1560828822845}
System.out.println(resJson);
可以改为序列化成日期字符串:
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.SIMPLIFIED_CHINESE);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT+8"));
// 设置这个会影响所有Bean的Date序列化和反序列化
objectMapper.setDateFormat(dateFormat);
resJson = objectMapper.writeValueAsString(transaction);
// {"date":"20190618 11:33:42845","create":"2019-06-18 11:33:42"}
System.out.println(resJson);
String transactionJsonStr = "{\"type\":\"transfer\",\"date\":\"20190118 14:27:52052\",\"create\":\"2019-01-18 14:27:52\"," +
"\"transactionTypeEnum\":\"TICKET\",\"transactionType\":2,\"carTypeEnum\":3}";
transaction = objectMapper.readValue(transactionJsonStr, Transaction.class);
System.out.println(transaction);
Jackson内置有树模型用来代表JSON对象,这个树模型具体有什么用呢?当你不知道要解析的JSON的结构你就会发现它会很有用,或者由于某种原因你没法创建一个类来表示这个JSON对象。另外一个好处是你可以在使用这个JSON对象之前操纵这个JSON对象,核心类是JsonNode。
String carJson =
"{ \"brand\" : \"Mercedes\", \"doors\" : 5," +
" \"owners\" : [\"John\", \"Jack\", \"Jill\"]," +
" \"nestedObject\" : { \"field\" : \"value\" } }";
JsonNode jsonNode = objectMapper.readValue(carJson, JsonNode.class);
// 或者
jsonNode = objectMapper.readTree(carJson);
// 取JSON属性值
JsonNode brandNode = jsonNode.get("brand");
String brand = brandNode.asText();
System.out.println("brand = " + brand);
JsonNode doorsNode = jsonNode.get("doors");
int doors = doorsNode.asInt();
System.out.println("doors = " + doors);
// 取JSON数组
JsonNode jsonArray = jsonNode.get("owners");
JsonNode jsonArrayNode = jsonArray.get(0);
String john = jsonArrayNode.asText();
System.out.println("john = " + john);
// 取JSON内嵌对象
JsonNode childNode = jsonNode.get("nestedObject");
JsonNode childField = childNode.get("field");
String field = childField.asText();
System.out.println("field = " + field);
JsonNode类是不可变的,意味着我们不能直接创建该类的对象,然而可以使用JsonNode的子类ObjectNode来构建对象:
// 创建ObjectNode
ObjectNode objectNode = objectMapper.createObjectNode();
objectNode.put("brand", "Mercedes");
objectNode.put("doors", 5);
ObjectNode nestNode = objectMapper.createObjectNode();
nestNode.put("field", "value");
objectNode.set("nestedObject", nestNode);
System.out.println(objectMapper.writeValueAsString(objectNode));
主要可分为三类:
读写注解(同时影响序列化和反序列化过程)
读注解(只影响反序列化过程)
写注解(只影响序列化过程)
@JsonIgnore用于需要忽略的属性(不参与序列化和反序列化)
public class PersonIgnore {
@JsonIgnore
private long personId = 0;
private String name = null;
// get set......
}
PersonIgnore obj = new PersonIgnore(1,"John");
// {"name":"John"}
System.out.println(objectMapper.writeValueAsString(obj));
@JsonIgnoreProperties用于需要忽略的一系列属性
@JsonIgnoreProperties({"firstName", "lastName"})
public class PersonIgnoreProperties {
private long personId = 0;
private String firstName = null;
private String lastName = null;
// get set......
}
PersonIgnoreProperties personIgnoreProperties = new PersonIgnoreProperties(1,"John","Rod");
// {"personId":1}
System.out.println(objectMapper.writeValueAsString(personIgnoreProperties));;
@JsonIgnoreType表明所有用到此类的地方都会被忽略
public class PersonIgnoreType {
private long personId = 0;
private String name = null;
private Address address = null;
//get set......
}
@JsonIgnoreType
public class Address {
private String streetName = null;
private String houseNumber = null;
private String zipCode = null;
private String city = null;
private String country = null;
// get set......
}
PersonIgnoreType obj = new PersonIgnoreType(1, "John", new Address("China"));
// {"personId":1,"name":"John"}
System.out.println(objectMapper.writeValueAsString(obj));
@JsonAutoDetect用于检测需要将哪类访问权限的属性参与序列化和反序列化
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY )
public class PersonAutoDetect {
private long personId = 123;
private String name = null;
// get set......
}
JsonAutoDetect.Visibility用于指明可见性级别,包括ANY, DEFAULT, NON_PRIVATE, NONE, PROTECTED_AND_PRIVATE and PUBLIC_ONLY
@JsonProperty表明序列化和反序列化时以注解指明的为准
public class Person {
@JsonProperty("id")
private long personId = 0;
private String name = null;
// get set......
}
JSON:{ “id” : 1234, “name” : “John”}
@JsonTypeInfo和@JsonSubTypes用来处理多态类型的序列化及反序列化,这样说有点抽象,来看下具体例子
抽象父类:
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonSubTypes({
@JsonSubTypes.Type(value = MemberInformation.class),
@JsonSubTypes.Type(value = EcardUserInformation.class)})
@JsonIgnoreProperties(ignoreUnknown = true)
public abstract class LoginUserInformation implements Serializable {
private static final long serialVersionUID = -933578885036345619L;
private String userType = "1";
private String ip = "127.0.0.1";
// get set......
}
两个具体子类:
public class MemberInformation extends LoginUserInformation implements Serializable {
private static final long serialVersionUID = 3334937746962893477L;
private String fpcardno = "513712340023";
private String passwd = "12346";
// get set......
}
public class EcardUserInformation extends LoginUserInformation implements Serializable {
private static final long serialVersionUID = -8191715041208933113L;
private String aid;
private String cellPhone;
private String email;
// get set......
}
测试代码:
LoginUserInformation loginUserInformation = new EcardUserInformation();
((EcardUserInformation) loginUserInformation).setAid("124124124");
// {"@class":"org.javamaster.b2c.test.model.jackson.EcardUserInformation","userType":"1","ip":"127.0.0.1",
// "aid":"124124124","cellPhone":null,"email":null}
System.out.println(objectMapper.writeValueAsString(loginUserInformation));
LoginUserInformation memberInformation = new MemberInformation();
((MemberInformation) memberInformation).setPasswd("qq123123");
// {"@class":"org.javamaster.b2c.test.model.jackson.MemberInformation","userType":"1","ip":"127.0.0.1",
// "fpcardno":"513712340023","passwd":"qq123123"}
System.out.println(objectMapper.writeValueAsString(memberInformation));
String jsonStr = "{\"@class\":\"org.javamaster.b2c.core.model.jackson.MemberInformation\",\"userType\":\"1\"," +
"\"ip\":\"127.0.0.1\",\"fpcardno\":\"513712340023\",\"passwd\":\"qq123123\"}";
memberInformation = objectMapper.readValue(jsonStr, LoginUserInformation.class);
System.out.println(objectMapper.writeValueAsString(memberInformation));
@JsonSetter用于表明set方法应该匹配的JSON的属性,当Java bean的属性名和JSON的属性名不一致时就可以使用此注解
public class Person {
private long personId = 0;
private String name = null;
@JsonSetter("id") // 应该和JSON的id属性匹配
public void setPersonId(long personId) { this.personId = personId; }
// get set......
}
JSON:{ “id” : 1234, “name” : “John”}
@JsonAnySetter用于指明将所有JSON未知的属性都收集在一起
public class Bag {
private Map<String, Object> properties = new HashMap<>();
@JsonAnySetter
public void set(String fieldName, Object value){
this.properties.put(fieldName, value);
}
public Object get(String fieldName){
return this.properties.get(fieldName);
}
}
所有JSON未知的属性都会收集到Bag类的properties属性里
@JsonCreator用于表明Java bean有一个构造方法能够匹配JSON的属性和bean的属性,对于一些没有set方法的bean(不可变对象)来说这个注解是很有用的。
public class PersonImmutable {
private final long id;
private final String name;
@JsonCreator
public PersonImmutable(@JsonProperty("id")long id, @JsonProperty("name")String name) {
this.id = id;
this.name = name;
}
// get set......
}
JSON:{ “id” : 1234, “name” : “John”}
@JsonDeserialize用于定制bean属性的反序列化过程
public class PersonDeserialize {
private long id = 0;
private String name = null;
// 这里我们想把1映射为true,0映射为false
@JsonDeserialize(using = OptimizedBooleanDeserializer.class)
private boolean enabled = false;
// get set......
}
public class OptimizedBooleanDeserializer extends JsonDeserializer<Boolean> {
@Override
public Boolean deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
String text = jsonParser.getText();
if ("0".equals(text)) {
return false;
} else {
return true;
}
}
}
JSON:{“id” : 1234, “name” : “John”,“enabled”: 0}
@JsonInclude用于表明值满足特定条件的属性才会被序列化
// 说明值不为null且不为空字符串的属性才会被序列化
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class PersonInclude {
private long personId = 0;
private String name = null;
private String name1 = "";
private String name2 = "John";
// get set......
}
JSON:{“personId” : 0, “name2” : “John”}
@JsonGetter用于指明序列化后的属性名而不是用bean的字段名
public class PersonGetter {
private long personId = 0;
@JsonGetter("id")
public long personId() { return this.personId; }
@JsonSetter("id")
public void personId(long personId) { this.personId = personId; }
}
序列化后: {“id”: 0}
@JsonAnyGetter能够让你使用一个Map作为容器包含任何你想要序列化的属性
public class PersonAnyGetter {
private Map<String, Object> properties = new HashMap<>();
@JsonAnyGetter
public Map<String, Object> properties() {
return properties;
}
public Map<String, Object> getProperties() {
return properties;
}
}
@JsonPropertyOrder用于指明bean属性序列化的顺序
@JsonPropertyOrder({"name", "personId"})
public class PersonPropertyOrder {
private long personId = 0;
private String name = "John";
// get set......
}
序列化后: {“name”:“John”,“personId”,0}
@JsonRawValue用于表明属性值不做任何处理原样输出,String类型的值序列化后会被双引号括住,使用此注解后将不会加上双引号
public class PersonRawValue {
private long personId = 0;
@JsonRawValue
private String address = "$#";
// get set......
}
不加此注解时输出是这样的:{“personId”:0,“address”:"KaTeX parse error: Expected 'EOF', got '#' at position 1: #̲"},加了后输出是这样的:{"…#},这很明显是非法的JSON结构,所以我们为什么要这么做呢?这么做的理由是address属性的值是JSON字符串来的,像这样:
public class PersonRawValue {
private long personId = 0;
@JsonRawValue
private String address = "{ \"street\" : \"Wall Street\", \"no\":1}";
// get set......
}
序列化后是这样的:{“personId”:0,“address”:{ “street” : “Wall Street”, “no”:1}}
不加注解时序列化后是这样的:{“personId”:0,“address”:“{ “street” : “Wall Street”, “no”:1}”}
@JsonValue表明Jackson不做序列化,由此注解标注的方法完成序列化工作
public class PersonValue {
private long personId = 0;
private String name = null;
@JsonValue
public String toJson(){
return this.personId + "," + this.name;
}
}
序列化后是这样的:“0,null”
@JsonSerialize用于定制bean属性的序列化过程
public class PersonSerializer {
private long personId = 0;
private String name = "John";
// 这里希望把false序列化成0,true序列化成1
@JsonSerialize(using = OptimizedBooleanSerializer.class)
private boolean enabled = false;
// get set......
}
public class OptimizedBooleanSerializer extends JsonSerializer<Boolean> {
@Override
public void serialize(Boolean aBoolean, JsonGenerator jsonGenerator,
SerializerProvider serializerProvider)
throws IOException {
if (aBoolean) {
jsonGenerator.writeNumber(1);
} else {
jsonGenerator.writeNumber(0);
}
}
}
// 完全定制类的序列化和反序列化过程
SimpleModule carModule = new CarModule();
// 注册针对这个类型的处理模块
objectMapper.registerModule(carModule);
Car car = new Car();
car.setBrand("BMW");
car.setDoors(4);
String json = objectMapper.writeValueAsString(car);
System.out.println(json);
car = objectMapper.readValue(json, Car.class);
System.out.println(objectMapper.writeValueAsString(car));
其中CarModule CarSerializer CarDeserializer的定义:
// 类型处理模块
public class CarModule extends SimpleModule {
public CarModule() {
super(PackageVersion.VERSION);
addDeserializer(Car.class, new CarDeserializer(Car.class));
addSerializer(Car.class, new CarSerializer(Car.class));
}
}
// 序列化器
public class CarSerializer extends StdSerializer<Car> {
private static final long serialVersionUID = 2807109332342106505L;
public CarSerializer(Class<Car> c) {
super(c);
}
@Override
public void serialize(Car car, JsonGenerator jsonGenerator,
SerializerProvider serializerProvider)
throws IOException {
jsonGenerator.writeStartObject();
if (car.getBrand() != null) {
jsonGenerator.writeStringField("brand", car.getBrand());
} else {
jsonGenerator.writeNullField("brand");
}
if (car.getDoors() != null) {
jsonGenerator.writeNumberField("doors", car.getDoors());
} else {
jsonGenerator.writeNullField("doors");
}
jsonGenerator.writeEndObject();
}
}
// 反序列化器
public class CarDeserializer extends StdDeserializer<Car> {
private static final long serialVersionUID = 4977601024588834191L;
public CarDeserializer(Class<?> c) {
super(c);
}
@Override
public Car deserialize(JsonParser parser, DeserializationContext deserializer) throws IOException {
Car car = new Car();
while (!parser.isClosed()) {
JsonToken jsonToken = parser.nextToken();
if (JsonToken.FIELD_NAME == jsonToken) {
String fieldName = parser.getCurrentName();
parser.nextToken();
if ("doors".equals(fieldName)) {
car.setDoors(parser.getValueAsInt());
} else if ("brand".equals(fieldName)) {
car.setBrand(parser.getValueAsString());
}
}
}
return car;
}
}
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://lebron.blog.csdn.net/article/details/124774237
内容来源于网络,如有侵权,请联系作者删除!