关于java:Jackson – 仅覆盖特定字段的自定义序列化程序

Jackson - custom serializer that overrides only specific fields

我知道如何在Jackson中使用自定义序列化程序(通过扩展JsonSerializer),但我希望默认序列化程序适用于所有字段,只适用于1个字段,我希望使用自定义序列化程序重写该字段。

注释不是一个选项,因为我正在序列化一个生成的类(节俭)。

在编写自定义Jackson序列化程序时,如何仅指定要重写的某些字段?

更新:

下面是我要序列化的类:

1
2
3
4
5
6
7
8
9
class Student {
    int age;
    String firstName;
    String lastName;
    double average;
    int numSubjects

    // .. more such properties ...
}

上面的类有许多属性,其中大多数使用本地类型。我只想重写自定义序列化程序中的一些属性,让Jackson像往常一样处理其余的属性。例如,我只想将"年龄"字段转换为自定义输出。


不能修改类并不意味着不能使用注释:只需在注释中使用mix。如需了解如何使用此功能,请参阅此博客条目(或Google了解更多关于"Jackson Mixin Annotations"的内容)。

我在Protobuf和Thrift生成的类中专门使用了Jackson,它们工作得很好。对于早期的节俭版本,我不得不禁用对"is setters"的发现,节俭生成的方法用于查看是否已显式设置了特定属性,但其他方面的工作正常。


假设你的目标类是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class Student {
    int age;
    String firstName;
    String lastName;
    double average;
    int numSubjects;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public double getAverage() {
        return average;
    }

    public void setAverage(double average) {
        this.average = average;
    }

    public int getNumSubjects() {
        return numSubjects;
    }

    public void setNumSubjects(int numSubjects) {
        this.numSubjects = numSubjects;
    }

}

您需要编写一个自定义序列化程序,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyCustomSerializer extends JsonSerializer<Student> {

    @Override
    public void serialize(Student value, JsonGenerator jgen,
            SerializerProvider provider) throws IOException,
            JsonProcessingException {
        if (value != null) {
            jgen.writeStartObject();
            jgen.writeStringField("age","Age:" + value.getAge()); //Here a custom way to render age field is used
            jgen.writeStringField("firstName", value.getFirstName());
            jgen.writeStringField("lastName", value.getLastName());
            jgen.writeNumberField("average", value.getAverage());
            jgen.writeNumberField("numSubjects", value.getNumSubjects());
            //Write other properties
            jgen.writeEndObject();
        }
    }

}

然后将其添加到对象映射器

1
2
3
4
5
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("custom",
        Version.unknownVersion());
module.addSerializer(Student.class, new MyCustomSerializer());
mapper.registerModule(module);

然后像这样使用

1
2
3
4
5
6
7
8
9
10
Student s = new Student();
s.setAge(2);
s.setAverage(3.4);
s.setFirstName("first");
s.setLastName("last");
s.setNumSubjects(3);

StringWriter sw = new StringWriter();
mapper.writeValue(sw, s);
System.out.println(sw.toString());

它会产生一个O/P

