【问题标题】:Jackson - custom serializer that overrides only specific fieldsJackson - 仅覆盖特定字段的自定义序列化程序
【发布时间】:2013-03-01 00:09:18
【问题描述】:

我知道如何在 Jackson 中使用自定义序列化程序(通过扩展 JsonSerializer),但我希望默认序列化程序适用于所有字段,除了 1 个字段,我想使用自定义序列化程序覆盖它。

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

在编写自定义杰克逊序列化程序时,如何仅指定要覆盖的某些字段?

更新:

这是我要序列化的类:

class Student {
    int age;
    String firstName;
    String lastName;
    double average;
    int numSubjects

    // .. more such properties ...
}

上面的类有很多属性,其中大部分使用原生类型。我只想覆盖自定义序列化程序中的一些属性,让 Jackson 像往常一样处理其余的。例如我只想将“年龄”字段转换为自定义输出。

【问题讨论】:

标签: java serialization jackson


【解决方案1】:

假设你的目标类是

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

}

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

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

}

然后将其添加到 ObjectMapper

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

然后像这样使用它

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

{"年龄":"年龄: 2","firstName":"first","lastName":"last","average":3.4,"numSubjects":3}

【讨论】:

  • 假设我为具有 3 个字段的类 T 定义了一个自定义序列化程序,并且我只在我的序列化程序中执行一个 write()(对于我想要覆盖的唯一字段),其他 2 个字段使用默认值进行序列化?
  • 可以,但是所有被覆盖类型的字段都会使用自定义序列化器
  • 知道了-在我的例子中,我有一个类,其中的字段具有本机类型。例如int count, long total 等。在这种情况下我不能使用这种方法。
【解决方案2】:

我遇到了同样的问题,我用CustomSerializerFactory解决了它。

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

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

    }
}

然后你可以像下面这样使用它:

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

【讨论】:

    【解决方案3】:

    仅仅因为你不能修改类并不意味着你不能使用注释:只需使用混合注释。例如,请参阅 this 博客条目(或使用“jackson mixin annotations”在 google 上获取更多信息)了解如何使用它。

    我专门将 Jackson 与 protobuf 和 thrift 生成的类一起使用,它们工作得很好。对于较早的 Thrift 版本,我必须禁用“is-setters”的发现,Thrift 生成的方法以查看是否已显式设置特定属性,否则一切正常。

    【讨论】:

    • 这看起来很整洁 - 谢谢。我也会尝试这种方法。
    • 是的,了解选项是件好事,有些在某些情况下效果更好,有些在其他情况下效果更好。
    【解决方案4】:

    在@JsonView 的帮助下,我们可以决定要序列化的模型类的字段满足最低标准(我们必须定义标准),就像我们可以拥有一个具有 10 个属性的核心类,但只能序列化 5 个属性,它们是仅供客户使用

    通过简单地创建以下类来定义我们的视图:

    public class Views
    {
        static class Android{};
        static class IOS{};
        static class Web{};
    }
    

    带视图的注释模型类:

    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 转换器:

        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 中

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

    这就是您可以决定要序列化哪些字段的方式。

    感谢

    【讨论】:

      【解决方案5】:

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

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

      你想覆盖 id 字段,例如:

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

      对领域做任何你需要的事情:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-02-12
        • 2021-07-22
        • 2015-01-27
        • 1970-01-01
        • 2012-02-01
        • 1970-01-01
        相关资源
        最近更新 更多