Objectmapper.readerforupdating 不适用于嵌套对象

Objectmapper.readerforupdating not working for nested objects

提问人:Eni 提问时间:5/2/2023 更新时间:5/2/2023 访问量:246

问:

我在视图的帮助下使用不同的写入权限进行 Objectmapper.readerfordating 时遇到了问题,它在主实体上工作正常,但在嵌套对象上工作不正常。我有以下示例:

public class A {

    @JsonView(value={WritePermission.Admin})
    private String name;
    
    @JsonView(value={WritePermission.User})
    private String property;
    
    @JsonView(value={WritePermission.User})
    private List<B> list;
}

public class B {
    @JsonView(value={WritePermission.Admin})
    private String name;
    
    @JsonView(value={WritePermission.User})
    private String property;
    
}

public class WritePermission {
    public WritePermission() {
    }
   
     public static class Admin extends WritePermission.User {
        public Admin() {
        }
    }

    public static class User {
        public User() {
        }
    }
}    

对于反序列化,我使用以下内容:objectMapper.readerForUpdating(initialEntityOfAClass).withView(WritePermission.User.class).forType(A.class).readValue(json) 并且也尝试过这个,但我得到的结果相同: ObjectReader objectReader = objectMapper.readerForUpdating(initialEntityOfAClass);

当我想使用用户写入角色反序列化 json 时,我只想覆盖我有权的属性,这适用于类 A 中的属性(在 name 属性中仍然是旧值,因为我无权更新,属性属性已更新)但它不适用于 B 项列表 - 而不是像 A 一样更改 B 对象(名称仍然是旧值,属性是从 json 更新的)为 B 列表创建新对象,并且 name 保持 null,因为作为用户,我无权将值写入 name 属性。如果我在 B 列表中设置@JsonMerge,而不是合并,我会在一个列表中获取旧的(名称已设置但属性未更改的对象)和新创建的对象(属性更改但名称=null 的对象)......谁能帮我?

java 嵌套列表 json-deserialization objectmapper json-view

评论


答:

0赞 SpaceTrucker 5/2/2023 #1

这里有两个问题。第一个问题是基于如果存在读取方法,则不读取属性值。因此,嵌套对象的反序列化始终从一个值开始,因此首先构造一个新实例。com.fasterxml.jackson.databind.deser.impl.MethodPropertynull

另一个问题是处理已经是列表一部分的集合元素。

要解决第一个问题,请执行以下操作:

  • 如果存在读取方法,则创建一个读取属性值的实现,然后使用该值作为反序列化的基础:com.fasterxml.jackson.databind.deser.SettableBeanProperty
public class DeepUpdatingMethodProperty extends SettableBeanProperty {

    private final MethodProperty delegate;
    private final Method propertyReader;

    public DeepUpdatingMethodProperty(MethodProperty src, Method propertyReader) {
        super(src);
        this.delegate = src;
        this.propertyReader = propertyReader;
    }

    @Override
    public SettableBeanProperty withValueDeserializer(JsonDeserializer<?> deser) {
        return new DeepUpdatingMethodProperty((MethodProperty) delegate.withValueDeserializer(deser), propertyReader);
    }

    @Override
    public SettableBeanProperty withName(PropertyName newName) {
        return new DeepUpdatingMethodProperty((MethodProperty) delegate.withName(newName), propertyReader);
    }

    @Override
    public SettableBeanProperty withNullProvider(NullValueProvider nva) {
        return new DeepUpdatingMethodProperty((MethodProperty) delegate.withNullProvider(nva), propertyReader);
    }

    @Override
    public AnnotatedMember getMember() {
        return delegate.getMember();
    }

    @Override
    public <A extends Annotation> A getAnnotation(Class<A> acls) {
        return delegate.getAnnotation(acls);
    }

    @Override
    public void deserializeAndSet(JsonParser p, DeserializationContext ctxt, Object instance) throws IOException {
        deserializeSetAndReturn(p, ctxt, instance);
    }

    @Override
    public Object deserializeSetAndReturn(JsonParser p, DeserializationContext ctxt, Object instance)
            throws IOException {
        if (instance != null && !p.hasToken(JsonToken.VALUE_NULL)) {
            Object readValue;
            try {
                readValue = readValueFromInstance(instance);
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                _throwAsIOE(p, e, instance);
                return instance;
            }
            if (readValue != null) {
                return deepUpdateDeserializeSetAndReturn(p, ctxt, instance, readValue);
            }
        }
        return delegate.deserializeSetAndReturn(p, ctxt, instance);
    }

