Jackson의 사용자 지정 deserializer에서 기본 deserializer를 어떻게 호출합니까?


105

Jackson의 사용자 지정 deserializer에 문제가 있습니다. 기본 직렬 변환기에 액세스하여 역 직렬화중인 개체를 채우고 싶습니다. 채우기 후에 몇 가지 사용자 지정 작업을 수행하지만 먼저 기본 Jackson 동작으로 개체를 역 직렬화하려고합니다.

이것이 제가 현재 가지고있는 코드입니다.

public class UserEventDeserializer extends StdDeserializer<User> {

  private static final long serialVersionUID = 7923585097068641765L;

  public UserEventDeserializer() {
    super(User.class);
  }

  @Override
  @Transactional
  public User deserialize(JsonParser jp, DeserializationContext ctxt)
      throws IOException, JsonProcessingException {

    ObjectCodec oc = jp.getCodec();
    JsonNode node = oc.readTree(jp);
    User deserializedUser = null;
    deserializedUser = super.deserialize(jp, ctxt, new User()); 
    // The previous line generates an exception java.lang.UnsupportedOperationException
    // Because there is no implementation of the deserializer.
    // I want a way to access the default spring deserializer for my User class.
    // How can I do that?

    //Special logic

    return deserializedUser;
  }

}

필요한 것은 기본 디시리얼라이저를 초기화하여 특수 로직을 시작하기 전에 POJO를 미리 채울 수있는 방법입니다.

사용자 지정 deserializer 내에서 deserialize를 호출 할 때 serializer 클래스를 구성하는 방법에 관계없이 현재 컨텍스트에서 메서드가 호출되는 것 같습니다. 내 POJO의 주석 때문입니다. 이로 인해 명백한 이유로 스택 오버플로 예외가 발생합니다.

나는 초기화를 시도 BeanDeserializer했지만 프로세스가 매우 복잡하고 올바른 방법을 찾지 못했습니다. 또한 AnnotationIntrospector.NET 파일의 주석을 무시하는 데 도움이 될 수 있다고 생각하면서을 (를) 사용하지 않도록 오버로드 해 보았습니다 DeserializerContext. 마지막으로 JsonDeserializerBuildersSpring에서 응용 프로그램 컨텍스트를 확보하기 위해 몇 가지 마법 작업을 수행해야했지만 성공했을 수도 있습니다 . 예를 들어 JsonDeserializer주석 을 읽지 않고 역 직렬화 컨텍스트를 구성하는 방법과 같이 더 깨끗한 솔루션으로 이끌 수있는 모든 것을 감사하겠습니다 .


2
아니요. 이러한 접근 방식은 도움이되지 않습니다. 문제는 완전히 구성된 기본 deserializer가 필요하다는 것입니다. 이를 위해서는 하나가 빌드되고 deserializer가 액세스 할 수 있어야합니다. DeserializationContext당신이 만들거나 변경해야 할 것이 아닙니다. 에서 제공합니다 ObjectMapper. AnnotationIntrospector마찬가지로 액세스 권한을 얻는 데 도움이되지 않습니다.
StaxMan 2013-08-21

결국 어떻게 했습니까?
khituras

좋은 질문. 잘 모르겠지만 아래 답변이 도움이되었다고 확신합니다. 나는 현재 우리가 작성한 코드를 소유하고 있지 않습니다. 해결책을 찾으면 다른 사람들을 위해 여기에 게시하십시오.
Pablo Jomer 2014 년

답변:


93

StaxMan이 이미 제안했듯이을 작성하고을 BeanDeserializerModifier통해 등록하여이를 수행 할 수 있습니다 SimpleModule. 다음 예제가 작동합니다.

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);
  }
}

감사! 이미 다른 방법으로 해결했지만 시간이 더 있으면 해결 방법을 살펴 보겠습니다.
파블로 Jomer

5
동일한 작업을 수행하는 방법이 JsonSerializer있습니까? 여러 직렬 변환기가 있지만 공통 코드가 있으므로 생성하고 싶습니다. 직렬 변환기를 직접 호출하려고하지만 결과는 JSON 결과에서
풀리지

1
@herau BeanSerializerModifier, ResolvableSerializerContextualSerializer직렬화 사용할 정합 인터페이스이다.
StaxMan 2016 년

EE 에디션 컨테이너 (Wildfly 10)에 적용됩니까? (java.lang.NullPointerException이 있었다)는 I JsonMappingException 얻을 (기준 체인을 통해 : 인 java.util.ArrayList [0])
user1927033

