ジャクソンのカスタムデシリアライザーに問題があります。デフォルトのシリアライザにアクセスして、デシリアライズするオブジェクトにデータを入力したい。移植後、いくつかのカスタム処理を行いますが、最初にデフォルトのジャクソンの動作でオブジェクトをデシリアライズします。
これは私が現在持っているコードです。
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
アノテーションを読み取らずにデシリアライゼーションコンテキストを構築する方法など、よりクリーンなソリューションにつながる可能性のあるものをいただければ幸いです。
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);
}
}
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;
}
}
これを行うにはいくつかの方法がありますが、正しく行うにはもう少し作業が必要です。デフォルトのデシリアライザが必要とする情報はクラス定義から構築されるため、基本的にサブクラス化は使用できません。
したがって、おそらく使用できるのはBeanDeserializerModifier
を作成し、Module
インターフェイスを介して登録することです(SimpleModule
を使用)。 modifyDeserializer
を定義/オーバーライドする必要があります。独自のロジック(型が一致する場合)を追加する特定のケースでは、独自のデシリアライザーを構築し、指定されたデフォルトのデシリアライザーを渡します。 deserialize()
メソッドでは、呼び出しを委任し、結果オブジェクトを取得できます。
または、実際にオブジェクトを作成してデータを取り込む必要がある場合は、そのようにして、3番目の引数を取るdeserialize()
のオーバーロードバージョンを呼び出すことができます。デシリアライズするオブジェクト。
動作する可能性がある別の方法(100%確実ではない)は、Converter
オブジェクト(@JsonDeserialize(converter=MyConverter.class)
)を指定することです。これはジャクソン2.2の新しい機能です。あなたの場合、Converterは実際に型を変換しませんが、オブジェクトを簡単に変更します:しかし、デフォルトのデシリアライザが最初に呼び出され、Converter
。
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;
}
これは本当に簡単になりません。
追加の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;
}
}
私にとってより簡単な解決策は、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;
}
カスタムデシリアライザーを最初から作成しようとすると、失敗することになります。
代わりに、カスタム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;
}
}
BeanSerializerModifier
を使用しても大丈夫ではありませんでした。カスタムデシリアライザ自体ではなく、中央のObjectMapper
でいくつかの動作の変更を宣言するためです。実際、エンティティクラスにJsonSerialize
。あなたがそれを同じように感じるなら、あなたはここで私の答えに感謝するかもしれません: https://stackoverflow.com/a/43213463/653539
これが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によって提供されるため、使用してください。
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;
}