2016-03-22 16 views
9

Hãy tưởng tượng kịch bản sau đây:Làm thế nào để tạo một deserializer tùy chỉnh trong Jackson cho một loại chung?

class <T> Foo<T> { 
    .... 
} 

class Bar { 
    Foo<Something> foo; 
} 

Tôi muốn viết một Jackson deserializer tùy chỉnh cho Foo. Để thực hiện điều đó (ví dụ: để deserialize Bar lớp có thuộc tính Foo<Something>), tôi cần biết loại bê tông Foo<T>, được sử dụng trong Bar, vào thời gian deserialization (ví dụ: tôi cần biết rằng TSomething trong đó trường hợp đặc biệt).

Làm thế nào để viết một trình khử mùi như vậy? Nó sẽ có thể làm điều đó, vì Jackson làm điều đó với các bộ sưu tập và bản đồ đã nhập.

Làm rõ:

Dường như có 2 phần để giải pháp của vấn đề:

1) Lấy kiểu được khai báo tài sản foo bên Bar và sử dụng để deserialize Foo<Somehting>

2) Tìm hiểu tại thời gian deserialization rằng chúng tôi đang deserializing tài sản foo bên trong lớp Bar để hoàn thành thành công bước 1)

Làm cách nào để hoàn tất 1 và 2?

+0

Bạn có thể sử dụng phản ánh để tìm ra rằng * tuyên bố * loại 'foo' là' Foo '. Đó là về nó. Tôi không biết về Jackson để nói nếu đó là một phương tiện để kết thúc của bạn ở đây. ['Field # getGenericType'] (http://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Field.html#getGenericType--) – Radiodef

Trả lời

23

Bạn có thể triển khai JsonDeserializer tùy chỉnh cho loại chung của mình cũng thực hiện ContextualDeserializer.

Ví dụ, giả sử chúng ta có các loại wrapper đơn giản sau đây có chứa một giá trị chung:

public static class Wrapper<T> { 
    public T value; 
} 

Bây giờ chúng ta muốn deserialize JSON trông như thế này:

{ 
    "name": "Alice", 
    "age": 37 
} 

vào một thể hiện của một lớp trông như thế này:

public static class Person { 
    public Wrapper<String> name; 
    public Wrapper<Integer> age; 
} 

Triển khai ContextualDeserializer cho phép chúng ta tạo ra một deserializer cụ thể cho từng trường trong lớp Person, dựa trên các tham số kiểu chung của trường. Điều này cho phép chúng tôi deserialize tên như một chuỗi, và tuổi như một số nguyên.

Các deserializer hoàn toàn trông như thế này:

public static class WrapperDeserializer extends JsonDeserializer<Wrapper<?>> implements ContextualDeserializer { 
    private JavaType valueType; 

    @Override 
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { 
     JavaType wrapperType = property.getType(); 
     JavaType valueType = wrapperType.containedType(0); 
     WrapperDeserializer deserializer = new WrapperDeserializer(); 
     deserializer.valueType = valueType; 
     return deserializer; 
    } 

    @Override 
    public Wrapper<?> deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException { 
     Wrapper<?> wrapper = new Wrapper<>(); 
     wrapper.value = ctxt.readValue(parser, valueType); 
     return wrapper; 
    } 
} 

Tốt nhất là nhìn vào createContextual đây đầu tiên, vì điều này sẽ được gọi đầu tiên của Jackson. Chúng tôi đọc loại trường từ số BeanProperty (ví dụ: Wrapper<String>) và sau đó trích xuất thông số loại chung chung đầu tiên (ví dụ: String). Sau đó, chúng tôi tạo trình khử giải phóng mới và lưu trữ loại bên trong là valueType.

Sau khi deserialize được gọi trên deserializer mới được tạo ra này, chúng tôi chỉ đơn giản có thể yêu cầu Jackson deserialize giá trị như loại bên trong chứ không phải là toàn bộ loại wrapper, và trả lại một mới Wrapper chứa giá trị deserialized.

Để đăng ký deserializer tùy chỉnh này, sau đó chúng tôi cần phải tạo ra một mô-đun chứa nó, và đăng ký rằng mô-đun:

SimpleModule module = new SimpleModule() 
     .addDeserializer(Wrapper.class, new WrapperDeserializer()); 

ObjectMapper objectMapper = new ObjectMapper(); 
objectMapper.registerModule(module); 

Nếu sau đó chúng tôi cố gắng deserialize ví dụ JSON từ trên cao, chúng ta có thể thấy hoạt động như mong đợi:

Person person = objectMapper.readValue(json, Person.class); 
System.out.println(person.name.value); // prints Alice 
System.out.println(person.age.value); // prints 37 

Có một số chi tiết khác về cách hoạt động của công cụ giải mã theo ngữ cảnh trong Jackson documentation.

+1

Cảm ơn bạn đã đăng bài này. Vô cùng hữu ích để đạt được những gì tôi muốn. Tôi đã thực hiện một vài thay đổi hoạt động với các giá trị gốc trong trường hợp bất kỳ ai khác từ Google đến xung quanh. https://gist.github.com/darylteo/a7be65b539c0d8d3ca0de94d96763f33 –

+1

Cảm ơn bạn, điều đó thực sự đã giúp Loại theo ngữ cảnh có thể là chung chung, trong trường hợp thuộc tính đó sẽ là rỗng, bạn cần tạo JsonDeserializer bằng cách sử dụng ctxt.getContextualType –

1

Nếu mục tiêu chính nó là một kiểu generic thì tài sản sẽ được null, cho bạn rằng sẽ cần phải nhận được valueTtype từ DeserializationContext:

@Override 
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { 
    if (property == null) { // context is generic 
     JMapToListParser parser = new JMapToListParser(); 
     parser.valueType = ctxt.getContextualType().containedType(0); 
     return parser; 
    } else { // property is generic 
     JavaType wrapperType = property.getType(); 
     JavaType valueType = wrapperType.containedType(0); 
     JMapToListParser parser = new JMapToListParser(); 
     parser.valueType = valueType; 
     return parser; 
    } 
} 
+0

Đối với bất cứ ai nhận được một NPE trên câu trả lời được chấp nhận, giải pháp này có lẽ là những gì bạn cần. Thankyou - Tôi không biết anh đã cứu tôi bao nhiêu thời gian. – Martin

Các vấn đề liên quan