    private Object deepUpdateDeserializeSetAndReturn(JsonParser p, DeserializationContext ctxt, Object instance,
            Object readValue) throws IOException, JacksonException, JsonMappingException {
        Object value;
        if (_valueTypeDeserializer == null) {
            value = _valueDeserializer.deserialize(p, ctxt, readValue);
            if (value == null) {
                if (NullsConstantProvider.isSkipper(_nullProvider)) {
                    return instance;
                }
                value = _nullProvider.getNullValue(ctxt);
            }
        } else {
            value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer, readValue);
        }
        return setAndReturn(instance, value);
    }

    private Object readValueFromInstance(Object instance)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        return propertyReader.invoke(instance);
    }

    @Override
    public void set(Object instance, Object value) throws IOException {
        delegate.set(instance, value);
    }

    @Override
    public Object setAndReturn(Object instance, Object value) throws IOException {
        return delegate.setAndReturn(instance, value);
    }
}
  • 如果存在属性读取方法,请使用 a 修改 a 的属性以使用上述实现com.fasterxml.jackson.databind.deser.BeanDeserializerModifiercom.fasterxml.jackson.databind.deser.BeanDeserializer
public class DeepUpdatingDeserializerModifier extends BeanDeserializerModifier {
    
    @Override
    public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc,
            JsonDeserializer<?> deserializer) {
        if(deserializer instanceof BeanDeserializer beanDeserializer) {
            enableDeepUpdateForReadableProperties(beanDesc, beanDeserializer);
        }
        return deserializer;
    }

    private void enableDeepUpdateForReadableProperties(BeanDescription beanDesc, BeanDeserializer beanDeserializer) {
        Iterator<SettableBeanProperty> iter = beanDeserializer.properties();
        while(iter.hasNext()) {
            SettableBeanProperty property = iter.next();
            if (property instanceof MethodProperty methodProperty) {
                Method propertyReader = getPropertyReader(methodProperty, beanDesc);
                if (propertyReader != null) {
                    DeepUpdatingMethodProperty adoptedProperty = new DeepUpdatingMethodProperty(methodProperty, propertyReader);
                    beanDeserializer.replaceProperty(methodProperty, adoptedProperty);
                }
            }
        }
    }

    private static Method getPropertyReader(MethodProperty src, BeanDescription beanDesc) {
        BeanInfo beanInfo;
        Class<?> propertyRawClass = beanDesc.getBeanClass();
        try {
            beanInfo = Introspector.getBeanInfo(propertyRawClass);
        } catch (IntrospectionException e) {
            throw new IllegalStateException(MessageFormat.format("Could not introspect {0}.", propertyRawClass), e);
        }
        return Arrays.asList(beanInfo.getPropertyDescriptors()).stream()
                .filter(e -> Objects.equals(src.getName(), e.getName())).map(PropertyDescriptor::getReadMethod)
                .findFirst().orElse(null);
    }
}

请注意,这仅适用于方法属性,而不适用于构造函数属性。

关于第二个问题,我将集合反序列化更改为不同的内容,以便我使用类似的东西来反序列化集合,而不是普通的 json 表示:

{
    "someListProperty": {
        "collectionModifications": [{
                "_type": "removeIf",
                "predicate": {
                    "_type": "hasPropertyWithValue",
                    "property": "foo",
                    "valueMatcher": {
                        "_type": "or",
                        "matchers": [{
                                "_type": "equalTo",
                                "value": "foobar"
                            }, {
                                "_type": "startsWith",
                                "prefix": "foo."
                            }
                        ]
                    }
                }
            }, {
                "_type": "add",
                "elements": [{
                        "foo": "bar"
                    }, {
                        "foo": "baz"
                    }
                ]
            }
        ]
    }
}

上面的示例将首先从列表中删除属性等于或以开头的值,然后添加 中指定的值。您可以根据自己的需要实现自己的。但是,我发现在这种情况下使用地图而不是列表更容易,但我的示例中还没有这样的用例。在示例中,可以使用该属性作为映射键。foofoobarfoo.elementscom.fasterxml.jackson.databind.deser.std.CollectionDeserializername