2011-11-29 7 views
11

私はジャクソンの質問があります。 ジャクソンはオブジェクトまたは配列を逆シリアル化します

2種類を有することができるプロパティをデシリアライズする方法はあり

は、いくつかのオブジェクトのためにそれはすなわち、それは空の配列として表示されます他人のために、この

"someObj" : { "obj1" : 5, etc....} 

よう

"someObj" : [] 

表示されますどんな助けもありがとう!

ありがとうございます!

答えて

12

ジャクソンには、この特定のケースを自動的に処理する組み込みの設定がないため、カスタムのデシリアライズ処理が必要です。

以下は、このようなカスタム非直列化の例を示しています。

import java.io.IOException; 

import org.codehaus.jackson.JsonNode; 
import org.codehaus.jackson.JsonParser; 
import org.codehaus.jackson.JsonProcessingException; 
import org.codehaus.jackson.Version; 
import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility; 
import org.codehaus.jackson.annotate.JsonMethod; 
import org.codehaus.jackson.map.DeserializationContext; 
import org.codehaus.jackson.map.JsonDeserializer; 
import org.codehaus.jackson.map.ObjectMapper; 
import org.codehaus.jackson.map.module.SimpleModule; 

public class JacksonFoo 
{ 
    public static void main(String[] args) throws Exception 
    { 
    // {"property1":{"property2":42}} 
    String json1 = "{\"property1\":{\"property2\":42}}"; 

    // {"property1":[]} 
    String json2 = "{\"property1\":[]}"; 

    SimpleModule module = new SimpleModule("", Version.unknownVersion()); 
    module.addDeserializer(Thing2.class, new ArrayAsNullDeserializer()); 

    ObjectMapper mapper = new ObjectMapper().setVisibility(JsonMethod.FIELD, Visibility.ANY).withModule(module); 

    Thing1 firstThing = mapper.readValue(json1, Thing1.class); 
    System.out.println(firstThing); 
    // output: 
    // Thing1: property1=Thing2: property2=42 

    Thing1 secondThing = mapper.readValue(json2, Thing1.class); 
    System.out.println(secondThing); 
    // output: 
    // Thing1: property1=null 
    } 
} 

class Thing1 
{ 
    Thing2 property1; 

    @Override 
    public String toString() 
    { 
    return String.format("Thing1: property1=%s", property1); 
    } 
} 

class Thing2 
{ 
    int property2; 

    @Override 
    public String toString() 
    { 
    return String.format("Thing2: property2=%d", property2); 
    } 
} 

class ArrayAsNullDeserializer extends JsonDeserializer<Thing2> 
{ 
    @Override 
    public Thing2 deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException 
    { 
    JsonNode node = jp.readValueAsTree(); 
    if (node.isObject()) 
     return new ObjectMapper().setVisibility(JsonMethod.FIELD, Visibility.ANY).readValue(node, Thing2.class); 
    return null; 
    } 
} 

(あなたは常にコレクションにバインドするための入力を強制的にDeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAYを利用することができ、それはおそらく、私は問題は、現在記述されている方法を与え取ると思いアプローチではありません。)

+0

ありがとう、私はちょっとこれが事実になると思った...決して簡単にすることができます!あなたはそれを書く上で良いチュートリアルを知っていますか? – dardo

+0

元の入力を修正する機会はありませんか?最悪の場合、それが唯一のケースであれば、文字列は「[]」から「{}」に置き換えられます – stivlo

+0

私はそれがWebサービスコールだと思っています。会社2のウェブサイト – dardo

13

編集:Jackson 2.5.0以降では、DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_EMPTY_OBJECTを使用して問題を解決できます。ブルースが提供する

ソリューションは、いくつかの問題/欠点があります。

  • あなたはそれがシリアライザをキャッシュするので
  • ObjectMapperを再利用しなければならない、そのようにデシリアライズする必要が種類ごとにそのコードを複製する必要がありますし、デシリアライザは、したがって、作成するのに費用がかかる。あなたの配列にいくつかの値が含まれている場合は、おそらくjacksonがそれをデシリアライズできないようにしたいと思っています。ここで

その問題についての私の「一般的な」解決策です:

public abstract class EmptyArrayAsNullDeserializer<T> extends JsonDeserializer<T> { 

    private final Class<T> clazz; 

    protected EmptyArrayAsNullDeserializer(Class<T> clazz) { 
    this.clazz = clazz; 
    } 

    @Override 
    public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { 
    ObjectCodec oc = jp.getCodec(); 
    JsonNode node = oc.readTree(jp); 
    if (node.isArray() && !node.getElements().hasNext()) { 
     return null; 
    } 
    return oc.treeToValue(node, clazz); 
    } 
} 

