Jackson - Deserialize โดยใช้คลาสทั่วไป


147

ฉันมีสตริง json ซึ่งฉันควร deSerialize ในชั้นเรียนต่อไปนี้

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

ฉันต้องทำอย่างไร? นี่เป็นวิธีปกติ

mapper.readValue(jsonString, Data.class);

แต่ฉันจะพูดถึงสิ่งที่ T หมายถึงได้อย่างไร



คำตอบ:


239

คุณต้องสร้างTypeReferenceวัตถุสำหรับประเภทสามัญแต่ละประเภทที่คุณใช้และใช้สำหรับการดีซีเรียลไลเซชัน ตัวอย่างเช่น -

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

ฉันต้องใช้มันเป็น TypeReference <Data <T>> () {} ... แต่ฉันได้รับข้อผิดพลาดต่อไปนี้ - ไม่สามารถเข้าถึง java.lang.class.Class ส่วนตัว () จาก java.lang.class ได้ ไม่สามารถตั้งค่าการเข้าถึง ไม่สามารถทำให้ตัวสร้าง java.lang.Class สามารถเข้าถึงได้
gnjago

ไม่ไม่Data<T>นั่นไม่ใช่ประเภท คุณต้องระบุคลาสจริง Data<Object>มิฉะนั้นจะเป็นเช่นเดียวกับ
StaxMan

