具有条件隐藏成员的Jackson自定义序列化程序会生成无效的JSON

时间:2015-06-29 16:42:24

标签: java json serialization jackson

我需要创建一个有条件地跳过字段的自定义序列化程序。 与上述情况相反 Skip objects conditionally when serializing with jackson我的班级包含一名POJO成员。 PersonalInfo的地址为成员。如果地址被隐藏,生成的JSON仍然具有"地址"标签,但没有价值。 我无法弄清楚如何解决这个问题。

在ObjectMapper上创建自定义序列化程序(参见http://www.baeldung.com/jackson-custom-serialization处的3.)会得到完全相同的结果。

以下是引用问题的改编代码,显示问题:

public class JacksonHide {
    @JsonIgnoreProperties("hidden")
    public static interface IHideable {
        boolean isHidden();
    }

    public static class Address implements IHideable {
        public final String city;
        public final String street;
        public final boolean hidden;

        public Address(String city, String street, boolean hidden) {
            this.city = city;
            this.street = street;
            this.hidden = hidden;
        }

        @Override
        public boolean isHidden() {
            return hidden;
        }
    }

    public static class PersonalInfo implements IHideable {
        public final String name;
        public final int age;
        public final Address address;
        public final boolean hidden;

        public PersonalInfo(String name, int age, Address address, boolean hidden) {
            this.name = name;
            this.age = age;
            this.address = address;
            this.hidden = hidden;
        }

        @Override
        public boolean isHidden() {
            return hidden;
        }
    }

    private static class MyBeanSerializerModifier extends BeanSerializerModifier {
        @Override
        public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
            if (IHideable.class.isAssignableFrom(beanDesc.getBeanClass())) {
                return new MyIHideableJsonSerializer((JsonSerializer<IHideable>) serializer);
            }
            return super.modifySerializer(config, beanDesc, serializer);
        }

        private static class MyIHideableJsonSerializer extends JsonSerializer<IHideable> {
            private final JsonSerializer<IHideable> serializer;

            public MyIHideableJsonSerializer(JsonSerializer<IHideable> serializer) {
                this.serializer = serializer;
            }

            @Override
            public void serialize(IHideable value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
                if (!value.isHidden()) {
                    serializer.serialize(value, jgen, provider);
                }

            }
        }
    }

    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.setSerializerModifier(new MyBeanSerializerModifier());
        mapper.registerModule(module);

        PersonalInfo p1 = new PersonalInfo("John", 30, new Address("A", "B", false), false);
        PersonalInfo p2 = new PersonalInfo("Ivan", 20, new Address("C", "D", true), true);
        PersonalInfo p3 = new PersonalInfo("Mary", 40, new Address("C", "D", true), false);
        Address a1 = new Address("A", "B", false);
        Address a2 = new Address("C", "D", true);

        System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(Arrays.asList(p1, p2, p3, a1, a2)));
    }

}

更新: 感谢我的反馈,我现在有一个基于@JSONFilter的版本,它给了我至少有效的JSON。不幸的是节点仍在那里,但现在是空的({})。我怎么能完全摆脱它们?

public class JacksonFilterHide {

    @JsonFilter("HiddenFilter")
    @JsonIgnoreProperties("hidden")
    public static interface IHideable {
        boolean isHidden();
    }

    public static class Address implements IHideable {
        public final String city;
        public final String street;
        public final boolean hidden;

        public Address(String city, String street, boolean hidden) {
            this.city = city;
            this.street = street;
            this.hidden = hidden;
        }

        @Override
        public boolean isHidden() {
            return hidden;
        }
    }

    public static class PersonalInfo implements IHideable {
        public final String name;
        public final int age;
        public final Address address;
        public final boolean hidden;

        public PersonalInfo(String name, int age, Address address, boolean hidden) {
            this.name = name;
            this.age = age;
            this.address = address;
            this.hidden = hidden;
        }

        @Override
        public boolean isHidden() {
            return hidden;
        }
    }

    static final PropertyFilter hiddenFilter = new SimpleBeanPropertyFilter() {
        @Override
        public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer) throws Exception {
            if (include(writer)) {
                if (pojo instanceof IHideable && ((IHideable) pojo).isHidden()) {
                    return;
                } else {
                    writer.serializeAsField(pojo, jgen, provider);
                    return;
                }
            } 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;
        }
    };

    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        // ObjectMapper mapper = UserInteractionModel.getMapper();
        FilterProvider filters = new SimpleFilterProvider().addFilter("HiddenFilter", hiddenFilter);
        mapper.setFilters(filters);
        mapper.enable(SerializationFeature.INDENT_OUTPUT);

        PersonalInfo p1 = new PersonalInfo("John", 30, new Address("A", "B", false), false);
        PersonalInfo p2 = new PersonalInfo("Ivan", 20, new Address("C", "D", true), true);
        PersonalInfo p3 = new PersonalInfo("Mary", 40, new Address("C", "D", true), false);
        Address a1 = new Address("A", "B", false);
        Address a2 = new Address("C", "D", true);

        System.out.println(mapper.writeValueAsString(Arrays.asList(p1, p2, p3, a1, a2)));
    }

}

现在输出是:

  

[{&#34; name&#34; :&#34;约翰&#34;,&#34;年龄&#34; :30,&#34;地址&#34; :{       &#34;城市&#34; :&#34; A&#34;,       &#34;街道&#34; :&#34; B&#34; },{},{&#34; name&#34; :&#34;玛丽&#34;,&#34;年龄&#34; :40,&#34;地址&#34; :{}},{&#34; city&#34; :&#34; A&#34;,&#34;街道&#34; :&#34; B&#34; },{}]

预期:

  

[{&#34; name&#34; :&#34;约翰&#34;,&#34;年龄&#34; :30,&#34;地址&#34; :{       &#34;城市&#34; :&#34; A&#34;,       &#34;街道&#34; :&#34; B&#34; },{&#34; name&#34; :&#34;玛丽&#34;,&#34;年龄&#34; :40,},{&#34; city&#34; :&#34; A&#34;,&#34;街道&#34; :&#34; B&#34; }]

UPDATE2 通过遍历树并删除空节点来临时修复。丑陋,但现在工作。仍在寻找更好的答案。

private void removeEmptyNodes(JSONObject json) {
    Iterator<String> iter = json.keys();
    while (iter.hasNext()) {
        String key = iter.next();
        JSONObject node;
        try {
            node = json.getJSONObject(key);
        } catch (JSONException e) {
            continue;
        }
        if (node.length() == 0) {
            iter.remove();
        } else {
            removeEmptyNodes(node);
        }

    }
}

受此问题启发的解决方案:How do I remove empty json nodes in Java with Jackson?

1 个答案:

答案 0 :(得分:2)

您的序列化程序已损坏:如果请求,它无法选择不写入值。在写入属性值的情况下,调用者已经写出了属性名称,因此不写入值确实会破坏输出。 这或者导致抛出(理想情况下)异常,或者输出损坏(理想情况下不太好);无论如何,JsonSerializer不允许尝试决定是否正在写入值。

要排除正在序列化的媒体资源,您的有效选择包括:

  1. 静态属性注释,例如@JsonIgnore@JsonIgnoreProperties,始终排除特定的命名属性
  2. 静态注释@JsonInclude基于值的类型(无空,无缺席,无空值)
  3. 静态定义,但动态选择要使用的@JsonView(可以通过与视图关联动态排除的属性集)
  4. 动态@JsonFilter
  5. 包含属性
  6. 的类型的自定义序列化程序