【问题标题】:Jackson multiple different schema for serialization of nested fieldsJackson 用于序列化嵌套字段的多种不同模式
【发布时间】:2020-07-11 21:37:41
【问题描述】:

在使用 Jackson 进行序列化时,我希望有几种不同的模式。 假设我有以下课程:

public class Department {
      private Person head;
      private Person deputy;
      private List<Person> staff;
      // getters and setters
}

public class Person {
       private String name
       private int code;
      // getters and setters
}

现在,我想为 Department 类设置两个不同的架构。第一个仅包含headdeputy,其中head 包含namecode,但deputy 仅包含name。第二个模式应该递归地包含所有字段。

因此,我们将有两个不同的 json。使用第一个架构:

{
    "head" : {
        "name" : "John",
        "code" : 123
     },
     "deputy" : { 
        "name" : "Jack"
     } 
}

,并使用第二个模式:

{
    "head" : {
        "name" : "John",
        "code" : 123
     },
     "deputy" : { 
        "name" : "Jack",
        "code" : "234"
     },
     "staff": [
        { 
            "name" : "Tom",
            "code" : "345"
         },
         { 
            "name" : "Matt",
            "code" : "456"
         },
     ]
}

问题:我应该如何处理 Jackson?

注意:这些类只是示例。对于这个简单的示例,编写四个不同的包装类可能是可能的,但考虑一个包含十几个类的复杂示例,每个类都有多个字段。使用包装类,我们应该生成很多样板代码。

任何帮助将不胜感激!

