Java 如何使用Gson处理具有相同属性名称的不同数据类型?
我目前正在使用Gson用Java编写RSS提要解析器。我将RSS的XML转换为JSON,然后使用Gson将JSON反序列化为JavaPOJO(有点迂回,但这是有原因的)。对于下面列出的提要1(BBC)反序列化,一切都进行得很好,但是对于下面列出的提要2(NPR),我开始抛出异常 我想我已经发现了问题,但我不确定如何解决它:Java 如何使用Gson处理具有相同属性名称的不同数据类型?,java,json,gson,deserialization,json-deserialization,Java,Json,Gson,Deserialization,Json Deserialization,我目前正在使用Gson用Java编写RSS提要解析器。我将RSS的XML转换为JSON,然后使用Gson将JSON反序列化为JavaPOJO(有点迂回,但这是有原因的)。对于下面列出的提要1(BBC)反序列化,一切都进行得很好,但是对于下面列出的提要2(NPR),我开始抛出异常 我想我已经发现了问题,但我不确定如何解决它: 这两个RSS源会出现问题(例如): 对于这些不同的RSS源,一个名为“guid”的字段被返回为a)一个包含两个字段的对象(如在BBC RSS源中)或b)一个字符串(如在
这两个RSS源会出现问题(例如): 对于这些不同的RSS源,一个名为“guid”的字段被返回为a)一个包含两个字段的对象(如在BBC RSS源中)或b)一个字符串(如在NPR RSS源中) 以下是相关JSON的一些释义版本: BBC RSS源
// is returning 'guid' as an object
"item" :
[
{
// omitted other fields for brevity
"guid" : {
"isPermalink" : false,
"content" : "http:\/\/www.bbc.co.uk\/news\/uk-england-33745057"
},
},
{
// ...
}
]
// is returning 'guid' as a string
"item" :
[
{
// omitted other fields for brevity
"guid" : "http:\/\/www.npr.org\/sections\/thetwo-way\/2015\/07\/31\/428188125\/chimps-in-habeas-corpus-case-will-no-longer-be-used-for-research?utm_medium=RSS&utm_campaign=news"
},
{
// ...
}
]
NPR RSS源
// is returning 'guid' as an object
"item" :
[
{
// omitted other fields for brevity
"guid" : {
"isPermalink" : false,
"content" : "http:\/\/www.bbc.co.uk\/news\/uk-england-33745057"
},
},
{
// ...
}
]
// is returning 'guid' as a string
"item" :
[
{
// omitted other fields for brevity
"guid" : "http:\/\/www.npr.org\/sections\/thetwo-way\/2015\/07\/31\/428188125\/chimps-in-habeas-corpus-case-will-no-longer-be-used-for-research?utm_medium=RSS&utm_campaign=news"
},
{
// ...
}
]
我用Java对此进行建模,如下所示:
// RSSFeedItem.java
private Guid guid;
// GUID.java
private boolean isPermalink;
private String content;
所以在这种情况下,调用
Gson gson = new Gson();
RssFeed rssFeed = gson.fromJson(jsonData, RssFeed.class);
对于bbcss提要,但在解析nprss提要时引发异常
导致我得出这是一个类型错误的结论的具体错误如下(在尝试反序列化NPR RSS提要时):
因此,无论如何,我要说的是:我如何处理Gson的这种情况,其中一个字段被作为可能不同的数据类型返回?我猜可能有某种技巧或注释可以达到这种效果,但我不确定,在查看Gson文档后,我找不到现成的答案。这是我的示例代码,希望对您有所帮助
public <T> List<T> readData(InputStream inputStream, Class<T> clazz) throws Exception {
ArrayList<Object> arrayList = new ArrayList<>();
GsonBuilder gsonBuilder = new GsonBuilder();
Gson gson = gsonBuilder.create();
JsonReader jsonReader = new JsonReader(new InputStreamReader(inputStream, "UTF_8"));
jsonReader.setLenient(true);
JsonToken jsonToken = jsonReader.peek();
switch (jsonToken) {
case BEGIN_ARRAY:
jsonReader.beginArray();
while (jsonReader.hasNext()) {
arrayList.add(gson.fromJson(jsonReader, clazz));
}
jsonReader.endArray();
break;
case BEGIN_OBJECT:
T data = clazz.cast(gson.fromJson(jsonReader, clazz));
arrayList.add(data);
break;
case NUMBER:
Integer number = Integer.parseInt(jsonReader.nextString());
arrayList.add(number);
break;
default:
jsonReader.close();
inputStream.close();
return Collections.emptyList();
}
jsonReader.close();
inputStream.close();
return (List<T>) arrayList;
}
更新:您还可以参考Streams
class(gson-2.3.1.jar)中的parse(JsonReader-reader)
像这样
JsonElement jsonElement = Streams.parse(jsonReader);
我的答案是使用类层次结构
abstract class Guid {
private boolean isPermalink;
private String content;
// getters and setters omitted
}
class GuidObject extends Guid {}
class GuidString extends Guid {}
class RssFeedItem {
// super class to receive instances of sub classes
private Guid guid;
}
并为Guid
注册反序列化程序:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Guid.class, new JsonDeserializer<Guid>() {
@Override
public Guid deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
// Dispatch based on the type of json
if (json.isJsonObject()) {
// If it's an object, it's essential we deserialize
// into a sub class, otherwise we'll have an infinite loop
return context.deserialize(json, GuidObject.class);
} else if (json.isJsonPrimitive()) {
// Primitive is easy, just set the most
// meaningful field. We can also use GuidObject here
// But better to keep it clear.
Guid guid = new GuidString();
guid.setContent(json.getAsString());
return guid;
}
// Cannot parse, throw exception
throw new JsonParseException("Expected Json Object or Primitive, was " + json + ".");
}
});
GsonBuilder=newgsonbuilder();
registerTypeAdapter(Guid.class,新的JsonDeserializer()){
@凌驾
公共Guid反序列化(JsonElement json,类型typeOfT,JsonDeserializationContext)引发JsonParseException{
//基于json类型的分派
if(json.isJsonObject()){
//如果它是一个对象,我们必须反序列化它
//进入一个子类,否则我们将有一个无限循环
反序列化(json,GuidObject.class);
}else if(json.isJsonPrimitive()){
//原语很简单,只需设置最简单的
//有意义的字段。我们也可以在这里使用GuidObject
//但最好保持清楚。
Guid=new GuidString();
setContent(json.getAsString());
返回guid;
}
//无法分析,引发异常
抛出新的JsonParseException(“预期的Json对象或原语为“+Json+”);
}
});
通过这种方式,您可以潜在地处理更复杂的JSON对象,并根据您喜欢的任何条件进行分派。将其作为对象类,而不是根据调用进行其他类类型和类型转换
// RSSFeedItem.java
private Object guid;
您可以使用
类型适配器
。其思想是只在不同的情况(字符串或对象)之间进行选择,并委托实际的反序列化
注册工厂:
public class RSSFeedItem {
@JsonAdapter(GuidAdapterFactory.class)
private Guid guid;
}
这将创建适配器:
public class GuidAdapterFactory implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
return (TypeAdapter<T>) new GuidAdapter(gson);
}
}
public类GuidAdapterFactory实现TypeAdapterFactory{
@凌驾
公共类型适配器创建(Gson Gson,TypeToken类型){
返回(TypeAdapter)新guidapter(gson);
}
}
它决定如何处理guid:
public class GuidAdapter extends TypeAdapter<Guid> {
private final Gson gson;
public GuidAdapter(Gson gson) {
this.gson = gson;
}
@Override
public void write(JsonWriter jsonWriter, Guid guid) throws IOException {
throw new RuntimeException("Not implemented");
}
@Override
public Guid read(JsonReader jsonReader) throws IOException {
switch (jsonReader.peek()) {
case STRING:
// only a String, create the object
return new Guid(jsonReader.nextString(), true);
case BEGIN_OBJECT:
// full object, forward to Gson
return gson.fromJson(jsonReader, Guid.class);
default:
throw new RuntimeException("Expected object or string, not " + jsonReader.peek());
}
}
}
public类GuidAdapter扩展了TypeAdapter{
私人最终Gson Gson;
公共引导器(Gson Gson){
this.gson=gson;
}
@凌驾
公共无效写入(JsonWriter JsonWriter,Guid)引发IOException{
抛出新的RuntimeException(“未实现”);
}
@凌驾
公共Guid读取(JsonReader JsonReader)引发IOException{
开关(jsonReader.peek()){
大小写字符串:
//仅一个字符串,创建对象
返回新的Guid(jsonReader.nextString(),true);
案例开始对象:
//完整对象,转发到Gson
返回gson.fromJson(jsonReader,Guid.class);
违约:
抛出新的RuntimeException(“预期的对象或字符串,而不是“+jsonReader.peek()”);
}
}
}
几句话:
- 它只会工作,因为适配器是用属性注册的。在委派实际的反序列化时,全局注册它会触发递归调用
- 之所以需要工厂,是因为我们需要对
对象的引用,否则我们可以直接注册适配器类Gson
- 我相信
比类型适配器
更有效,因为它不需要构建反序列化器
树,尽管在这种情况下差异可能可以忽略不计JsonElement