Jackson - Deserialize bằng cách sử dụng lớp chung


147

Tôi có một chuỗi json, mà tôi nên khử lớp cho lớp sau

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

Tôi phải làm nó như thế nào? Đây là cách thông thường

mapper.readValue(jsonString, Data.class);

Nhưng làm thế nào để tôi đề cập đến những gì T đại diện cho?



Câu trả lời:


238

Bạn cần tạo một TypeReferenceđối tượng cho từng loại chung mà bạn sử dụng và sử dụng nó để khử lưu huỳnh. Ví dụ -

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

Tôi phải sử dụng nó như TypeReference <Dữ liệu <T >> () {} ... Nhưng tôi đang gặp phải lỗi sau - không thể truy cập java.lang. Class.Class () từ java.lang. Class. Không thể đặt quyền truy cập. Không thể tạo một trình tạo java.lang.Class có thể truy cập
gnjago

Không, không Data<T>, đó KHÔNG phải là một loại. Bạn phải chỉ định lớp thực tế; nếu không nó là giống như Data<Object>.
StaxMan

18
Điều gì xảy ra nếu tôi không biết nó là lớp nào cho đến khi chạy? Tôi sẽ lấy lớp làm tham số trong thời gian chạy. Giống như công này <T> void Deserialize (Class <T> clazz {ObjectMapper mapper = new ObjectMapper (); mapper.readValue (jsonString, TypeReference mới <Json <T >> () {});}
gnjago

1
Tôi đã hỏi chính xác câu hỏi đầy đủ ở đây stackoverflow.com/questions/11659844/ từ
gnjago

Tên gói đầy đủ là TypeReferencegì? phải com.fasterxml.jackson.core.typekhông
Lôi Dương

88

Bạn không thể làm điều đó: bạn phải chỉ định loại được giải quyết đầy đủ, như Data<MyType>. Tchỉ là một biến số, và như là vô nghĩa.

Nhưng nếu bạn có nghĩa là Tsẽ được biết đến, chỉ cần không tĩnh, bạn cần tạo tương đương TypeReferenceđộng. Các câu hỏi khác được tham chiếu có thể đã đề cập đến vấn đề này, nhưng nó sẽ trông giống như:

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

2
Điều gì xảy ra nếu tôi không biết nó là lớp nào cho đến khi chạy? Tôi sẽ lấy lớp làm tham số trong thời gian chạy. Giống như công này <T> void Deserialize (Class <T> clazz {ObjectMapper mapper = new ObjectMapper (); mapper.readValue (jsonString, TypeReference mới <Json <T >> () {});}
gnjago

1
Tôi đã hỏi toàn bộ câu hỏi ở đây stackoverflow.com/questions/11659844/ từ
gnjago

2
Sau đó, chỉ cần vượt qua lớp như là, không cần TypeReference: return mapper.readValue(json, clazz);Chính xác vấn đề ở đây là gì?
StaxMan

2
Vấn đề là "Dữ liệu" là một lớp chung. Tôi cần xác định loại T trong thời gian chạy. Tham số clazz là những gì T chúng tôi tại thời gian chạy. Vì vậy, làm thế nào để gọi readValue? gọi nó với TypeReference mới> Json <T >> không hoạt động Câu hỏi đầy đủ có ở đây stackoverflow.com/questions/11659844/
Kẻ

2
Đồng ý. Sau đó, bạn cần sử dụng TypeFactory.. Tôi sẽ chỉnh sửa câu trả lời của tôi.
StaxMan

30

Điều đầu tiên bạn làm là tuần tự hóa, sau đó bạn có thể thực hiện khử lưu huỳnh.
vì vậy khi bạn thực hiện tuần tự hóa, bạn nên sử dụng @JsonTypeInfođể cho phép jackson ghi thông tin lớp vào dữ liệu json của bạn. Những gì bạn có thể làm là như thế này:

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

Sau đó, khi bạn giải tuần tự hóa, bạn sẽ thấy jackson đã giải tuần tự hóa dữ liệu của bạn thành một lớp mà các biến số của bạn thực sự có trong thời gian chạy.


không hoạt động, gặp lỗi dưới đây tài sản 'dữ liệu')
gaurav9620

15

Đối với dữ liệu lớp <>

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

Điều này bây giờ không được chấp nhận.
Evan Gertis

13

Chỉ cần viết một phương thức tĩnh trong lớp Util. Tôi đang đọc một Json từ một tập tin. bạn cũng có thể cung cấp String cho 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())));
}