{"age":"Age:
2","firstName":"first","lastName":"last","average":3.4,"numSubjects":3}


我也面临同样的问题,我用CustomSerializerFactory解决了它。

这种方法允许您忽略所有对象或特定类型的某些特定字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class EntityCustomSerializationFactory extends CustomSerializerFactory {

    //ignored fields
    private static final Set<String> IGNORED_FIELDS = new HashSet<String>(
            Arrays.asList(
                   "class",
                   "value",
                   "some"
            )
    );


    public EntityCustomSerializationFactory() {
        super();
    }

    public EntityCustomSerializationFactory(Config config) {
        super(config);
    }

    @Override
    protected void processViews(SerializationConfig config, BeanSerializerBuilder builder) {
        super.processViews(config, builder);

        //ignore fields only for concrete class
        //note, that you can avoid or change this check
        if (builder.getBeanDescription().getBeanClass().equals(Entity.class)){
            //get original writer
            List<BeanPropertyWriter> originalWriters = builder.getProperties();

            //create actual writers
            List<BeanPropertyWriter> writers = new ArrayList<BeanPropertyWriter>();

            for (BeanPropertyWriter writer: originalWriters){
                String propName = writer.getName();

                //if it isn't ignored field, add to actual writers list
                if (!IGNORED_FIELDS.contains(propName)){
                    writers.add(writer);
                }
            }

            builder.setProperties(writers);
        }

    }
}

之后,您可以使用它,如下所示:

1
2
objectMapper.setSerializerFactory(new EntityCustomSerializationFactory());
objectMapper.writeValueAsString(new Entity());//response will be without ignored fields


如果不想用注释污染模型,可以使用mixin。

1
2
3
4
ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.setMixInAnnotation(Student.class, StudentMixin.class);
mapper.registerModule(simpleModule);

并且要覆盖ID字段,例如:

1
2
3
4
public abstract class StudentMixin {
    @JsonSerialize(using = StudentIdSerializer.class)
    public String id;
}

用这个领域做你需要做的一切:

1
2
3
4
5
6
public class StudentIdSerializer extends JsonSerializer<Integer> {
    @Override
    public void serialize(Integer integer, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString(String.valueOf(integer * 2));
    }
}


在@jsonview的帮助下,我们可以决定要序列化的模型类的字段满足最小条件(我们必须定义条件),就像我们可以有一个具有10个属性的核心类,但只有5个属性可以序列化,这仅对客户端是必需的

通过创建以下类定义视图:

1
2
3
4
5
6
public class Views
{
    static class Android{};
    static class IOS{};
    static class Web{};
}

带视图的注释模型类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Demo
{
    public Demo()
    {
    }

@JsonView(Views.IOS.class)
private String iosField;

@JsonView(Views.Android.class)
private String androidField;

@JsonView(Views.Web.class)
private String webField;

 // getters/setters
...
..
}

现在,我们必须通过简单地从Spring扩展httpMessageConverter类来编写自定义JSON转换器,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
    public class CustomJacksonConverter implements HttpMessageConverter<Object>
    {
    public CustomJacksonConverter()
        {
            super();
        //this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView(Views.ClientView.class));
        this.delegate.getObjectMapper().configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
        this.delegate.getObjectMapper().setSerializationInclusion(Include.NON_NULL);

    }

    // a real message converter that will respond to methods and do the actual work
    private MappingJackson2HttpMessageConverter delegate = new MappingJackson2HttpMessageConverter();

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return delegate.canRead(clazz, mediaType);
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return delegate.canWrite(clazz, mediaType);
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return delegate.getSupportedMediaTypes();
    }

    @Override
    public Object read(Class<? extends Object> clazz,
            HttpInputMessage inputMessage) throws IOException,
            HttpMessageNotReadableException {
        return delegate.read(clazz, inputMessage);
    }

    @Override
    public void write(Object obj, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException
    {
        synchronized(this)
        {
            String userAgent = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader("userAgent");
            if ( userAgent != null )
            {
                switch (userAgent)
                {
                case"IOS" :
                    this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView(Views.IOS.class));
                    break;
                case"Android" :
                    this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView(Views.Android.class));
                    break;
                case"Web" :
                    this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView( Views.Web.class));
                    break;
                default:
                    this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView( null ));
                    break;
                }
            }
            else
            {
                // reset to default view
                this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView( null ));
            }
            delegate.write(obj, contentType, outputMessage);
        }
    }

}

现在需要告诉Spring使用这个定制的JSON转换,只需将其放入dispatcher-servlet.xml中即可。

1
2
3
4
5
6
<mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean id="jsonConverter" class="com.mactores.org.CustomJacksonConverter">
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

这就是如何决定要序列化哪些字段的方法。

桑克斯