ジャクソン-ジェネリッククラスを使用して逆シリアル化


147

私は次のクラスにデシリアライズする必要があるjson文字列を持っています

class Data <T> {
    int found;
    Class<T> hits
}

どうすればいいのですか?これが通常の方法です

mapper.readValue(jsonString, Data.class);

しかし、どのように私はTが何の略であるかについて言及しますか



回答:


238

TypeReference使用するジェネリック型ごとにオブジェクトを作成し、それを逆シリアル化に使用する必要があります。例えば ​​-

mapper.readValue(jsonString, new TypeReference<Data<String>>() {});

TypeReference <Data <T >>(){}として使用する必要がありますが、次のエラーが発生します-java.lang.classからプライベートjava.lang.class.Class()にアクセスできません。アクセスの設定に失敗しました。java.lang.Classコンストラクタアクセスできるようにすることはできません
gnjago

いいえ、Data<T>それはタイプではありません。実際のクラスを指定する必要があります。それ以外はと同じData<Object>です。
StaxMan 2012

18
実行時までどのクラスかわからない場合はどうなりますか?実行時にクラスをパラメーターとして取得します。このようにpublic <T> void deSerialize(Class <T> clazz {ObjectMapper mapper = new ObjectMapper(); mapper.readValue(jsonString、new TypeReference <Json <T >>(){});}
gnjago

1
私は正しくここに完全な質問をしてきたstackoverflow.com/questions/11659844/...
gnjago

の完全なパッケージ名はTypeReference何ですか?それがありますかcom.fasterxml.jackson.core.type
レイヤン

88

それを行うことはできません:のように、完全に解決された型を指定する必要がありますData<MyType>Tは単なる変数であり、無意味です。

しかし、それTが静的ではなく知られることになる場合は、TypeReference動的に同等のものを作成する必要があります。参照されている他の質問はすでにこれについて言及している可能性がありますが、次のようになります。

public Data<T> read(InputStream json, Class<T> contentClass) {
   JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, contentClass);
   return mapper.readValue(json, type);
}

2
実行時までどのクラスかわからない場合はどうなりますか?実行時にクラスをパラメーターとして取得します。このようにpublic <T> void deSerialize(Class <T> clazz {ObjectMapper mapper = new ObjectMapper(); mapper.readValue(jsonString、new TypeReference <Json <T >>(){});}
gnjago

1
私はここで完全な質問をしましたstackoverflow.com/questions/11659844/…–
gnjago

2
必要がために、ではないとして、それからちょうどクラスを渡すTypeReferencereturn mapper.readValue(json, clazz);正確には何の問題はここですか?
StaxMan 2012

2
問題は、「データ」がジェネリッククラスであることです。実行時にTのタイプを指定する必要があります。パラメータclazzは、実行時に使用されるものです。それで、どのようにreadValueを呼び出すのですか?新しいTypeReferenceでそれを呼び出す> JSON <T >>完全な質問はここで働いていませんstackoverflow.com/questions/11659844/...
gnjago

2
OK。次に、TypeFactory..を使用する必要があります。私の回答を編集します。
StaxMan 2012

30

最初に行うのはシリアライズで、次にデシリアライズを行うことができます。
したがって、シリアライズする場合は、を使用@JsonTypeInfoして、クラス情報をjsonデータに書き込むようにしてください。あなたができることはこのようなものです:

Class Data <T> {
    int found;
    @JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")
    Class<T> hits
}

次に、逆シリアル化すると、Jacksonがデータを逆シリアル化して、変数ヒットが実際に実行時のクラスになることを確認します。


機能しない、エラーcom.fasterxml.jackson.databind.exc.InvalidTypeIdExceptionが発生する:[シンプルタイプ、クラスjava.lang.Object]のサブタイプを解決しようとしたときにタイプIDが見つからない:タイプIDプロパティ '@class'がありません(POJOの場合)プロパティ 'data')
gaurav9620

15

クラスData <>の場合

ObjectMapper mapper = new ObjectMapper();
JavaType type = mapper.getTypeFactory().constructParametrizedType(Data.class, Data.class, Parameter.class);
Data<Parameter> dataParam = mapper.readValue(jsonString,type)

これは非推奨になりました。
Evan Gertis

13

Utilクラスに静的メソッドを記述するだけです。ファイルからJsonを読み取っています。あなたはまたreadValueに文字列を与えることができます

public static <T> T convertJsonToPOJO(String filePath, Class<?> target) throws JsonParseException, JsonMappingException, IOException, ClassNotFoundException {
        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.readValue(new File(filePath), objectMapper .getTypeFactory().constructCollectionType(List.class, Class.forName(target.getName())));
}

使用法:

List<TaskBean> list =  Util.<List<TaskBean>>convertJsonToPOJO("E:/J2eeWorkspaces/az_workspace_svn/az-client-service/dir1/dir2/filename.json", TaskBean.class);

7

ジェネリック型の型を知っている別のクラスでそれをラップできます。

例えば、

class Wrapper {
 private Data<Something> data;
}
mapper.readValue(jsonString, Wrapper.class);