질문은 사용 readTree()하지만 대답은 그렇지 않습니다. 이 접근 방식과 Derek Cochran이 게시 한 접근 방식의 장점은 무엇입니까 ? 이 작업을 수행하는 방법이 readTree()있습니까?
Gili

14

나는 받아 들여진 대답보다 훨씬 더 읽기 쉬운 ans 에서 대답을 찾았다 .

    public User deserialize(JsonParser jp, DeserializationContext ctxt)
        throws IOException, JsonProcessingException {
            User user = jp.readValueAs(User.class);
             // some code
             return user;
          }

이보다 더 쉽지는 않습니다.


안녕 길리! 감사합니다. 사람들이이 답변을 찾고 검증 할 시간이 있기를 바랍니다. 나는 더 이상 그렇게 할 수있는 위치에 있지 않습니다. 지금은 대답을 받아 들일 수 없기 때문입니다. 사람들이 이것이 가능한 해결책이라고 말하는 것을 본다면 당연히 그들을 향해 안내 할 것입니다. 모든 버전에서 이것이 가능하지 않을 수도 있습니다. 공유해 주셔서 감사합니다.
Pablo Jomer 2018

Jackson 2.9.9로 컴파일하지 않습니다. JsonParser.readTree ()가 존재하지 않습니다.
ccleve

@ccleve 단순한 오타처럼 보입니다. 결정된.
Gili

이것이 Jackson 2.10에서 작동하는지 확인할 수 있습니다. 감사합니다!
Stuart Leyland-Cole

2
나는 이것이 어떻게 작동하는지 이해하지 못한다. 이것은 StackOverflowErrorJackson이 동일한 serializer를 다시 사용할 것이기 때문에 결과가 User..
john16384

12

에 사용할 수 DeserializationContext있는 readValue()방법이 있습니다. 이것은 기본 deserializer와 사용자 지정 deserializer 모두에서 작동합니다.

그냥 호출해야합니다 traverse()JsonNode당신이 검색 읽고 싶은 수준 JsonParser에 전달할 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;
    }

}

DeserialisationContext.readValue는 () ObjectMapper의 방법이 존재하지 않는
페드로 보르헤스에게

이 솔루션은 그러나 당신이 값 클래스 예를 들어 Date.class 역 직렬화하는 경우 nextToken ()를 호출해야 할 수도 있습니다, 잘 작동
revau.lt

9

이를 수행하는 몇 가지 방법이 있지만 올바르게 수행하려면 약간의 작업이 필요합니다. 기본적으로 기본 디시리얼라이저에 필요한 정보는 클래스 정의에서 빌드되기 때문에 하위 클래스를 사용할 수 없습니다.

따라서 가장 많이 사용할 수있는 것은를 생성하고 인터페이스 BeanDeserializerModifier를 통해 등록하는 것입니다 Module(사용 SimpleModule). 정의 / 재정의해야하며 modifyDeserializer, 고유 한 논리 (유형이 일치하는 위치)를 추가하려는 특정 경우에 대해 고유 한 deserializer를 생성하고 제공된 기본 deserializer를 전달해야합니다. 그런 다음 deserialize()메서드 에서 호출을 위임하고 결과 Object를 가져올 수 있습니다.

또는 실제로 개체를 만들고 채워야하는 경우 그렇게하여 deserialize()세 번째 인수 를 사용 하는 오버로드 된 버전을 호출 할 수 있습니다 . 역 직렬화 할 개체입니다.

작동 할 수있는 또 다른 방법 (100 % 확실하지는 않음)은 Converter객체 ( @JsonDeserialize(converter=MyConverter.class)) 를 지정하는 것 입니다. 이것은 새로운 Jackson 2.2 기능입니다. 귀하의 경우 Converter는 실제로 유형을 변환하지 않고 객체를 간단하게 수정합니다.하지만 기본 deserializer가 먼저 호출되고 Converter.


내 대답은 여전히 ​​유효합니다. Jackson이 위임 할 기본 deserializer를 구성하도록해야합니다. "무시"할 방법을 찾아야합니다. BeanDeserializerModifier이를 허용하는 콜백 핸들러입니다.
StaxMan 2013-08-21

7

추가 User 클래스를 선언 할 수있는 경우 주석을 사용하여 구현할 수 있습니다.

// your class
@JsonDeserialize(using = UserEventDeserializer.class)
public class User {
...
}

// extra user class
// reset deserializer attribute to default
@JsonDeserialize
public class UserPOJO extends User {
}

