如何使用定制的Gson解串器分析嵌套的JSON数组对象? [英] How do I parse a nested JSON array of object with a custom Gson deserializer?
问题描述
{
data:[
{
id:43,
type:position,
attributes:{
address-id:1,
雇主ID:11
}
}
],
包括:[
{
id:1,
type:address,
attributes:{
line-1:21 london london,
line-2:,
line-3:,
locality:,
region:London,
post-code:,
country:UK,
latitude:,
longitude:
}
},
{
id:11,
type:employer,
attributes:{
title:Mr,
名称:S,
last-name: T
}
}
]
}
我的Retrofit调用是:
@GET(/ api / positions)
Single< PositionResponse>为getPosition();
和
public class PositionResponse {
@SerializedName(data)
@Expose
private List< DataResponse>数据;
@SerializedName(包含)
@Expose
私人列表< IncludedModel>包括在内;
公共列表< DataResponse> getData(){
返回数据;
}
public void setData(List< DataResponse> data){
this.data = data;
}
公共列表< IncludedModel> getIncluded(){
包括返回;
}
public void setIncluded(包含List< IncludedModel>){
this.included = included;
}
}
}
它有更多的数据。我如何创建一个自定义 TypeAdapter
或 JsonDeserializer
来解析 List< IncludedModel> code>?出于某种原因,我可以为
Object $ c>创建一个自定义
JsonDeserializer
或 TypeAdapter
$ c>,但是当涉及到一个 List
时,我似乎无法让它工作。
$ b 我的
TypeAdapter
如下: public class IncludedTypeAdapter extends TypeAdapter< ArrayList< IncludedModel>> {
$ b @Override
public void write(JsonWriter out,ArrayList< IncludedModel> value)throws IOException {
}
@Override
public ArrayList< IncludedModel> read(JsonReader in)抛出IOException {
ArrayList< IncludedModel> list = new ArrayList<>();
IncludedModel模型=新的IncludedModel();
Gson gson = new Gson();
in.beginArray();
String id = null;
//in.beginObject();
while(in.hasNext()){
JsonToken nextToken = in.peek();
if(JsonToken.BEGIN_OBJECT.equals(nextToken)){
in.beginObject();
} else if(JsonToken.NAME.equals(nextToken)){
if(JsonToken.NAME.name()。equals(id)){
id = in.nextString();
model.setId(id);
} else if(JsonToken.NAME.name()。equals(type)){
String type = in.nextString();
model.setMytype(type);
switch(type){
case BaseModelType.Employer:
EmployerResponse employer = gson.fromJson(in,EmployerResponse.class);
model.setEmployer(employer);
休息;
}
}
}
}
list.add(model);
返回列表;
}
我注册我的Gson:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(IncludeModel.class,new IncludedTypeAdapter());
//gsonBuilder.registerTypeAdapter(new IncludedTypeAdapter());
gsonBuilder.serializeNulls();
Gson gson = gsonBuilder.create();
返回gson;
我通过 GsonConverterFactory
进行改造注册。
我得到:
期望的BEGIN_ARRAY,但在第1行的BEGIN_OBJECT列6292 path $ .included [0]
我怀疑是因为我的Retrofit响应是< PositionResponse> 这是一个
JsonObject
。
总结我的问题:如何反序列化使用我自己的自定义类型适配器列出< IncludeModel>
对象,记住我的Retrofit服务的响应类型是 PositionResponse
?非常感谢您的患者和答复。
使用JSON树模型很容易, JsonDeserializer
。纯粹的类型适配器有点矫枉过正(以及 RuntimeTypeAdapterFactory 是我认为的,因为它仍然是面向树的),而对于你的JSON文档最简单的情况,你可以使用类似的东西(你可以找到类似的方法在我昨天的回答有更多的解释,但你的情况略有不同)。我假设你想要有这样的映射:
抽象类元素{
final String id = null;
private Element(){
}
static final class Address
extends Element {
@SerializedName(line -1)final String line1 = null;
@SerializedName(line-2)final String line2 = null;
@SerializedName(line-3)final String line3 = null;
@SerializedName(locality)final String locality = null;
@SerializedName(region)final String region = null;
@SerializedName(post-code)final String postCode = null;
@SerializedName(country)final String country = null;
@SerializedName(latitude)final String latitude = null;
@SerializedName(longitude)final String longitude = null;
$ b $ private address(){
}
@Override
public String toString(){
return country ++ region;
}
static final class雇主
extends Element {
@SerializedName(title)final String title = null;
@SerializedName(first-name)final String firstName = null;
@SerializedName(last-name)final String lastName = null;
$ b私人雇主(){
}
@Override
public String toString(){
return title +''+ firstName + ''+ lastName;
$ b static final class Position
extends Element {
@SerializedName(address-id)最终字符串addressId = null;
@SerializedName(employer-id)final String employerId = null;
private Position(){
}
@Override
public String toString(){
return'('+ addressId +' ;'+ employerId +')';
}
}
}
您只需要:
Gson
再一次丢失了原始配置;你重做所有Gson可以做的不足:列表和POJO通过反射; JsonToken
如果通过 switch
(通过 enum
s是单例,并且使用引用比较它们是完全合法的等于 ==
)等。)
通过类似这样的事情:
final class ElementJsonDeserializer
实现JsonDeserializer< Element> {
private static final JsonDeserializer< Element> elementJsonDeserializer = new ElementJsonDeserializer();
private ElementJsonDeserializer(){
}
static JsonDeserializer< Element> getElementJsonDeserializer(){
return elementJsonDeserializer;
$ b @Override
public元素反序列化(final JsonElement jsonElement,final Type类型,final JsonDeserializationContext上下文)
抛出JsonParseException {
final JsonObject jsonObject = jsonElement.getAsJsonObject();
final String typeCode = jsonObject.getAsJsonPrimitive(type)。getAsString();
final Class <?扩展Element> clazz中;
switch(typeCode){
caseaddress:
clazz = Element.Address.class;
休息;
caseemployer:
clazz = Element.Employer.class;
休息;
caseposition:
clazz = Element.Position.class;
休息;
默认值:
抛出新的JsonParseException(Unrecognized type:+ typeCode);
}
reattach(jsonObject,attributes);
return context.deserialize(jsonElement,clazz);
private static void reattach(final JsonObject parent,final String property){
final JsonObject child = parent.getAsJsonObject(property);
parent.remove(property); //在确定它是JSON对象后移除
copyTo(parent,child);
$ b $ private static void copyTo(final JsonObject to,final JsonObject from){
for(final Entry< String,JsonElement> e:from.entrySet()){
to.add(e.getKey(),e.getValue());
}
}
}
当然,您可以重构上述内容以提取策略来实施策略设计模式以重用它。把它放在一起:
$ b
final class Response {
final List< ;组件> data = null;
final List< Element>包括= null;
$ b $ / code> (上面的一个看起来像一个 Map< String,List< Element>>
但您决定)。 $ b
private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(Element.class,getElementJsonDeserializer())
.create();
public static void main(final String ... args)
throws IOException {
try(final JsonReader jsonReader = getPackageResourceJsonReader(Q43811168.class,data.json)) {
final Response response = gson.fromJson(jsonReader,Response.class);
dump(response.data);
dump(response.included);
private final void void dump(final Iterable< Element> elements){
for(final Element e:elements){
System。的out.print(e.getClass()getSimpleName());
System.out.print(#);
System.out.print(e.id);
System.out.print(:);
System.out.println(e);
$ / code $ / pre
$ b $输出:
位置#43:(1; 11)
地址#1:英国伦敦
雇主#11:ST先生
Good day all. I am having a bit of trouble understanding this. I have a JSON that looks like this:
{
"data": [
{
"id": "43",
"type": "position",
"attributes": {
"address-id": "1",
"employer-id": "11"
}
}
],
"included": [
{
"id": "1",
"type": "address",
"attributes": {
"line-1": "21 london london",
"line-2": "",
"line-3": "",
"locality": "",
"region": "London",
"post-code": "",
"country": "UK",
"latitude": "",
"longitude": ""
}
},
{
"id": "11",
"type": "employer",
"attributes": {
"title": "Mr",
"first-name": "S",
"last-name": "T"
}
}
]
}
And my Retrofit call is:
@GET("/api/positions")
Single<PositionResponse> getPosition();
And my PositionResponse
class:
public class PositionResponse {
@SerializedName("data")
@Expose
private List<DataResponse> data;
@SerializedName("included")
@Expose
private List<IncludedModel> included;
public List<DataResponse> getData() {
return data;
}
public void setData(List<DataResponse> data) {
this.data = data;
}
public List<IncludedModel> getIncluded() {
return included;
}
public void setIncluded(List<IncludedModel> included) {
this.included = included;
}
}
}
Now imagine it has a lot more data. How can I create a custom TypeAdapter
or JsonDeserializer
for parsing the List<IncludedModel>
? For some reason, I can create a custom JsonDeserializer
or TypeAdapter
for Object
, but when it comes to a List
, I don't seem to be able to get that to work.
My TypeAdapter
is as follows:
public class IncludedTypeAdapter extends TypeAdapter<ArrayList<IncludedModel>> {
@Override
public void write(JsonWriter out, ArrayList<IncludedModel> value) throws IOException {
}
@Override
public ArrayList<IncludedModel> read(JsonReader in) throws IOException {
ArrayList<IncludedModel> list = new ArrayList<>();
IncludedModel model = new IncludedModel();
Gson gson = new Gson();
in.beginArray();
String id = null;
//in.beginObject();
while(in.hasNext()){
JsonToken nextToken = in.peek();
if(JsonToken.BEGIN_OBJECT.equals(nextToken)){
in.beginObject();
} else if(JsonToken.NAME.equals(nextToken)){
if(JsonToken.NAME.name().equals("id")){
id = in.nextString();
model.setId(id);
} else if(JsonToken.NAME.name().equals("type")){
String type = in.nextString();
model.setMytype(type);
switch (type) {
case BaseModelType.Employer:
EmployerResponse employer = gson.fromJson(in, EmployerResponse.class);
model.setEmployer(employer);
break;
}
}
}
}
list.add(model);
return list;
}
And i register to my Gson:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(IncludeModel.class, new IncludedTypeAdapter());
//gsonBuilder.registerTypeAdapter(new IncludedTypeAdapter());
gsonBuilder.serializeNulls();
Gson gson = gsonBuilder.create();
return gson;
Which I register on retrofit through GsonConverterFactory
.
I am getting:
Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 6292 path $.included[0]
which I suspect is because my Retrofit response is <PositionResponse>
which is a JsonObject
.
To summarize my question: how do I deserialize the List<IncludeModel>
object with my own custom type adapter bearing in mind the response type from my Retrofit service is PositionResponse
? Many thanks for your patients and answers.
解决方案 It's easy if you're using JSON tree models using JsonDeserializer
. Pure type adapters are somewhat an overkill (as well as RuntimeTypeAdapterFactory is, I think, since it's still tree-oriented), and in the most simple case for your JSON document you could use something like this (you can find a similar approach in my yesterday answer having some more explanations, but you case slightly differs).
I'm assuming you would like to have mappings like these:
abstract class Element {
final String id = null;
private Element() {
}
static final class Address
extends Element {
@SerializedName("line-1") final String line1 = null;
@SerializedName("line-2") final String line2 = null;
@SerializedName("line-3") final String line3 = null;
@SerializedName("locality") final String locality = null;
@SerializedName("region") final String region = null;
@SerializedName("post-code") final String postCode = null;
@SerializedName("country") final String country = null;
@SerializedName("latitude") final String latitude = null;
@SerializedName("longitude") final String longitude = null;
private Address() {
}
@Override
public String toString() {
return country + " " + region;
}
}
static final class Employer
extends Element {
@SerializedName("title") final String title = null;
@SerializedName("first-name") final String firstName = null;
@SerializedName("last-name") final String lastName = null;
private Employer() {
}
@Override
public String toString() {
return title + ' ' + firstName + ' ' + lastName;
}
}
static final class Position
extends Element {
@SerializedName("address-id") final String addressId = null;
@SerializedName("employer-id") final String employerId = null;
private Position() {
}
@Override
public String toString() {
return '(' + addressId + ';' + employerId + ')';
}
}
}
All you have to do is just:
- determine the expected object type;
- "align" the JSON tree (if it fits your needs sure);
- just delegate the deserialization work to Gson via the deserialization context (your example does not do it well: you're instantiating
Gson
once again losing the original configuration; you redo all Gson can do out of box: lists and POJO by reflection; JsonToken
are much better if checked via switch
(by the way enum
s are singletons and it's perfectly legal to compare them using reference equality ==
), etc).
So, it can be implemented by something like this:
final class ElementJsonDeserializer
implements JsonDeserializer<Element> {
private static final JsonDeserializer<Element> elementJsonDeserializer = new ElementJsonDeserializer();
private ElementJsonDeserializer() {
}
static JsonDeserializer<Element> getElementJsonDeserializer() {
return elementJsonDeserializer;
}
@Override
public Element deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
final JsonObject jsonObject = jsonElement.getAsJsonObject();
final String typeCode = jsonObject.getAsJsonPrimitive("type").getAsString();
final Class<? extends Element> clazz;
switch ( typeCode ) {
case "address":
clazz = Element.Address.class;
break;
case "employer":
clazz = Element.Employer.class;
break;
case "position":
clazz = Element.Position.class;
break;
default:
throw new JsonParseException("Unrecognized type: " + typeCode);
}
reattach(jsonObject, "attributes");
return context.deserialize(jsonElement, clazz);
}
private static void reattach(final JsonObject parent, final String property) {
final JsonObject child = parent.getAsJsonObject(property);
parent.remove(property); // remove after we're sure it's a JSON object
copyTo(parent, child);
}
private static void copyTo(final JsonObject to, final JsonObject from) {
for ( final Entry<String, JsonElement> e : from.entrySet() ) {
to.add(e.getKey(), e.getValue());
}
}
}
Of course, you can refactor the above to extract a strategy to implement the strategy design pattern to reuse it. Put it all together:
final class Response {
final List<Element> data = null;
final List<Element> included = null;
}
(The above one looks like a Map<String, List<Element>>
but you decide).
private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(Element.class, getElementJsonDeserializer())
.create();
public static void main(final String... args)
throws IOException {
try ( final JsonReader jsonReader = getPackageResourceJsonReader(Q43811168.class, "data.json") ) {
final Response response = gson.fromJson(jsonReader, Response.class);
dump(response.data);
dump(response.included);
}
}
private static void dump(final Iterable<Element> elements) {
for ( final Element e : elements ) {
System.out.print(e.getClass().getSimpleName());
System.out.print(" #");
System.out.print(e.id);
System.out.print(": ");
System.out.println(e);
}
}
Output:
Position #43: (1;11)
Address #1: UK London
Employer #11: Mr S T
这篇关于如何使用定制的Gson解串器分析嵌套的JSON数组对象?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!