Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BeanDeserializerModifier not called again for changed config #1956

Closed
simontb opened this issue Mar 2, 2018 · 4 comments
Closed

BeanDeserializerModifier not called again for changed config #1956

simontb opened this issue Mar 2, 2018 · 4 comments

Comments

@simontb
Copy link

simontb commented Mar 2, 2018

To work around #437 I wrote a BeanDeserializerModifier and override the updateBuilder method. There I retrieve the active view by calling config.getActiveView() and remove the properties that are not visible from the builder.

When I use the same ObjectMapper to read my model with different views, the DeserializerModifier is only called for the first read and therefore does not adjust the properties.

This is my model:

    public class Model {

    @JsonView(Viewable.class)
    String a;

    @JsonView(Hidden.class)
    String b;

    public String getA() {
        return a;
    }

    public void setA(String a) {
        this.a = a;
    }

    public String getB() {
        return b;
    }

    public void setB(String b) {
        this.b = b;
    }

}

This is the code reading the model:

public static void main(String... args) throws IOException {
    SimpleModule module = new SimpleModule();
    module.setDeserializerModifier(new JacksonFailOnDisabledPropertiesModifier());
    ObjectMapper mapper = new ObjectMapper().registerModule(module);

    String content = "{" +
            "\n\"a\":\"A\"" +
//            "," +
//            "\n\"b\": \"B\"" +
            "\n}";
    Model model = mapper.readerWithView(Viewable.class).forType(Model.class).readValue(content);

    System.out.println("A: " + model.getA());
    System.out.println("B: " + model.getB());
    System.out.println();

    model = mapper.readerWithView(Hidden.class).forType(Model.class).readValue(content);
    System.out.println("A: " + model.getA());
    System.out.println("B: " + model.getB());
}

I would expect the read with the view Hidden.class to fail, because a is provided instead of b, but the output is:

A: A
B: null

A: null
B: null
@cowtowncoder
Copy link
Member

@simontb Not a bug, feature. All serializers/deserializers are created once and cached. Modifiers are only called during creation. View application, on the other hand, is dynamic, and you can not rely on specific view settings in modifier. So this approach will unfortunately not work.

@simontb
Copy link
Author

simontb commented Mar 2, 2018

Thanks for the quick reply. I already figured out, that this is intended in the current implementation, but what's the point in having access to getActiveView() if this is not called for every deserialization? Creating a new ObjectMapper for every view is not an option, because it runs within a Spring application.

@cowtowncoder
Copy link
Member

getActiveView() is called for every serialization (or equivalent access), that does work.
It's only modifier that does not get called. Serializer instances have dynamic access to this information.

But to change active view you will need to use ObjectWriter (similar to ObjectReader), which is intended to be used when you need to configure settings. So mapper instance must be configured once, can not be changed (changes either do not take effect, or can cause problems).
But new ObjectReaders and ObjectWriters can be created from mapper (or reader/writer instances). They are light-weight to create (unlike ObjectMapper).

I hope this helps.

@mikeheeren
Copy link

Thanks for the quick reply. I already figured out, that this is intended in the current implementation, but what's the point in having access to getActiveView() if this is not called for every deserialization? Creating a new ObjectMapper for every view is not an option, because it runs within a Spring application.

Really late reply, but I had the same challenge. And also from a Spring (Boot 3) application. I noticed that the MappingJackson2HttpMessageConverter contains a customizeReader method that can be overridden. Here I create a new ObjectMapper (with the BeanDeserializerModifier) specificially for that view, cache it and return the ObjectReader from that mapper instead of the original one:

@Component
public class ActiveViewAwareMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {

    private final Map<Class<?>, ObjectMapper> activeViewAwareObjectMappers = new HashMap<>();

    public ActiveViewAwareMappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
        super(objectMapper);
    }

    @Override
    protected ObjectReader customizeReader(ObjectReader reader, JavaType javaType) {
        var activeView = reader.getConfig().getActiveView();
        if (activeView != null) {
            var customViewObjectMapper = activeViewAwareObjectMappers.computeIfAbsent(activeView, view -> {
                var module = new SimpleModule();
                module.setDeserializerModifier(new IgnoreJsonViewBeanDeserializerModifier(view));
                return getObjectMapper().copy().registerModule(module);
            });
            return customViewObjectMapper.readerWithView(activeView).forType(javaType);
        }
        return super.customizeReader(reader, javaType);
    }

    @RequiredArgsConstructor
    private static class IgnoreJsonViewBeanDeserializerModifier extends BeanDeserializerModifier {

        private final Class<?> view;

        @Override
        public BeanDeserializerBuilder updateBuilder(DeserializationConfig config,
                                                     BeanDescription beanDesc,
                                                     BeanDeserializerBuilder builder) {
            var propertyIterator = builder.getProperties();
            while (propertyIterator.hasNext()) {
                final var property = propertyIterator.next();
                if (!property.visibleInView(view)) {
                    builder.addIgnorable(property.getName());
                }
            }
            return builder;
        }

    }

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants