Jackson - Deserializacja przy użyciu klasy ogólnej

Odpowiedzi:

238

Musisz utworzyć TypeReferenceobiekt dla każdego używanego typu ogólnego i użyć go do deserializacji. Na przykład -

mapper.readValue(jsonString, new TypeReference<Data<String>>() {});
Eser Aygün
źródło
Muszę użyć go jako TypeReference <Data <T>> () {} ... Ale pojawia się następujący błąd - nie mogę uzyskać dostępu do prywatnego java.lang.class.Class () z java.lang.class. Nie udało się ustawić dostępu. Nie można udostępnić konstruktora java.lang.Class
gnjago
Nie, nie Data<T>, to NIE jest typ. Musisz określić aktualną klasę; w przeciwnym razie jest taki sam jak Data<Object>.
StaxMan
18
A jeśli nie wiem, jaka to klasa do czasu uruchomienia? Otrzymam klasę jako parametr podczas działania. Podobnie jak ten public <T> void deSerialize (Class <T> clazz {ObjectMapper mapper = new ObjectMapper (); mapper.readValue (jsonString, new TypeReference <Json <T>> () {});}
gnjago
1
Tutaj poprawnie zadałem pełne pytanie stackoverflow.com/questions/11659844/…
gnjago
jaka jest pełna nazwa pakietu TypeReference? czy to com.fasterxml.jackson.core.typejest
Lei Yang
88

Nie możesz tego zrobić: musisz określić typ w pełni rozwiązany, na przykład Data<MyType>. Tjest tylko zmienną i bez znaczenia.

Ale jeśli masz na myśli, że Tbędzie znany, tylko nie statycznie, musisz utworzyć odpowiednik TypeReferencedynamicznie. Inne przywoływane pytania mogą już o tym wspominać, ale powinno to wyglądać mniej więcej tak:

public Data<T> read(InputStream json, Class<T> contentClass) {
   JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, contentClass);
   return mapper.readValue(json, type);
}
StaxMan
źródło
2
A jeśli nie wiem, jaka to klasa do czasu uruchomienia? Otrzymam klasę jako parametr podczas działania. Podobnie jak ten public <T> void deSerialize (Class <T> clazz {ObjectMapper mapper = new ObjectMapper (); mapper.readValue (jsonString, new TypeReference <Json <T>> () {});}
gnjago
1
Tutaj zadałem pełne pytanie stackoverflow.com/questions/11659844/…
gnjago
2
Następnie wystarczy zaliczyć klasę tak, jak jest, bez potrzeby TypeReference: Na return mapper.readValue(json, clazz);czym dokładnie polega problem?
StaxMan
2
Problem polega na tym, że „Dane” to klasa ogólna. Muszę określić, jaki typ T jest w czasie wykonywania. Parametr clazz jest tym, co robimy w czasie wykonywania. Jak więc wywołać readValue? wywołanie go nowym TypeReference> Json <T>> nie działa Pełne pytanie jest tutaj stackoverflow.com/questions/11659844/…
gnjago
2
Dobrze. Następnie musisz użyć TypeFactory… Zmienię swoją odpowiedź.
StaxMan
30

Pierwszą rzeczą, którą musisz zrobić, jest serializacja, a następnie możesz przeprowadzić deserializację.
więc kiedy robisz serializację, powinieneś użyć, @JsonTypeInfoaby pozwolić Jacksonowi zapisywać informacje o klasach do twoich danych JSON. Oto co możesz zrobić:

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

Następnie, kiedy deserializujesz, zobaczysz, że jackson zdeserializował twoje dane do klasy, w której trafia twoja zmienna, w rzeczywistości w czasie wykonywania.

CharlieQ
źródło
nie działa, pojawia się błąd poniżej com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Brak identyfikatora typu podczas próby rozwiązania podtypu [typ prosty, klasa java.lang.Object]: brak właściwości identyfikatora typu „@class” (dla POJO property 'data')
gaurav9620
15

Dla klasy Data <>

ObjectMapper mapper = new ObjectMapper();
JavaType type = mapper.getTypeFactory().constructParametrizedType(Data.class, Data.class, Parameter.class);
Data<Parameter> dataParam = mapper.readValue(jsonString,type)
Devesh
źródło
To jest teraz przestarzałe.
Evan Gertis
13

Po prostu napisz metodę statyczną w klasie Util. Czytam Json z pliku. możesz nadać String również 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())));
}

