developer tip

Jackson-일반 클래스를 사용하여 직렬화 해제

optionbox 2020. 7. 26. 12:54
반응형

Jackson-일반 클래스를 사용하여 직렬화 해제


json 문자열이 있는데 다음 클래스로 직렬화 해제해야합니다.

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

어떻게합니까? 이것은 일반적인 방법입니다

mapper.readValue(jsonString, Data.class);

그러나 T가 의미하는 것을 어떻게 언급합니까?


사용하는 TypeReference각 제네릭 형식에 대한 개체 를 생성하고 역 직렬화에 사용해야합니다. 예를 들어

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

그렇게 할 수 없습니다 :과 같이 완전히 해결 된 유형을 지정해야합니다 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);
}

먼저 직렬화를 수행 한 다음 직렬화 해제를 수행 할 수 있습니다.
따라서 직렬화를 수행 할 때 @JsonTypeInfoJackson이 클래스 정보를 json 데이터에 쓰도록 해야합니다 . 당신이 할 수있는 일은 다음과 같습니다

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

그런 다음 deserialize하면 Jackson이 변수 적중이 실제로 런타임에 클래스로 데이터를 deserialize 한 것을 알 수 있습니다.


클래스 Data <>

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

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

일반 유형의 유형을 알고있는 다른 클래스로 랩핑 할 수 있습니다.

예 :

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

여기에 구체적인 유형이 있습니다. 당신은 reified type마다 wrapper가 필요합니다. 그렇지 않으면 Jackson은 어떤 객체를 생성할지 모릅니다.


역 직렬화해야하는 JSON 문자열에는 parameter에 대한 유형 정보가 포함되어야합니다 T.
Jackson이 매개 변수 유형에 대한 유형 정보 를 JSON 문자열에서 읽거나 쓸 수 있도록 매개 변수 T로 클래스에 전달할 수있는 모든 클래스에 Jackson 주석을 넣어야 합니다.DataT

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 {

}

Once each of the class (or their common supertype) that can be passed as parameter T is annotated, Jackson will include information about parameter T in the JSON. Such JSON can then be deserialized without knowing the parameter T at compile time.
This Jackson documentation link talks about Polymorphic Deserialization but is useful to refer to for this question as well.


From Jackson 2.5, an elegant way to solve that is using the TypeFactory.constructParametricType(Class parametrized, Class... parameterClasses) method that allows to define straigthly a Jackson JavaType by specifying the parameterized class and its parameterized types.

Supposing you want to deserialize to Data<String>, you can do :

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

Note that if the class declared multiple parameterized types, it would not be really harder :

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

We could do :

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

if you're using scala and know the generic type at compile time, but don't want to manually pass TypeReference everywhere in all your api l ayers, you can use the following code (with 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)
  }

which can be used like this:

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

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;
    }
    ...
 }

참고URL : https://stackoverflow.com/questions/11664894/jackson-deserialize-using-generic-class

반응형