ここ何かは具体的なタイプです。具象化されたタイプごとにラッパーが必要です。それ以外の場合、ジャクソンは作成するオブジェクトを認識しません。


6

逆シリアル化が必要なJSON文字列には、パラメーターに関する型情報が含まれている必要がありますT
パラメーターの型に関する型情報が表示されるように、パラメーターTとしてクラスに渡すことができるすべてのクラスにジャクソンアノテーションを配置する必要がありDataます。TをJSON文字列から読み書きできるがあります。

T抽象クラスを拡張する任意のクラスであると想定しますResult

class Data <T extends Result> {
    int found;
    Class<T> hits
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
@JsonSubTypes({
        @JsonSubTypes.Type(value = ImageResult.class, name = "ImageResult"),
        @JsonSubTypes.Type(value = NewsResult.class, name = "NewsResult")})
public abstract class Result {

}

public class ImageResult extends Result {

}

public class NewsResult extends Result {

}

パラメーターとして渡すことができる各クラス(またはそれらの共通のスーパータイプ)にT 注釈が付けられると、Jacksonはパラメーターに関する情報をTJSONに含めます。このようなJSONは、Tコンパイル時にパラメーターを知らなくても逆シリアル化できます。
このジャクソンのドキュメントリンクは、ポリモーフィックな逆シリアル化について説明していますが、この質問についても参照するのに役立ちます。


リストが必要な場合、どうすればこれを管理できますか?たとえば、List <ImageResult>
Luca Archidiacono

5

Jackson 2.5から、それを解決するためのエレガントな方法は、 TypeFactory.constructParametricType(Class parametrized、Class ... parameterClasses)メソッドを使用することJavaTypeです。

に逆シリアル化したい場合Data<String>、次のことができます:

// the json variable may be a String, an InputStream and so for...
JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, String.class);
Data<String> data = mapper.readValue(json, type);

クラスが複数のパラメーター化された型を宣言した場合は、それほど難しくはないことに注意してください。

class Data <T, U> {
    int found;
    Class<T> hits;
    List<U> list;
}

私たちはできる:

JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, String.class, Integer);
Data<String, Integer> data = mapper.readValue(json, type);

すばらしい、ありがとう。typereferenceを使用すると、マップから特定のオブジェクトへのクラスキャスト例外が発生しましたが、これは実際に機能します。
タクシアズマ

1
public class Data<T> extends JsonDeserializer implements ContextualDeserializer {
    private Class<T> cls;
    public JsonDeserializer createContextual(DeserializationContext ctx, BeanProperty prop) throws JsonMappingException {
        cls = (Class<T>) ctx.getContextualType().getRawClass();
        return this;
    }
    ...
 }

0

scalaを使用していて、コンパイル時にジェネリック型を知っているが、すべてのAPIのどこにでも手動でTypeReferenceを渡したくない場合は、次のコードを使用できます(jackson 2.9.5を使用)。

def read[T](entityStream: InputStream)(implicit typeTag: WeakTypeTag[T]): T = {

    //nathang: all of this *crazy* scala reflection allows us to handle List[Seq[Map[Int,Value]]]] without passing
    // new TypeReference[List[Seq[Map[Int,Value]]]]](){} to the function

    def recursiveFindGenericClasses(t: Type): JavaType = {
      val current = typeTag.mirror.runtimeClass(t)

      if (t.typeArgs.isEmpty) {
        val noSubtypes = Seq.empty[Class[_]]
        factory.constructParametricType(current, noSubtypes:_*)
      }

      else {
        val genericSubtypes: Seq[JavaType] = t.typeArgs.map(recursiveFindGenericClasses)
        factory.constructParametricType(current, genericSubtypes:_*)
      }

    }

    val javaType = recursiveFindGenericClasses(typeTag.tpe)

    json.readValue[T](entityStream, javaType)
  }

これは次のように使用できます:

read[List[Map[Int, SomethingToSerialize]]](inputStream)

0

Jacksonで汎用JSON文字列をJavaオブジェクトに逆シリアル化するには、次のものが必要です。

  1. JSONクラスを定義します。

  2. 属性マッピングを実行します。

最終的なコード、テスト済み、すぐに使用できるもの:

static class MyJSON {

    private Map<String, Object> content = new HashMap<>();

    @JsonAnySetter
    public void setContent(String key, Object value) {
        content.put(key, value);
    }
}

String json = "{\"City\":\"Prague\"}";

try {

    MyPOJO myPOJO = objectMapper.readValue(json, MyPOJO.class);

    String jsonAttVal = myPOJO.content.get("City").toString();

    System.out.println(jsonAttVal);

} catch (IOException e) {
    e.printStackTrace();
}

重要:
@JsonAnySetterアノテーションは必須です。これにより、一般的なJSON解析とポピュレーションが保証されます。

ネストされた配列のより複雑なケースについては、Baeldungリファレンスを参照してください:https ://www.baeldung.com/jackson-mapping-dynamic-object


0

あまりよくないが単純な決定の例(Jacksonだけでなく、Spring RestTemplateなども):

Set<MyClass> res = new HashSet<>();
objectMapper.readValue(json, res.getClass());
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.