JSON serialization and deserialization also play this way

Mixin is no stranger to front-end developers. Well known front-end frameworks such as Vue and React use mixin. For back-end development, especially Java back-end development, mixin is a very strange concept. Today, let's get back-end developers to know mixin through Jackson.

scene

For example, we refer to a Jar package, in which a class needs to be deserialized in a certain scenario, but this class does not provide a default construction. What should I do? Pull down the original project and rewrite it? Make a bad decision! You can use the Mixin feature provided by Jackson to solve this problem.

Mixin in Jackson

We can interpret Mixin in Jackson in this way: configure the serialization or deserialization functions that cannot be realized by the target object through a mixed object, and mix these personalized configurations into the target object during serialization or deserialization. Blending does not change any characteristics of the target object itself. The blending object and the target object are mapped. Next, let's implement a mixed DEMO.

Implementation of Mixin

We have a User class. We need to be extreme for demonstration. This extreme situation is unlikely to occur in actual development. This User does not have a parameterless construct, nor does it have a getter method for attributes.

public class User {
    private final String name;
    private final Integer age;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

Writing Mixin classes

I want to serialize and deserialize this extreme User. According to the previous play, we can serialize the User class by adding @ JsonAutoDetect annotation; Add the @ JsonDeserialize annotation and specify the deserialization class to deserialize. However, today we don't need to make any changes to the User. We just need to write a Mixin class to configure the above two annotations.

@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
        isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonDeserialize(using = UserMixin.UserDeserializer.class)
public abstract class UserMixin {

    /**
     * Deserialization class
     **/
    static class UserDeserializer extends JsonDeserializer<User> {

        @Override
        public User deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            ObjectMapper mapper = (ObjectMapper) p.getCodec();
            JsonNode jsonNode = mapper.readTree(p);

            String name = readJsonNode(jsonNode, "name").asText(null);
            String age = readJsonNode(jsonNode, "age").asText(null);
            Integer ageVal = Objects.isNull(age)? null: Integer.valueOf(age);
            return new User(name,ageVal);
        }

        private JsonNode readJsonNode(JsonNode jsonNode, String field) {
            return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
        }
    }

}

❝ for other notes, please refer to previous ones Jackson article Introduction of

Mixin mapping target class

After writing the Mixin class, we map UserMixin and User through the addMixIn method in ObjectMapper. And write an example of serialization and deserialization.

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.addMixIn(User.class, UserMixin.class);

        User felord = new User("felord", 12);
        String json = objectMapper.writeValueAsString(felord);
        //{"name":"felord","age":12} 
        System.out.println("json = " + json);

        String jsonStr = "{\"name\":\"felord\",\"age\":12}";

        User user = objectMapper.readValue(jsonStr, User.class);
        // User{name='felord', age=12}
        System.out.println("user = " + user);

In this way, we implement personalized JSON serialization and deserialization without making any changes to the target class.

Module in Jackson

Jackson also provides a modular function, which can carry out modular and unified management of personalized configuration, and can be referenced on demand or even pluggable. It can also manage a set of mixins. Declaring a Jackson Module is very simple. You can inherit SimpleModule and override some of its methods. For Mixin, we can write:

public class UserModule extends SimpleModule {
   public UserModule() {
       super(UserModule.class.getName());
   }

   @Override
   public void setupModule(SetupContext context) {
        context.setMixInAnnotations(User.class,UserMixin.class);
   }
}

Module can also be registered in ObjectMapper and achieve the desired effect:

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new UserModule());
        // ellipsis

Module is more powerful. We usually use the following modules:

  • Jackson module parameter names this module can access the names of constructors and method parameters
  • jackson-datatype-jdk8 supports other new features besides Java 8's time API
  • jackson-datatype-jsr310 is used to support the JSR310 time API newly added in Java 8

In addition, Spring Security also supports SecurityJackson2Modules, which includes the following modules:

      ObjectMapper mapper = new ObjectMapper();
      mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
      mapper.registerModule(new CoreJackson2Module());
      mapper.registerModule(new CasJackson2Module());
      mapper.registerModule(new WebJackson2Module());
      mapper.registerModule(new WebServletJackson2Module());
      mapper.registerModule(new WebServerJackson2Module());
      mapper.registerModule(new OAuth2ClientJackson2Module());

It is recommended to take a look at the source code of SecurityJackson2Modules and study and imitate the use of Module.

Added by ccx004 on Fri, 19 Nov 2021 11:11:44 +0200