、あなたはまだ、それぞれ異なるタイプのためのカスタムデシリアライザを作成する必要があるが、それを書くのは非常に簡単だとあなたがいません任意のロジックを複製:

public class Thing2Deserializer extends EmptyArrayAsNullDeserializer<Thing2> { 

    public Thing2Deserializer() { 
    super(Thing2.class); 
    } 
} 

は、あなたはいつものようにそれを使用する:

@JsonDeserialize(using = Thing2Deserializer.class) 

あなたがタイプごとに1つのカスタムデシリアライザを実装すなわち、その最後のステップ、を取り除くための方法を見つけた場合、私はすべての耳だ;)

+0

返信@fabien参照:私がしようとすると、デフォルト(引数なし)コンストラクタを持っていませんこれを使って。私は基本クラスにデフォルトコンストラクタを追加しようとしましたが、運がないわけではありません。 – speedynomads

+0

デシリアライズするjacksonに渡すクラスに、(その基本クラスだけでなく)パブリックのデフォルトコンストラクタがあることを確認します。上の例では、私はThing2.classで逆シリアル化します。 Thing2のクラスには、パブリックのデフォルトコンストラクタがあります。 逆シリアル化しようとしているクラスのすべてのサブクラスにもパブリックのデフォルトコンストラクタがあることを確認してください。疑わしい場合は、プリミティブ型だけの単純なクラスを試してみて、それがうまくいけばクラスの1つに問題があることを知ってください;) – fabien

2

別の角度は次のようになり、オブジェクトのために、より一般的にこの問題に取り組むためにありますBeanDeserializerを使用して逆シリアル化し、BeanDeserializerModifierを作成し、マッパーに登録します。 BeanDeserializerModifierは、BeanDeserializerFactoryのサブクラス化の代替品です。使用する通常のデシリアライザ以外のものを返す機会を与えたり、変更することができます。

したがって、構築時に別のデシリアライザを受け入れることができる新しいJsonDeserializerを作成してから、そのシリアライザを保持します。 deserializeメソッドでは、現在JsonToken.START_ARRAYを指しているJsonParserが渡されているかどうかを確認できます。 JsonToken.START_ARRAYが渡されていない場合は、このカスタムに渡されたデフォルトのデシリアライザを作成時にデシリアライズして使用してください。

最後に、デフォルトのデシリアライザがカスタムデシリアライザが使用しているコンテキストに正しく接続されるように、ResolvableDeserializerを実装するようにしてください。

class ArrayAsNullDeserialzer extends JsonDeserializer implements ResolvableDeserializer { 
    JsonDeserializer<?> mDefaultDeserializer; 

    @Override 
    /* Make sure the wrapped deserializer is usable in this deserializer's contexts */ 
    public void resolve(DeserializationContext ctxt) throws JsonMappingException { 
     ((ResolvableDeserializer) mDefaultDeserializer).resolve(ctxt); 
    } 

    /* Pass in the deserializer given to you by BeanDeserializerModifier */ 
    public ArrayAsNullDeserialzer(JsonDeserializer<?> defaultDeserializer) { 
     mDefaultDeserializer = defaultDeserializer; 
    } 

    @Override 
    public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { 
     JsonToken firstToken = jp.getCurrentToken(); 
     if (firstToken == JsonToken.START_ARRAY) { 
      //Optionally, fail if this is something besides an empty array 
      return null; 
     } else { 
      return mDefaultDeserializer.deserialize(jp, ctxt); 
     } 
    } 
} 

一般的なデシリアライザフックを使用できるようになりましたので、これを使用できる修飾語を作成しましょう。これは簡単ですが、BeanDeserializerModifierにmodifyDeserializerメソッドを実装するだけです。 となるデシリアライザがBeanを非直列化するために使用されています。また、デシリアライズされるBeanDescを渡すので、[]をすべての型に対してnullとして扱うかどうかはここで制御できます。

public class ArrayAsNullDeserialzerModifier extends BeanDeserializerModifier { 

    @Override 
    public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) { 
     if (true /* or check beanDesc to only do this for certain types, for example */) { 
      return new ArrayAsNullDeserializer(deserializer); 
     } else { 
      return deserializer; 
     } 
    } 
} 

最後に、BeanDeserializerModifierをObjectMapperに登録する必要があります。これを行うには、モジュールを作成し、セットアップにモディファイアを追加します(SimpleModuleにはこのためのフックはありません)。他の場所のモジュールについて詳しく読むことができますが、追加するモジュールがまだない場合の例です:

Module m = new Module() { 
    @Override public String getModuleName() { return "MyMapperModule"; } 
    @Override public Version version() { return Version.unknownVersion(); } 
    @Override public void setupModule(Module.SetupContext context) { 
     context.addBeanDeserializerModifier(new ArrayAsNullDeserialzerModifier()); 
    } 
}; 
関連する問題