Stosowanie:

List<TaskBean> list =  Util.<List<TaskBean>>convertJsonToPOJO("E:/J2eeWorkspaces/az_workspace_svn/az-client-service/dir1/dir2/filename.json", TaskBean.class);
AZ_
źródło
7

Możesz umieścić go w innej klasie, która zna typ twojego ogólnego typu.

Na przykład,

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

Tutaj Coś jest konkretnym typem. Potrzebujesz opakowania na zreifikowany typ. W przeciwnym razie Jackson nie wie, jakie obiekty tworzyć.

sksamuel
źródło
6

Ciąg JSON, który musi zostać zdeserializowany, będzie musiał zawierać informacje o typie parametru T.
Będziesz musiał umieścić adnotacje Jacksona na każdej klasie, która może być przekazana jako parametr Tdo klasy, Dataaby TJackson mógł odczytać informacje o typie parametru z / zapisać do łańcucha JSON.

Załóżmy, że Tmoże to być dowolna klasa, która rozszerza klasę abstrakcyjną 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 {

}

Gdy każda klasa (lub ich wspólny nadtyp), która może zostać przekazana jako parametr, T zostanie opatrzona adnotacją, Jackson umieści informacje o parametrze Tw JSON. Taki JSON można następnie deserializować bez znajomości parametru Tw czasie kompilacji.
Ten link do dokumentacji Jacksona mówi o deserializacji polimorficznej, ale jest przydatny również w przypadku tego pytania.

Sanjeev Sachdev
źródło
i jak sobie z tym poradzić, jeśli chcę mieć listę? Powiedzmy, że List <ImageResult>
Luca Archidiacono
5

Od Jackson 2.5, elegancki sposób rozwiązania tego problemu polega na użyciu TypeFactory.constructParametricType (Class parametized, Class ... parameterClasses) , która pozwala na proste zdefiniowanie Jacksona JavaTypeprzez określenie sparametryzowanej klasy i jej sparametryzowanych typów.

Przypuśćmy, że chcesz przeprowadzić deserializację Data<String> , możesz wykonać:

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

Zwróć uwagę, że gdyby klasa zadeklarowała wiele sparametryzowanych typów, nie byłoby to naprawdę trudniejsze:

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

Moglibyśmy zrobić :

JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, String.class, Integer);
Data<String, Integer> data = mapper.readValue(json, type);
davidxxx
źródło
Super, dzięki, że zadziałało. Z typereferencją dostałem wyjątek od mapowania do konkretnego obiektu, ale to naprawdę działa.
Tacsiazuma
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;
    }
    ...
 }
mikka22
źródło
0

jeśli używasz scala i znasz typ ogólny w czasie kompilacji, ale nie chcesz ręcznie przekazywać TypeReference wszędzie we wszystkich swoich api lachach, możesz użyć następującego kodu (z jacksonem 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)
  }

którego można użyć w ten sposób:

read[List[Map[Int, SomethingToSerialize]]](inputStream)
nathan g
źródło
0

Aby deserializować ogólny ciąg JSON do obiektu Java za pomocą Jacksona, potrzebujesz:

  1. Aby zdefiniować klasę JSON.

  2. Wykonaj mapowanie atrybutów.

Ostateczny kod, przetestowany i gotowy do użycia:

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

Ważne:
@JsonAnySetter adnotacja jest obowiązkowa, zapewnia ogólne analizowanie i wypełnianie JSON.

Bardziej skomplikowane przypadki z zagnieżdżonymi tablicami można znaleźć w dokumencie Baeldung: https://www.baeldung.com/jackson-mapping-dynamic-object

Mike B.
źródło
0

Przykład niezbyt dobrej, ale prostej decyzji (nie tylko dla Jacksona, ale także dla Spring RestTemplate itp.):

Set<MyClass> res = new HashSet<>();
objectMapper.readValue(json, res.getClass());
Demel
źródło