Jackson - 反序列化泛型类变量

39

我之前错误地发布了问题。我现在在这里正确地发布问题...

我得到的是一个JSON字符串作为HTTP响应。我知道它的结构。它如下所示:

public class Json<T> {
    public Hits<T> hits;
}
public class Hits<T> {
    public int found;
    public int start;
    public ArrayList<Hit<T>> hit;
}
public class Hit<T> {
    public String id;
    public Class<T> data;
}

"data"字段可以属于任何类。我只能在运行时知道它,我将以参数的形式获取它。这就是我进行反序列化的方式。

public <T> void deSerialize(Class<T> clazz) {
    ObjectMapper mapper = new ObjectMapper();
    mapper.readValue(jsonString,  new TypeReference<Json<T>>() {});
}

但是我遇到了一个错误 -

无法访问java.lang.Class()的私有构造函数,因此无法设置访问权限。无法将java.lang.Class构造函数设置为可访问的。


为什么要复制问题(参见https://dev59.com/I2gu5IYBdhLWcg3wASWO#11681540)? - StaxMan
请参见以下链接:https://dev59.com/zWQm5IYBdhLWcg3wtAw_ - lisak
如果您正在使用Scala,可以像这样做: https://dev59.com/I2gu5IYBdhLWcg3wASWO#49998502 - nathan g
6个回答

39

如果泛型类型只有在运行时才能确定,那么你需要显式构建JavaType

// do NOT create new ObjectMapper per each request!
final ObjectMapper mapper = new ObjectMapper();

public Json<T> void deSerialize(Class<T> clazz, InputStream json) {
    return mapper.readValue(json,
      mapper.getTypeFactory().constructParametricType(Json.class, clazz));
}

从我的经验来看,这个不起作用,但可能是一个bug: https://github.com/FasterXML/jackson-databind/issues/254 - lisak
引用的 bug 还有其他问题 - 像上面展示的那样解决通用类型可能可行,但这并没有解决其他可能遇到的问题。 - StaxMan
是的,非常抱歉,这个问题已经在这里得到解决了:https://dev59.com/zWQm5IYBdhLWcg3wtAw_ - lisak
如果我有几个泛型类型怎么办,比如class MyClass { T1 someValue; T2 someValue2; } - maks1m
@maks1m 那么您需要根据参数数量编写不同的方法。 - StaxMan
@StaxMan 你的意思是像这样构造参数类型(Json.class, clazz1, clazz2)吗? - maks1m

10

示例通用反序列化接口:

public interface Deserializable<T> {
    static final ObjectMapper mapper = new ObjectMapper();

    @SuppressWarnings("unchecked")
    default T deserialize(String rawJson) throws IOException {
        return mapper.readValue(rawJson, (Class<T>) this.getClass());
    }
}

我喜欢那个答案,至少对于我的情况是这样。我有很多课程,它们都应该有一个从JSON读取的方法。因此,我编写了一个带有类型R的基类,在读取类中,我遇到了同样的问题:readValue(file, R.class)??? 当然,这行不通,因为类型在编译期间被擦除了,那么该怎么办呢?this.getClass()是显而易见的答案。 - Werner Daehn

4

对我来说,在类似的情况下,这样的东西已经起作用了。

public <T> T jsonToObject(String jsonContainsObject, Class<T> objectType) {
       ObjectMapper objectMapper = new ObjectMapper();
       try {
         return objectMapper.readValue(jsonContainsObject, objectType);
       } catch (JsonProcessingException e) {
         //Your exception handling
       }
    return null; 
}

2
你正在将Class对象序列化和反序列化为JSON?也许将其保留为String类型并创建一个额外的getter方法,用于执行Class.forName操作。例如:
public class Hit {
    public String id;
    public String data;
    public Class<?> getDataClass() throws Exception {
       return Class.forName(data);
    }
}

1
你得到的错误信息是:“我想实例化一个新的Class对象,但是我无法执行new Class(),因为构造函数是私有的”。而且这没有意义。请提供一个JSON字符串示例。这样会更容易建议解决方案。我相信将类型为Class的属性序列化/反序列化是错误的路径。 - Piotr Gwiazda

1
需要反序列化的JSON字符串必须包含有关参数T的类型信息。您需要在可以作为参数T传递给类Json的每个类上放置Jackson注释,以便Jackson可以从/写入JSON字符串中读取有关参数类型T的类型信息。假设T可以是任何扩展抽象类Result的类。
public class Json<T extends Result> {
    public Hits<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 {

}

一旦可以作为参数传递的每个类(或它们的公共超类型)都被注释,Jackson 将在 JSON 中包含有关参数 T 的信息。这样的 JSON 可以在不知道编译时参数 T 的情况下进行反序列化。
这个 Jackson 文档链接 谈到了多态反序列化,但也适用于本问题。

-1
反序列化泛型类变量
如何告诉Jackson?Gson会更好吗?
Gson用户指南包括一个章节,正是我理解你试图实现的内容,尽管该文档示例可能仍然不完整。
在一篇博客文章中,我详细介绍了使用Gson 1.7.1的解决方案。以下是相关的代码示例。
同样(但更复杂)的解决方案也在同一篇博客文章中演示和描述了Jackson(1.8.2)。 (不同的方法和示例使用数百行代码,因此我省略了在此重新发布它们。)
public class GsonInstanceCreatorForParameterizedTypeDemo  
{  
 public static void main(String[] args)  
 {  
  Id<String> id1 = new Id<String>(String.class, 42);  
  
  Gson gson = new GsonBuilder().registerTypeAdapter(Id.class,  
    new IdInstanceCreator()).create();  
  String json1 = gson.toJson(id1);  
  System.out.println(json1);  
  // actual output: {"classOfId":{},"value":42}  
  // This contradicts what the Gson docs say happens.  
  // With custom serialization, as described in a  
  // previous Gson user guide section,   
  // intended output may be   
  // {"value":42}  
  
  // input: {"value":42}  
  String json2 = "{\"value\":42}";  
  
  Type idOfStringType=new TypeToken<Id<String>>(){}.getType();  
  Id<String> id1Copy = gson.fromJson(json2, idOfStringType);  
  System.out.println(id1Copy);  
  // output: classOfId=class java.lang.String, value=42  
  
  Type idOfGsonType = new TypeToken<Id<Gson>>() {}.getType();  
  Id<Gson> idOfGson = gson.fromJson(json2, idOfGsonType);  
  System.out.println(idOfGson);  
  // output: classOfId=class com.google.gson.Gson, value=42  
 }  
}  
  
class Id<T>  
{  
 private final Class<T> classOfId;  
 private final long value;  
  
 public Id(Class<T> classOfId, long value)  
 {  
  this.classOfId = classOfId;  
  this.value = value;  
 }  
  
 @Override  
 public String toString()  
 {  
  return "classOfId=" + classOfId + ", value=" + value;  
 }  
}  
  
class IdInstanceCreator implements InstanceCreator<Id<?>>  
{  
 @SuppressWarnings({ "unchecked", "rawtypes" })  
 public Id<?> createInstance(Type type)  
 {  
  Type[] typeParameters =   
    ((ParameterizedType) type).getActualTypeArguments();  
  Type idType = typeParameters[0];  
  return new Id((Class<?>) idType, 0L);  
 }  
} 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接