How do I call the default deserializer from a custom deserializer in Jackson

前端 未结 11 1027
野趣味
野趣味 2020-11-22 14:26

I have a problem in my custom deserializer in Jackson. I want to access the default serializer to populate the object I am deserializing into. After the population I will do

相关标签:
11条回答
  • 2020-11-22 14:40

    As StaxMan already suggested you can do this by writing a BeanDeserializerModifier and registering it via SimpleModule. The following example should work:

    public class UserEventDeserializer extends StdDeserializer<User> implements ResolvableDeserializer
    {
      private static final long serialVersionUID = 7923585097068641765L;
    
      private final JsonDeserializer<?> defaultDeserializer;
    
      public UserEventDeserializer(JsonDeserializer<?> defaultDeserializer)
      {
        super(User.class);
        this.defaultDeserializer = defaultDeserializer;
      }
    
      @Override public User deserialize(JsonParser jp, DeserializationContext ctxt)
          throws IOException, JsonProcessingException
      {
        User deserializedUser = (User) defaultDeserializer.deserialize(jp, ctxt);
    
        // Special logic
    
        return deserializedUser;
      }
    
      // for some reason you have to implement ResolvableDeserializer when modifying BeanDeserializer
      // otherwise deserializing throws JsonMappingException??
      @Override public void resolve(DeserializationContext ctxt) throws JsonMappingException
      {
        ((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
      }
    
    
      public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException
      {
        SimpleModule module = new SimpleModule();
        module.setDeserializerModifier(new BeanDeserializerModifier()
        {
          @Override public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer)
          {
            if (beanDesc.getBeanClass() == User.class)
              return new UserEventDeserializer(deserializer);
            return deserializer;
          }
        });
    
    
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(module);
        User user = mapper.readValue(new File("test.json"), User.class);
      }
    }
    
    0 讨论(0)
  • 2020-11-22 14:43

    I was not ok with using BeanSerializerModifier since it forces to declare some behavioral changes in central ObjectMapper rather than in custom deserializer itself and in fact it is parallel solution to annotating entity class with JsonSerialize. If you feel it the similar way, you might appreciate my answer here: https://stackoverflow.com/a/43213463/653539

    0 讨论(0)
  • 2020-11-22 14:45

    The DeserializationContext has a readValue() method you may use. This should work for both the default deserializer and any custom deserializers you have.

    Just be sure to call traverse() on the JsonNode level you want to read to retrieve the JsonParser to pass to readValue().

    public class FooDeserializer extends StdDeserializer<FooBean> {
    
        private static final long serialVersionUID = 1L;
    
        public FooDeserializer() {
            this(null);
        }
    
        public FooDeserializer(Class<FooBean> t) {
            super(t);
        }
    
        @Override
        public FooBean deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            JsonNode node = jp.getCodec().readTree(jp);
            FooBean foo = new FooBean();
            foo.setBar(ctxt.readValue(node.get("bar").traverse(), BarBean.class));
            return foo;
        }
    
    }
    
    0 讨论(0)
  • 2020-11-22 14:48

    Along the lines of what Tomáš Záluský has suggested, in cases where using BeanDeserializerModifier is undesirable you can construct a default deserializer yourself using BeanDeserializerFactory, although there is some extra setup necessary. In context, this solution would look like so:

    public User deserialize(JsonParser jp, DeserializationContext ctxt)
      throws IOException, JsonProcessingException {
    
        ObjectCodec oc = jp.getCodec();
        JsonNode node = oc.readTree(jp);
        User deserializedUser = null;
    
        DeserializationConfig config = ctxt.getConfig();
        JavaType type = TypeFactory.defaultInstance().constructType(User.class);
        JsonDeserializer<Object> defaultDeserializer = BeanDeserializerFactory.instance.buildBeanDeserializer(ctxt, type, config.introspect(type));
    
        if (defaultDeserializer instanceof ResolvableDeserializer) {
            ((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
        }
    
        JsonParser treeParser = oc.treeAsTokens(node);
        config.initialize(treeParser);
    
        if (treeParser.getCurrentToken() == null) {
            treeParser.nextToken();
        }
    
        deserializedUser = (User) defaultDeserializer.deserialize(treeParser, context);
    
        return deserializedUser;
    }
    
    0 讨论(0)
  • 2020-11-22 14:49

    You are bound to fail if you try to create your custom deserializer from scratch.

    Instead, you need to get hold of the (fully configured) default deserializer instance through a custom BeanDeserializerModifier, and then pass this instance to your custom deserializer class:

    public ObjectMapper getMapperWithCustomDeserializer() {
        ObjectMapper objectMapper = new ObjectMapper();
    
        SimpleModule module = new SimpleModule();
        module.setDeserializerModifier(new BeanDeserializerModifier() {
            @Override
            public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
                        BeanDescription beanDesc, JsonDeserializer<?> defaultDeserializer) 
                if (beanDesc.getBeanClass() == User.class) {
                    return new UserEventDeserializer(defaultDeserializer);
                } else {
                    return defaultDeserializer;
                }
            }
        });
        objectMapper.registerModule(module);
    
        return objectMapper;
    }
    

    Note: This module registration replaces the @JsonDeserialize annotation, i.e. the User class or User fields should no longer be annotated with this annotation.

    The custom deserializer should then be based on a DelegatingDeserializer so that all methods delegate, unless you provide an explicit implementation:

    public class UserEventDeserializer extends DelegatingDeserializer {
    
        public UserEventDeserializer(JsonDeserializer<?> delegate) {
            super(delegate);
        }
    
        @Override
        protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegate) {
            return new UserEventDeserializer(newDelegate);
        }
    
        @Override
        public User deserialize(JsonParser p, DeserializationContext ctxt)
                throws IOException {
            User result = (User) super.deserialize(p, ctxt);
    
            // add special logic here
    
            return result;
        }
    }
    
    0 讨论(0)
  • 2020-11-22 14:55

    There are couple of ways to do this, but to do it right involves bit more work. Basically you can not use sub-classing, since information default deserializers need is built from class definitions.

    So what you can most likely use is to construct a BeanDeserializerModifier, register that via Module interface (use SimpleModule). You need to define/override modifyDeserializer, and for the specific case where you want to add your own logic (where type matches), construct your own deserializer, pass the default deserializer you are given. And then in deserialize() method you can just delegate call, take the result Object.

    Alternatively, if you must actually create and populate the object, you can do so and call overloaded version of deserialize() that takes third argument; object to deserialize into.

    Another way that might work (but not 100% sure) would be to specify Converter object (@JsonDeserialize(converter=MyConverter.class)). This is a new Jackson 2.2 feature. In your case, Converter would not actually convert type, but simplify modify the object: but I don't know if that would let you do exactly what you want, since the default deserializer would be called first, and only then your Converter.

    0 讨论(0)
提交回复
热议问题