2017-08-30 36 views
1

我想将我的POJO转换为2个不同的CSV表示形式。杰克逊 - 如何获得视图依赖的CsvSchema?

我的POJO:

@NoArgsConstructor 
@AllArgsConstructor 
public static class Example { 
    @JsonView(View.Public.class) 
    private String a; 
    @JsonView(View.Public.class) 
    private String b; 
    @JsonView(View.Internal.class) 
    private String c; 
    @JsonView(View.Internal.class) 
    private String d; 

    public static final class View { 
     interface Public {} 
     interface Internal extends Public {} 
    } 
} 

Public视图暴露领域ab,并Internal视图公开所有领域。

问题是,如果我用.writerWithSchemaFor(Example.class)构建ObjectWriter,我的所有字段都包含在视图中,但被忽略。 ObjectWriter将创建Example.class定义的模式,但如果我应用.withView它将只隐藏字段,不会忽略它们。

这意味着我必须手动构建模式。

测试:

@Test 
public void testJson() throws JsonProcessingException { 
    final ObjectMapper mapper = new ObjectMapper(); 

    final Example example = new Example("1", "2", "3", "4"); 
    final String result = mapper.writerWithView(Example.View.Public.class).writeValueAsString(example); 
    System.out.println(result); // {"a":"1","b":"2"} 
} 

@Test 
public void testCsv() throws JsonProcessingException { 
    final CsvMapper mapper = new CsvMapper(); 

    final Example example = new Example("1", "2", "3", "4"); 
    final String result = mapper.writerWithSchemaFor(Example.class).withView(Example.View.Public.class).writeValueAsString(example); 
    System.out.println(result); // 1,2,, 
} 

@Test 
public void testCsvWithCustomSchema() throws JsonProcessingException { 
    final CsvMapper mapper = new CsvMapper(); 

    CsvSchema schema = CsvSchema.builder() 
      .addColumn("a") 
      .addColumn("b") 
      .build(); 

    final Example example = new Example("1", "2", "3", "4"); 
    final String result = mapper.writer().with(schema).withView(Example.View.Public.class).writeValueAsString(example); 
    System.out.println(result); // 1,2 
} 

testCsv测试有4​​个字段,但2被排除在外。 testCsvWithCustomSchema测试只有我想要的字段。

有没有办法让CsvSchema与我的@JsonView相匹配,而无需自己构造它?

回答

0

这里是一个解决方案,我用反射一样,我不是真的喜欢它,因为它仍然是“手动”建设模式。

该解决方案也很糟糕,因为它忽略了像MapperFeature.DEFAULT_VIEW_INCLUSION映射配置。

这似乎是做一些应该已经可以从库中。

@AllArgsConstructor 
public class GenericPojoCsvSchemaBuilder { 

    public CsvSchema build(final Class<?> type) { 
     return build(type, null); 
    } 

    public CsvSchema build(final Class<?> type, final Class<?> view) { 
     return build(CsvSchema.builder(), type, view); 
    } 

    public CsvSchema build(final CsvSchema.Builder builder, final Class<?> type) { 
     return build(builder, type, null); 
    } 

    public CsvSchema build(final CsvSchema.Builder builder, final Class<?> type, final Class<?> view) { 
     final JsonPropertyOrder propertyOrder = type.getAnnotation(JsonPropertyOrder.class); 

     final List<Field> fieldsForView; 

     // DO NOT use Arrays.asList because it uses an internal fixed length implementation which cannot use .removeAll (throws UnsupportedOperationException) 
     final List<Field> unorderedFields = Arrays.stream(type.getDeclaredFields()).collect(Collectors.toList()); 

     if (propertyOrder != null && propertyOrder.value().length > 0) { 
      final List<Field> orderedFields = Arrays.stream(propertyOrder.value()).map(s -> { 
       try { 
        return type.getDeclaredField(s); 
       } catch (final NoSuchFieldException e) { 
        throw new IllegalArgumentException(e); 
       } 
      }).collect(Collectors.toList()); 

      if (propertyOrder.value().length < type.getDeclaredFields().length) { 
       unorderedFields.removeAll(orderedFields); 
       orderedFields.addAll(unorderedFields); 
      } 

      fieldsForView = getJsonViewFields(orderedFields, view); 
     } else { 
      fieldsForView = getJsonViewFields(unorderedFields ,view); 
     } 

     final JsonIgnoreFieldFilter ignoreFieldFilter = new JsonIgnoreFieldFilter(type.getDeclaredAnnotation(JsonIgnoreProperties.class)); 

     fieldsForView.forEach(field -> { 
      if (ignoreFieldFilter.matches(field)) { 
       builder.addColumn(field.getName()); 
      } 
     }); 

     return builder.build(); 
    } 

    private List<Field> getJsonViewFields(final List<Field> fields, final Class<?> view) { 
     if (view == null) { 
      return fields; 
     } 

     return fields.stream() 
       .filter(field -> { 
        final JsonView jsonView = field.getAnnotation(JsonView.class); 
        return jsonView != null && Arrays.stream(jsonView.value()).anyMatch(candidate -> candidate.isAssignableFrom(view)); 
       }) 
       .collect(Collectors.toList()); 
    } 

    private class JsonIgnoreFieldFilter implements ReflectionUtils.FieldFilter { 

     private final List<String> fieldNames; 

     public JsonIgnoreFieldFilter(final JsonIgnoreProperties jsonIgnoreProperties) { 
      if (jsonIgnoreProperties != null) { 
       fieldNames = Arrays.asList(jsonIgnoreProperties.value()); 
      } else { 
       fieldNames = null; 
      } 
     } 

     @Override 
     public boolean matches(final Field field) { 
      if (fieldNames != null && fieldNames.contains(field.getName())) { 
       return false; 
      } 

      final JsonIgnore jsonIgnore = field.getDeclaredAnnotation(JsonIgnore.class); 
      return jsonIgnore == null || !jsonIgnore.value(); 
     } 
    } 
}