You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义带类注解类型的Jackson序列化/反序列化实现需求

Solution: Serialize/Deserialize @Special Data Classes with Jackson Mixins

Great question—using mixins is exactly the right approach here to keep your data classes clean of Jackson-specific annotations while hitting that custom serialization format you want. Let’s break this down into actionable, code-ready steps:

1. Define the @Special Annotation

First, make sure your custom annotation is retained at runtime so Jackson can access its value later:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Special {
    String value();
}

2. Create a Jackson Mixin for @Special Classes

This mixin acts as a bridge between your annotation and Jackson’s serialization system. It tells Jackson to use our custom handlers without polluting your data classes:

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

@JsonSerialize(using = SpecialClassSerializer.class)
@JsonDeserialize(using = SpecialClassDeserializer.class)
public interface SpecialMixin {
    // No methods needed—this is just a marker for Jackson
}

3. Implement the Custom Serializer

This serializer handles converting your @Special class instances into the desired JSON structure. It extracts the annotation value for the special field and wraps all class properties into params:

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

import java.io.IOException;
import java.lang.reflect.Field;

public class SpecialClassSerializer extends StdSerializer<Object> {

    public SpecialClassSerializer() {
        super(Object.class);
    }

    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        Special specialAnnotation = value.getClass().getAnnotation(Special.class);
        if (specialAnnotation == null) {
            throw new IllegalArgumentException("Class must be annotated with @Special");
        }

        gen.writeStartObject();
        // Write the "special" field from the annotation
        gen.writeStringField("special", specialAnnotation.value());
        // Write all class fields into the "params" object
        gen.writeObjectFieldStart("params");
        for (Field field : value.getClass().getDeclaredFields()) {
            field.setAccessible(true);
            try {
                gen.writeObjectField(field.getName(), field.get(value));
            } catch (IllegalAccessException e) {
                throw new IOException("Failed to access field: " + field.getName(), e);
            }
        }
        gen.writeEndObject();
        gen.writeEndObject();
    }
}

4. Implement the Custom Deserializer

The deserializer reverses the process: it parses the JSON, looks up the correct class for the special value, and converts the params into an instance of that class. We’ll use a simple registry to map special values to their classes (you can make this dynamic later if needed):

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class SpecialClassDeserializer extends StdDeserializer<Object> {

    // Registry to map special values to their corresponding data classes
    private static final Map<String, Class<?>> SPECIAL_CLASS_REGISTRY = new HashMap<>();

    // Static block to register your data classes (add more here as needed)
    static {
        SPECIAL_CLASS_REGISTRY.put("SpecialThing", MyThing.class);
    }

    public SpecialClassDeserializer() {
        super(Object.class);
    }

    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        String specialValue = null;
        Map<String, Object> params = null;

        // Parse the JSON structure
        while (p.nextToken() != JsonToken.END_OBJECT) {
            String fieldName = p.getCurrentName();
            p.nextToken();
            if ("special".equals(fieldName)) {
                specialValue = p.getText();
            } else if ("params".equals(fieldName)) {
                params = ctxt.readValue(p, Map.class);
            }
        }

        if (specialValue == null || params == null) {
            throw new IOException("Invalid JSON: missing 'special' or 'params' field");
        }

        // Look up the target class from the registry
        Class<?> targetClass = SPECIAL_CLASS_REGISTRY.get(specialValue);
        if (targetClass == null) {
            throw new IOException("No class registered for special value: " + specialValue);
        }

        // Convert params map to the target class instance
        return ctxt.convertValue(params, targetClass);
    }
}

5. Configure ObjectMapper with Mixins

Wire everything up by registering the mixin for all @Special annotated classes in your ObjectMapper:

import com.fasterxml.jackson.databind.ObjectMapper;

public class SpecialObjectMapperFactory {

    public static ObjectMapper createMapper() {
        ObjectMapper mapper = new ObjectMapper();
        // Link the mixin to all classes annotated with @Special
        mapper.addMixIn(Special.class, SpecialMixin.class);
        // Optional: Add Kotlin module if using Kotlin data classes
        // mapper.registerModule(new KotlinModule());
        return mapper;
    }
}

6. Test with Your Example

Your data class stays completely clean—no Jackson annotations required:

@Special("SpecialThing")
data class MyThing(val myVal: String)

Test serialization and deserialization:

public class Main {
    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = SpecialObjectMapperFactory.createMapper();

        // Serialization
        MyThing thing = new MyThing("myValValue");
        String json = mapper.writeValueAsString(thing);
        System.out.println(json);
        // Output: {"special":"SpecialThing","params":{"myVal":"myValValue"}}

        // Deserialization
        MyThing deserialized = (MyThing) mapper.readValue(json, Object.class);
        System.out.println(deserialized.myVal);
        // Output: myValValue
    }
}

Quick Notes

  • For Kotlin users: Don’t forget to include the jackson-module-kotlin dependency to handle Kotlin data classes properly.
  • The static registry can be replaced with a dynamic classpath scanner if you have many @Special classes to register.
  • If you need to handle ignored fields or custom property names, extend the serializer/deserializer to respect Jackson’s internal configuration rules.

内容的提问来源于stack exchange,提问作者mkobit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 09:15:53