web-dev-qa-db-ja.com

ジャクソンのカスタムデシリアライザーからデフォルトのデシリアライザーを呼び出す方法

ジャクソンのカスタムデシリアライザーに問題があります。デフォルトのシリアライザにアクセスして、デシリアライズするオブジェクトにデータを入力したい。移植後、いくつかのカスタム処理を行いますが、最初にデフォルトのジャクソンの動作でオブジェクトをデシリアライズします。

これは私が現在持っているコードです。

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に事前入力できるように、デフォルトのデシリアライザーを初期化する方法です。

カスタムデシリアライザー内からデシリアライズを呼び出すときシリアライザークラスをどのように構築しても、メソッドは現在のコンテキストから呼び出されるようです。 POJOの注釈のため。これにより、明らかな理由でスタックオーバーフロー例外が発生します。

BeanDeserializerを初期化しようとしましたが、プロセスは非常に複雑で、正しい方法を見つけることができませんでした。また、AnnotationIntrospectorの注釈を無視するのに役立つと考えて、DeserializerContextをオーバーロードしようとしました。最後に、JsonDeserializerBuildersを使用してある程度成功したかもしれませんが、Springからアプリケーションコンテキストを取得するためにいくつかの魔法のことをする必要がありました。 JsonDeserializerアノテーションを読み取らずにデシリアライゼーションコンテキストを構築する方法など、よりクリーンなソリューションにつながる可能性のあるものをいただければ幸いです。

86
Pablo Jomer

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

DeserializationContextには、使用できるreadValue()メソッドがあります。これは、デフォルトのデシリアライザーとお持ちのカスタムデシリアライザーの両方で機能するはずです。

JsonNodeを取得してtraverse()に渡す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;
    }

}
8
Derek Cochran

これを行うにはいくつかの方法がありますが、正しく行うにはもう少し作業が必要です。デフォルトのデシリアライザが必要とする情報はクラス定義から構築されるため、基本的にサブクラス化は使用できません。

したがって、おそらく使用できるのはBeanDeserializerModifierを作成し、Moduleインターフェイスを介して登録することです(SimpleModuleを使用)。 modifyDeserializerを定義/オーバーライドする必要があります。独自のロジック(型が一致する場合)を追加する特定のケースでは、独自のデシリアライザーを構築し、指定されたデフォルトのデシリアライザーを渡します。 deserialize()メソッドでは、呼び出しを委任し、結果オブジェクトを取得できます。

または、実際にオブジェクトを作成してデータを取り込む必要がある場合は、そのようにして、3番目の引数を取るdeserialize()のオーバーロードバージョンを呼び出すことができます。デシリアライズするオブジェクト。

動作する可能性がある別の方法(100%確実ではない)は、Converterオブジェクト(@JsonDeserialize(converter=MyConverter.class))を指定することです。これはジャクソン2.2の新しい機能です。あなたの場合、Converterは実際に型を変換しませんが、オブジェクトを簡単に変更します:しかし、デフォルトのデシリアライザが最初に呼び出され、Converter

8
StaxMan

https://stackoverflow.com/a/51927577/14731 で答えを見つけました。これは受け入れられた答えよりもはるかに読みやすいです。

public User deserialize(JsonParser jp, DeserializationContext ctxt)
    throws IOException, JsonProcessingException {
        JsonNode tree = jp.readTree(jp);

        // To call the default deserializer, simply invoke:
        User user = tree.get("user").traverse(jp.getCodec()).readValueAs(User.class);
        return user;
      }

これは本当に簡単になりません。

4
Gili

追加の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;
  }

}
4
Bill

私にとってより簡単な解決策は、ObjectMapperの別のBeanを追加し、それを使用してオブジェクトをデシリアライズすることでした( https://stackoverflow.com/users/1032167/varren コメントのおかげ) -私の場合、ID(int)またはオブジェクト全体にデシリアライズすることに興味がありました https://stackoverflow.com/a/46618193/98616

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

カスタムデシリアライザーを通過する必要がある各エンティティについて、私の場合は、Spring Boot AppのグローバルObjectMapper Beanで構成する必要があります(例: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;
}
1

カスタムデシリアライザーを最初から作成しようとすると、失敗することになります。

代わりに、カスタムBeanDeserializerModifierを介して(完全に構成された)デフォルトのデシリアライザーインスタンスを取得し、このインスタンスをカスタムデシリアライザークラスに渡す必要があります。

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フィールドには、このアノテーションを付けないでください。

カスタムデシリアライザーは、明示的な実装を提供しない限り、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;
    }
}
0
oberlies

BeanSerializerModifierを使用しても大丈夫ではありませんでした。カスタムデシリアライザ自体ではなく、中央のObjectMapperでいくつかの動作の変更を宣言するためです。実際、エンティティクラスにJsonSerialize。あなたがそれを同じように感じるなら、あなたはここで私の答えに感謝するかもしれません: https://stackoverflow.com/a/43213463/653539

0

これが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によって提供されるため、使用してください。

0
kaiser

Tomáš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();
    JsonDeserializer<Object> defaultDeserializer = BeanDeserializerFactory.instance.buildBeanDeserializer(ctxt, User.class, config.introspect(User.class));

    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
hopper