18
ถ้าฉันไม่รู้คลาสจะใช้งานได้จนถึงรันไทม์? ฉันจะรับคลาสเป็นพารามิเตอร์ระหว่างรันไทม์ ชอบสาธารณะ <T> โมฆะ deSerialize (คลาส <T> clazz {mapper ObjectMapper = new ObjectMapper (); mapper.readValue (jsonString, TypeReference ใหม่ <Json <T>> () {});}
gnjago

1
ฉันได้ถามคำถามเต็มอย่างถูกต้องที่นี่stackoverflow.com/questions/11659844/ …
gnjago

ชื่อเต็มของแพคเกจTypeReferenceคืออะไร มันคือcom.fasterxml.jackson.core.typeอะไร
Lei Yang

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
ถ้าฉันไม่รู้คลาสจะใช้งานได้จนถึงรันไทม์? ฉันจะรับคลาสเป็นพารามิเตอร์ระหว่างรันไทม์ ชอบสาธารณะ <T> โมฆะ deSerialize (คลาส <T> clazz {mapper ObjectMapper = new ObjectMapper (); mapper.readValue (jsonString, TypeReference ใหม่ <Json <T>> () {});}
gnjago

1
ฉันได้ถามคำถามเต็มรูปแบบที่นี่stackoverflow.com/questions/11659844/…
gnjago

2
จากนั้นเพียงแค่ผ่านชั้นเรียนตามที่เป็นอยู่ไม่จำเป็นต้องTypeReference: return mapper.readValue(json, clazz);ปัญหาที่นี่คืออะไร?
StaxMan

2
ปัญหาคือ "ข้อมูล" เป็นคลาสทั่วไป ฉันต้องระบุประเภท T ที่รันไทม์ พารามิเตอร์พารามิเตอร์คือสิ่งที่ T us ตอนรันไทม์ ดังนั้นจะเรียก readValue ได้อย่างไร? เรียกมันด้วย TypeReference ใหม่> Json <T>> ไม่ทำงานคำถามเต็มอยู่ที่นี่stackoverflow.com/questions/11659844/…
gnjago

2
ตกลง. ถ้างั้นคุณต้องใช้TypeFactory.. ฉันจะแก้ไขคำตอบของฉัน
StaxMan

30

สิ่งแรกที่คุณทำคือทำให้เป็นอันดับจากนั้นคุณสามารถทำการดีซีเรียลไลซ์ได้
ดังนั้นเมื่อคุณทำให้เป็นอันดับคุณควรใช้@JsonTypeInfoเพื่อให้แจ็คสันเขียนข้อมูลระดับลงในข้อมูล json ของคุณ สิ่งที่คุณสามารถทำได้คือ:

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

จากนั้นเมื่อคุณหยุดการปล่อยออกมาคุณจะพบว่าแจ็คสันได้ทำการแยกข้อมูลของคุณลงในคลาสที่ตัวแปรของคุณนิยมใช้งานจริงนั้นเป็นรันไทม์


ไม่ทำงานรับข้อผิดพลาดด้านล่าง com.fasterxml.jackson.databind.exc.InvalidTypeIdException: รหัสประเภทที่หายไปเมื่อพยายามแก้ไขประเภทย่อยของ [ประเภทที่เรียบง่ายคลาส java.lang.Object]: ไม่มีประเภท id คุณสมบัติ '@class' (สำหรับ POJO 'data' คุณสมบัติ)
gaurav9620

15

สำหรับคลาสข้อมูล <>

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 จากไฟล์ คุณสามารถกำหนดให้ String อ่านค่าได้เช่นกัน

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

นี่คือบางสิ่งบางอย่างเป็นรูปธรรม คุณต้องใช้ wrapper ต่อประเภท reified มิฉะนั้นแจ็กสันไม่ทราบว่าจะสร้างวัตถุอะไร


6

สตริง JSON ที่ต้อง deserialized Tจะต้องมีข้อมูลเกี่ยวกับชนิดพารามิเตอร์
คุณจะต้องใส่คำอธิบายประกอบแจ็คสันในทุกชั้นที่สามารถส่งเป็นพารามิเตอร์TไปยังคลาสDataเพื่อให้ข้อมูลประเภทเกี่ยวกับประเภทพารามิเตอร์Tสามารถอ่าน / เขียนจากสตริง JSON โดยแจ็คสัน

ขอให้เราคิดว่าอาจจะเป็นระดับใดที่ขยายระดับนามธรรมTResult

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 {

}

เมื่อแต่ละคลาส (หรือ supertype ทั่วไปของพวกเขา) ที่สามารถส่งผ่านเป็นT หมายเหตุประกอบพารามิเตอร์Jackson จะรวมข้อมูลเกี่ยวกับพารามิเตอร์Tใน JSON จากนั้น JSON นั้นสามารถทำการดีซีเรียลไลซ์ได้โดยไม่ต้องรู้พารามิเตอร์Tในเวลาคอมไพล์ ลิงก์เอกสารคู่มือของแจ็คสัน
นี้พูดถึง Polerorphic Deserialization แต่มีประโยชน์ในการอ้างถึงคำถามนี้เช่นกัน


และฉันจะจัดการสิ่งนี้ได้อย่างไรหากฉันต้องการมีรายชื่อ เช่นสมมติว่ารายการ <ImageResult>
Luca Archidiacono

5

จาก Jackson 2.5 เป็นวิธีที่ยอดเยี่ยมในการแก้ปัญหาโดยใช้ TypeFactory.constructParametricType (Class parametrized, Class ... parameterClasses)วิธีการที่ช่วยให้กำหนด Jackson แบบ straigthly JavaTypeโดยการระบุ classized พารามิเตอร์และประเภทของพารามิเตอร์

หากว่าคุณต้องการยกเลิกการซีเรียล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);

เยี่ยมมากขอบคุณมันใช้งานได้สำหรับฉัน ด้วยการพิมพ์ฉันได้รับการยกเว้นคลาสคาสต์จากแผนที่ไปยังวัตถุเฉพาะ แต่นี่ทำงานได้จริง
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

หากคุณใช้สกาล่าและรู้ประเภททั่วไปในเวลารวบรวม แต่ไม่ต้องการส่ง TypeReference ด้วยตนเองทุกที่ในทนายความของคุณคุณสามารถใช้รหัสต่อไปนี้ (ด้วยแจ็คสัน 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

ในการยกเลิกการวางจำหน่าย JSON-string ทั่วไปไปยัง Java-object ด้วย Jackson คุณต้อง:

  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

ตัวอย่างของการตัดสินใจที่ไม่ดี แต่เรียบง่าย (ไม่เพียง แต่สำหรับแจ็คสันเท่านั้นและสำหรับ Spring RestTemplate ฯลฯ ):

Set<MyClass> res = new HashSet<>();
objectMapper.readValue(json, res.getClass());
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.