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


105

ジャクソンのカスタムデシリアライザに問題があります。デフォルトのシリアライザにアクセスして、逆シリアル化先のオブジェクトを作成します。ポピュレーションの後で、カスタムの処理をいくつか行いますが、最初にデフォルトの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を事前入力できるように、デフォルトのデシリアライザーを初期化する方法が必要です。

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

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


2
いいえ。これらのアプローチは役に立ちません。問題は、完全に構築されたデフォルトのデシリアライザが必要になることです。これには、ビルドしてからデシリアライザがアクセスできるようにする必要があります。DeserializationContext作成または変更すべきものではありません。それはによって提供されObjectMapperます。AnnotationIntrospector同様に、アクセスを取得するのに役立ちません。
StaxMan 2013

結局どうやってそれをやったの?
キトゥラス2014年

良い質問。よくわかりませんが、以下の答えが役に立ったと確信しています。私は現在、私たちが書いたコードを所持していません。解決策が見つかった場合は、他の人のためにここに投稿してください。
Pablo Jomer 2014年

回答:


92

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

ありがとう!私はすでにこれを別の方法で解決しましたが、時間があれば、解決策を調べます。
Pablo Jomer

5
同じことをする方法はありJsonSerializerますか?私はいくつかのシリアライザを持っていますが、それらには共通のコードがあるので、それを一般化したいと思います。私は直接シリアライザを呼び出そうとしましたが、結果はJSON結果に
ラップ

1
@herau BeanSerializerModifierResolvableSerializerおよびContextualSerializerシリアル化に使用する一致するインターフェイスです。
StaxMan 2016年

これはEEエディションのコンテナー(Wildfly 10)に適用されますか?JsonMappingException:(以前はjava.lang.NullPointerException)を取得します(参照チェーン:java.util.ArrayList [0]を介して)
user1927033

質問は使用しますreadTree()が、回答は使用しません。このアプローチには、Derek Cochranによって投稿されたアプローチと比べてどのような利点がありますか?これを動作させる方法はありreadTree()ますか?
ギリ

14

受け入れられた回答よりもはるかに読みやすい回答をansで見つけました。

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

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


こんにちはギリ!ありがとうございます。この答えを見つけて、検証する時間があることを願っています。現時点では答えを受け入れることができないため、私はそうする立場にありません。人々がこれが可能な解決策であると言うのを見たら、もちろん私は彼らをそれに向けて導きます。また、すべてのバージョンでこれが可能であるとは限りません。共有してくれてありがとう。
Pablo Jomer

Jackson 2.9.9ではコンパイルできません。JsonParser.readTree()は存在しません。
1

@ccleve単純なタイプミスのように見えます。修繕。
ギリ

これがジャクソン2.10で動作することを確認できます。ありがとうございます。
Stuart Leyland-Cole

1
私は、この結果、どのようにこの作品得ることはありませんStackOverflowErrorジャクソンは、再度同じシリアライザを使用するために、User...
john16384

12

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

読み取り先のレベルを呼び出しtraverse()JsonNodeJsonParserに渡すを取得してください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;
    }

}

DeserializationContext.readValue()は存在しません。これはObjectMapperのメソッドです
Pedro Borges

このソリューションは適切に機能していますが、Date.classなどの値クラスを逆シリアル化する場合は、nextToken()を呼び出す必要がある場合があります
revau.lt

9

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

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

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

機能する可能性のあるもう1つの方法(100%確実ではありません)は、Converterオブジェクト(@JsonDeserialize(converter=MyConverter.class))を指定することです。これは、Jackson 2.2の新機能です。あなたの場合、Converterは実際には型を変換しませんが、オブジェクトの変更を単純化しますConverter


私の答えはまだ残っています。委任するデフォルトのデシリアライザをJacksonに構築させる必要があります。それを「オーバーライド」する方法を見つけなければなりません。BeanDeserializerModifierそれを可能にするコールバックハンドラです。
StaxMan 2013

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
うん。私のために働いた唯一のアプローチ。デシリアライザへの再帰的な呼び出しが原因でStackOverflowErrorsが発生していました。
1

2

これはObjectMapperを使用したワンライナーです

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


2

何の線に沿ってトマーシュ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;
}

1

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


1

より簡単な解決策は、別のBeanを追加し、ObjectMapperそれを使用してオブジェクトをデシリアライズすることでした(https://stackoverflow.com/users/1032167/varrenコメントに感謝)-私の場合、そのIDにデシリアライズすることに興味がありました(int)またはオブジェクト全体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 Appのグローバル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;
}

0

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

代わりに、カスタムを介して(完全に構成された)デフォルトのデシリアライザーインスタンスを取得し、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;
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.