Sử dụng:

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

7

Bạn có thể gói nó trong một lớp khác biết loại chung chung của bạn.

Ví dụ,

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

Ở đây một cái gì đó là một loại cụ thể. Bạn cần một trình bao bọc cho mỗi loại thống nhất. Nếu không thì Jackson không biết nên tạo đối tượng gì.


6

Chuỗi JSON cần được giải tuần tự hóa sẽ phải chứa thông tin loại về tham số T.
Bạn sẽ phải đặt các chú thích Jackson trên mỗi lớp có thể được truyền dưới dạng tham số Tcho lớp Datađể thông tin loại về loại tham số Tcó thể được đọc từ / ghi vào chuỗi JSON bởi Jackson.

Chúng ta hãy giả sử rằng Tcó thể là bất kỳ lớp nào mở rộng lớp trừu tượng 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 {

}

Khi mỗi lớp (hoặc siêu kiểu chung của chúng) có thể được chuyển qua làm tham số T được chú thích, Jackson sẽ bao gồm thông tin về tham số Ttrong JSON. JSON như vậy sau đó có thể được giải tuần tự hóa mà không cần biết tham số Ttại thời gian biên dịch. Liên kết tài liệu Jackson
này nói về Deserialization Polymorphic nhưng cũng hữu ích để tham khảo cho câu hỏi này.


và làm cách nào để quản lý mục này nếu tôi muốn có Danh sách? Giống như hãy nói Danh sách <ImageResult>
Luca Archidiacono

5

Từ Jackson 2.5, một cách đơn giản để giải quyết đó là sử dụng phương thức TypeFactory.constructParametricType (Class parametriised, Class ... tham số Class) cho phép xác định một JavaTypecách rõ ràng một Jackson bằng cách chỉ định lớp được tham số hóa và các loại tham số của nó.

Giả sử bạn muốn giải trừ Data<String>, bạn có thể làm:

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

Lưu ý rằng nếu lớp khai báo nhiều loại tham số, nó sẽ không thực sự khó hơn:

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

Chúng ta có thể làm:

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

Tuyệt vời, cảm ơn nó đã làm việc cho tôi. Với kiểu chữ tôi nhận được ngoại lệ classcast từ bản đồ đến đối tượng cụ thể nhưng điều này thực sự làm được việc.
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;
    }
    ...
 }

0

nếu bạn đang sử dụng scala và biết loại chung vào thời gian biên dịch, nhưng không muốn tự mình chuyển TypeReference ở mọi nơi trong tất cả các luật sư của mình, bạn có thể sử dụng mã sau (với 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)
  }

có thể được sử dụng như thế này:

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

0

Để giải tuần tự hóa một chuỗi JSON chung thành đối tượng Java với Jackson, bạn cần:

  1. Để định nghĩa một lớp JSON.

  2. Thực hiện ánh xạ thuộc tính.

Mã cuối cùng, đã thử nghiệm và sẵn sàng để được sử dụng:

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

Quan trọng:
@JsonAnySetter chú thích là bắt buộc, nó đảm bảo phân tích và phân tích JSON chung.

Đối với các trường hợp phức tạp hơn với mảng lồng nhau, vui lòng xem tài liệu tham khảo Baeldung: https://www.baeldung.com/jackson-mapping-dynamic-object


0

Ví dụ về quyết định không tốt lắm, nhưng quyết định đơn giản (không chỉ đối với Jackson, mà còn đối với Spring RestTemplate, v.v.):

Set<MyClass> res = new HashSet<>();
objectMapper.readValue(json, res.getClass());
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.