【问题讨论】:

    标签: java json jackson jackson-databind


    【解决方案1】:

    虽然@bigbounty 解决方案非常好,我认为Views,除了特定的DTOs,是一般要走的路,在这种情况下它可能不适用,因为对于同一个班级Person 我们实际上需要在同一个视图中出现两种不同的行为。

    @JsonFilters可以解决问题。

    这个main 方法计算你需要的图形:

    
      public static void main(String[] args) throws JsonProcessingException {
    
        final PropertyFilter departmentFilter = new SimpleBeanPropertyFilter() {
          @Override
          public void serializeAsField
            (Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer)
            throws Exception {
            if (include(writer)) {
              final String name = writer.getName();
              if (!name.equals("deputy") && !name.equals("staff")) {
                writer.serializeAsField(pojo, jgen, provider);
                return;
              }
    
              if (name.equals("staff")) {
                return;
              }
    
              // Ideally it should not be muted.
              final Department department = (Department)pojo;
              final Person deputy = department.getDeputy();
              deputy.setCode(-1);
    
              writer.serializeAsField(department, jgen, provider);
    
            } else if (!jgen.canOmitFields()) { // since 2.3
              writer.serializeAsOmittedField(pojo, jgen, provider);
            }
          }
          @Override
          protected boolean include(BeanPropertyWriter writer) {
            return true;
          }
          @Override
          protected boolean include(PropertyWriter writer) {
            return true;
          }
        };
    
        final PropertyFilter personFilter = new SimpleBeanPropertyFilter() {
    
          @Override
          public void serializeAsField
            (Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer)
            throws Exception {
            if (include(writer)) {
              if (!writer.getName().equals("code")) {
                writer.serializeAsField(pojo, jgen, provider);
                return;
              }
    
              int code = ((Person) pojo).getCode();
              if (code >= 0) {
                writer.serializeAsField(pojo, jgen, provider);
              }
            } else if (!jgen.canOmitFields()) { // since 2.3
              writer.serializeAsOmittedField(pojo, jgen, provider);
            }
          }
          @Override
          protected boolean include(BeanPropertyWriter writer) {
            return true;
          }
          @Override
          protected boolean include(PropertyWriter writer) {
            return true;
          }
        };
    
        final Department department = new Department();
        final Person head = new Person("John", 123);
        final Person deputy = new Person("Jack", 234);
        final List<Person> personList = Arrays.asList(new Person("Tom", 345), new Person("Matt", 456));
        department.setHead(head);
        department.setDeputy(deputy);
        department.setStaff(personList);
    
        final ObjectMapper mapper = new ObjectMapper();
    
        final FilterProvider schema1Filters = new SimpleFilterProvider()
          .addFilter("deparmentFilter", departmentFilter)
          .addFilter("personFilter", personFilter)
          ;
    
        mapper.setFilterProvider(schema1Filters);
    
        final String withSchema1Filters = mapper.writeValueAsString(department);
        System.out.printf("Schema 1:\n%s\n", withSchema1Filters);
    
        // You must maintain the filters once the classes are annotated with @JsonFilter
        // We can use two no-op builtin filters
        final FilterProvider schema2Filters = new SimpleFilterProvider()
          .addFilter("deparmentFilter", SimpleBeanPropertyFilter.serializeAll())
          .addFilter("personFilter", SimpleBeanPropertyFilter.serializeAll())
          ;
    
        mapper.setFilterProvider(schema2Filters);
    
        final String withSchema2Filters = mapper.writeValueAsString(department);
        System.out.printf("Schema 2:\n%s\n", withSchema2Filters);
      }
    

    要使此代码正常工作,您必须使用以下内容注释 Department 类:

    @JsonFilter("deparmentFilter")
    

    还有Person 类:

    @JsonFilter("personFilter")
    

    如您所见,Jackson 还提供了几个内置过滤器。

    此代码与您提出的测试类非常耦合,但可以通过使其更通用的方式进行扩展。

    请查看SimpleBeanPropertyFilter,了解如何创建自己的过滤器的示例。

    【讨论】:

      【解决方案2】:

      Jackson 库中有一个功能 JsonViews。

      你需要有一个视图类

      public class Views {
      
          public static class Normal{}
      
          public static class Extended extends Normal{}
      
      }
      

      接下来你注释 Department 类

      import com.fasterxml.jackson.annotation.JsonView;
      
      import java.util.List;
      
      public class Department {
      
          @JsonView(Views.Normal.class)
          private Person head;
      
          @JsonView(Views.Normal.class)
          private Person deputy;
      
          @JsonView(Views.Extended.class)
          private List<Person> staff;
      
          public Person getHead() {
              return head;
          }
      
          public void setHead(Person head) {
              this.head = head;
          }
      
          public Person getDeputy() {
              return deputy;
          }
      
          public void setDeputy(Person deputy) {
              this.deputy = deputy;
          }
      
          public List<Person> getStaff() {
              return staff;
          }
      
          public void setStaff(List<Person> staff) {
              this.staff = staff;
          }
      }
      

      保持 Person 类原样

      public class Person {
          private String name;
          private int code;
      
          public Person(String name, int code) {
              this.name = name;
              this.code = code;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public int getCode() {
              return code;
          }
      
          public void setCode(int code) {
              this.code = code;
          }
      }
      

      在主函数中,你正在序列化对象,你需要启用相应的视图。

      public class Main {
      
      
          static Department createDepartment(){
              Department department = new Department();
              Person head = new Person("John", 123);
              Person deputy = new Person("Jack", 234);
              List<Person> personList = Arrays.asList(new Person("Tom", 345), new Person("Matt", 456));
              department.setHead(head);
              department.setDeputy(deputy);
              department.setStaff(personList);
              return department;
      
          }
      
          public static void main(String[] args) throws JsonProcessingException {
      
              Department department = createDepartment();
      
              ObjectMapper mapper = new ObjectMapper();
      
              String normal = mapper.writerWithView(Views.Normal.class).writeValueAsString(department);
              String extended = mapper.writerWithView(Views.Extended.class).writeValueAsString(department);
      
              System.out.println("Normal View - " + normal);
              System.out.println("Extended View - " + extended);
         }
      }
      

      输出如下:

      Normal View - {"head":{"name":"John","code":123},"deputy":{"name":"Jack","code":234}}
      Extended View - {"head":{"name":"John","code":123},"deputy":{"name":"Jack","code":234},"staff":[{"name":"Tom","code":345},{"name":"Matt","code":456}]}
      

      【讨论】:

      • 感谢您的详细解答。我熟悉 JsonViews。但我认为它不能解决我的问题。例如,您的输出与我预期的 json 不同。因为您的映射器总是序列化 deputy 在两种模式中完全相同。但我想在每个模式中对其进行不同的序列化。
      • 您需要创建一个不同的视图。我的意思是类@vahidreza
      • 我没听懂你。你能解释一下吗?你的意思是我必须为Person 类创建不同的视图吗?如果是肯定的,我应该如何在Department类中注释deputy
      • @bigbounty 的答案是正确的@vahidreza。 @JsonView 可以接受一组视图。您可以使用 @JsonView({Views.Normal.class, Views.WithoutCode.class}) 来注释 deputy。您还必须使用适当的视图注释 Person 类以获得预期的结果。
      • @JoséCarlosCampanero,你的意思是 name 应该由 Normal 和 WithoudCode 注释,而 code 只由 Normal 注释?
      猜你喜欢
      • 1970-01-01
      • 2016-02-13
      • 1970-01-01
      • 2012-05-01
      • 1970-01-01
      • 2023-03-17
      • 1970-01-01
      • 2016-10-23
      • 2020-09-29
      相关资源
      最近更新 更多