自定义带类注解类型的Jackson序列化/反序列化实现需求
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-kotlindependency to handle Kotlin data classes properly. - The static registry can be replaced with a dynamic classpath scanner if you have many
@Specialclasses 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