public class UserEventDeserializer extends StdDeserializer<User> {

  ...
  @Override
  public User deserialize(JsonParser jp, DeserializationContext ctxt)
      throws IOException, JsonProcessingException {
    // specify UserPOJO.class to invoke default deserializer
    User deserializedUser = jp.ReadValueAs(UserPOJO.class);
    return deserializedUser;

    // or if you need to walk the JSON tree

    ObjectMapper mapper = (ObjectMapper) jp.getCodec();
    JsonNode node = oc.readTree(jp);
    // specify UserPOJO.class to invoke default deserializer
    User deserializedUser = mapper.treeToValue(node, UserPOJO.class);

    return deserializedUser;
  }

}

1
예. 나를 위해 일한 유일한 접근 방식. deserializer에 대한 재귀 호출로 인해 StackOverflowErrors가 발생했습니다.
ccleve

3

무엇의 라인을 따라 토마시 Záluský 제안했다 사용하는 경우, BeanDeserializerModifier사용 자신 디시리얼라이저 기본을 구성 할 수 있습니다 바람직하지 않다 BeanDeserializerFactory필요한 몇 가지 추가 설정이 있지만,. 맥락에서이 솔루션은 다음과 같습니다.

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;
}

이것은 Jackson 2.9.9에서 꿈처럼 작동합니다. 주어진 다른 예제와 같이 StackOverflowError가 발생하지 않습니다.
meta1203

2

다음은 ObjectMapper를 사용하는 oneliner입니다.

public MyObject deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
    OMyObject object = new ObjectMapper().readValue(p, MyObject.class);
    // do whatever you want 
    return object;
}

그리고 제발 : 정말 어떤 문자열 값이나 다른 것을 사용할 필요가 없습니다. 필요한 모든 정보는 JsonParser에서 제공하므로 사용하십시오.


1

사용자 지정 deserializer 자체가 아닌 BeanSerializerModifier중앙에서 일부 동작 변경 사항을 선언해야하기 때문에 사용하는 것이 좋지 않았 ObjectMapper으며 실제로 JsonSerialize. 비슷한 방식으로 느낀다면 여기 내 대답에 감사 할 것입니다. https://stackoverflow.com/a/43213463/653539


1

나에게 더 간단한 해결책은 다른 bean을 추가 ObjectMapper하고 객체를 deserialize하는 데 사용하는 것입니다 ( https://stackoverflow.com/users/1032167/varren 주석 덕분에 ). 제 경우에는 ID로 deserialize하는 데 관심이 있습니다. (정수) 또는 전체 개체 https://stackoverflow.com/a/46618193/986160

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import org.springframework.context.annotation.Bean;

import java.io.IOException;

public class IdWrapperDeserializer<T> extends StdDeserializer<T> {

    private Class<T> clazz;

    public IdWrapperDeserializer(Class<T> clazz) {
        super(clazz);
        this.clazz = clazz;
    }

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
        mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
        mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
        return mapper;
    }

    @Override
    public T deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
        String json = jp.readValueAsTree().toString();
          // do your custom deserialization here using json
          // and decide when to use default deserialization using local objectMapper:
          T obj = objectMapper().readValue(json, clazz);

          return obj;
     }
}

커스텀 디시리얼라이저를 거쳐야하는 각 엔터티에 대해 ObjectMapper제 경우에는 Spring Boot 앱 의 전역 빈 에서 구성해야합니다 (예 :) Category.

@Bean
public ObjectMapper objectMapper() {
    ObjectMapper mapper = new ObjectMapper();
                mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
            mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
            mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
            mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
    SimpleModule testModule = new SimpleModule("MyModule")
            .addDeserializer(Category.class, new IdWrapperDeserializer(Category.class))

    mapper.registerModule(testModule);

    return mapper;
}

0

사용자 지정 deserializer를 처음부터 만들려고하면 실패 할 수 있습니다.

대신 custom을 통해 (완전히 구성된) 기본 deserializer 인스턴스를 확보 한 BeanDeserializerModifier다음이 인스턴스를 사용자 지정 deserializer 클래스에 전달해야합니다.

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;
}

참고 :이 모듈 등록은 @JsonDeserialize주석을 대체합니다 . 즉, User클래스 또는 User필드는 더 이상이 주석으로 주석 처리되지 않아야합니다.

사용자 지정 deserializer는 DelegatingDeserializer명시 적 구현을 ​​제공하지 않는 한 모든 메서드가 위임되도록를 기반 으로해야합니다.

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;
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.