Jackson介绍

Jackson是Java最受欢迎的JSON类库之一,包含两个不同的解析器:

  1. Jackson ObjectMapper,将JSON转化为Java对象,或者转换为Jackson特定的树结构
  2. Jackson JsonParser,JSON流解析器,每次只解析一个JSON标记(不做讲解)

Jackson还包含两个不同的生成器:

  1. Jackson ObjectMapper,可以从Java对象生成JSON,或者是从Jackson的树结构生成JSON
  2. Jackson Generator,每次生成一个JSON标记(不做讲解)

在springMVC中的具体应用

在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;
    }

}

ObjectMapper解析JSON的原理

默认情况下,Jackson通过Java bean的get,set方法,通过去掉get,set再把首字母小写得到的名字去和JSON的属性进行匹配。例如对于getBrand()和setBrand()经过处理得到brand,就会匹配到JSON的brand属性,从而把JSON brand属性的值赋给bean的brand字段。通过一系列这样的处理,就将JSON转换成了Java bean。如果需要以不同的方式来匹配,那么就得使用定制的serializer和deserializer,或者使用Jackson提供的众多的注解。

ObjectMapper创建Java对象的多种方式

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));

配置ObjectMapper的工作方式

有时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说明

Jackson对于Date类的处理

默认情况下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的树模型JsonNode

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));

Jackson注解

主要可分为三类:

读写注解(同时影响序列化和反序列化过程)

  • @JsonIgnore
  • @JsonIgnoreProperties
  • @JsonIgnoreType
  • @JsonAutoDetect
  • @JsonProperty
  • @JsonTypeInfo
  • @JsonSubTypes

读注解(只影响反序列化过程)

  • @JsonSetter
  • @JsonAnySetter
  • @JsonCreator
  • @JsonDeserialize

写注解(只影响序列化过程)

  • @JsonInclude
  • @JsonGetter
  • @JsonAnyGetter
  • @JsonPropertyOrder
  • @JsonRawValue
  • @JsonValue
  • @JsonSerialize

@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;
    }
}