Gson을 이용하여 복잡한 json을 해석하다

19064 단어 Android
Gson을 사용하여 json을 해석하는 것은 흔히 볼 수 있는 일이다. 대부분의 경우 Gson 대상을 만들고 json과 대응하는 자바 클래스에 따라 해석하면 된다.
Gson gson = new Gson();
Person person = gson.form(json,Person.class);

그러나 비교적 복잡한 json, 예를 들어 아래와 같은attributes에 대응하는 jsonObject의 필드는 완전히 다르다. 이럴 때 위의 방법으로 간단하게 해석할 수 없다.
{
    "total": 2,
    "list": [
        {
            "type": "address",
            "attributes": {
                "street": "NanJing Road",
                "city": "ShangHai",
                "country": "China"
            }
        },
        {
            "type": "name",
            "attributes": {
                "first-name": "Su",
                "last-name": "Tu"
            }
        }
    ]
}

당연하지, 우리는 한 걸음 한 걸음 해결할 수 없다고 말하지만, 약간의 어리석은 방법으로는 그래도 괜찮다.예를 들어attributes에 대응하는 jsonObject를 수동으로 해석하고 동급 type에 대응하는value에 따라 이 jsonObject에 대응하는 자바 클래스가 무엇인지 판단할 수 있으며 마지막으로gson.from() 방법으로attributes에 대응하는 자바 대상을 해석할 수 있다.
ListInfoWithType listInfoWithType = new ListInfoWithType();

//   org.json     JSONObject   
JSONObject jsonObject = new JSONObject(TestJson.TEST_JSON_1);
int total = jsonObject.getInt("total");

//   org.json     JSONArray   
JSONArray jsonArray = jsonObject.getJSONArray("list");
Gson gson = new Gson();
List list = new ArrayList<>();

//  
for (int i = 0; i < jsonArray.length(); i++) {
    JSONObject innerJsonObject = jsonArray.getJSONObject(i);
    Class extends Attribute> clazz;
    String type = innerJsonObject.getString("type");
    if (TextUtils.equals(type, "address")) {
        clazz = AddressAttribute.class;
    } else if (TextUtils.equals(type, "name")) {
        clazz = NameAttribute.class;
    } else {
        //         
        continue;
    }
    AttributeWithType attributeWithType = new AttributeWithType();

//  Gson  
    Attribute attribute = gson.fromJson(innerJsonObject.getString("attributes"), clazz);
    attributeWithType.setType(type);
    attributeWithType.setAttributes(attribute);
    list.add(attributeWithType);
}

listInfoWithType.setTotal(total);
listInfoWithType.setList(list);

이렇게 하면 전체 json의 반서열화를 실현할 수 있지만 이런 방식은 비교적 번거롭고 우아하지 않다. 만약에 프로젝트에 이런 상황이 많이 존재한다면 중복된 육체노동을 많이 할 것이다.어떻게 더욱 우아하고 통용적으로 이런 문제를 해결할 것인가는 인터넷에서 답을 찾지 못했기 때문에 어쩔 수 없이 Gson을 깊이 연구할 수밖에 없다.이러한 목적을 가지고 Gson의 문서를 뒤져보니 한 마디가 발견되었다
Gson can work with arbitrary Java objects including pre-existing objects that you do not have source code of.
이 말은 Gson이 임의의 자바 대상을 처리할 수 있다는 것이다.그러면 위에서 말한 그런 반서열화 상황에 대해 말하자면 Gson도 할 수 있을 것이다.Gson의 문서를 연구한 결과 이러한 jsonObject 유형의 다른 상황을 사용자 정의 Json Deserializer 방식으로 해석할 수 있음을 발견했다.
대부분의 경우 Gson은 직접 new에 나오는 방식으로 만들어지지만 GsonBuilder라는 클래스로 Gson을 생성할 수도 있다.
  Gson gson = new GsonBuilder()
   .registerTypeAdapter(Id.class, new IdTypeAdapter())
   .enableComplexMapKeySerialization()
   .serializeNulls()
   .setDateFormat(DateFormat.LONG)
   .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
   .setPrettyPrinting()
   .setVersion(1.0)
   .create();

GsonBuilder는 registerTypeAdapter() 방법을 사용하여 대상 클래스를 등록합니다.대상 클래스를 시리얼화하거나 역시리얼화할 때 등록된 typeAdapter가 호출되므로 Gson의 시리얼화 및 역시리얼화 프로세스를 수동으로 수행할 수 있습니다.
GsonBuilder의registerTypeAdapte() 방법의 두 번째 매개 변수는 Object 유형으로 여러 종류의 typeAdapter를 등록할 수 있음을 의미한다. 현재 지원하는 유형은 JsonSerializer, JsonDeserializer, InstanceCreator, TypeAdapter이다.
  public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter)

한바탕 소란을 피우고 도구류를 썼는데 위에 있는 복잡한 json에 대해 10줄도 안 되는 코드를 사용하면 해결할 수 있고 비교적 우아하고 통용적이다.
MultiTypeJsonParser multiTypeJsonParser = new MultiTypeJsonParser.Builder()
        .registerTypeElementName("type")
        .registerTargetClass(Attribute.class)
        .registerTargetUpperLevelClass(AttributeWithType.class)
        .registerTypeElementValueWithClassType("address", AddressAttribute.class)
        .registerTypeElementValueWithClassType("name", NameAttribute.class)
        .build();

ListInfoWithType listInfoWithType = multiTypeJsonParser.fromJson(TestJson.TEST_JSON_1, ListInfoWithType.class);

본고는 복잡한 유형의 json을 분석하는 데 사용되는 일반적인 도구 클래스를 사용자 정의 Json Deserializer를 통해 간단하게 분석하고자 한다.이후에 비슷한 문제에 부딪히면 이런 처리 방법은 문제를 해결하는 방향을 제공할 수 있다.구체적인 코드와 실례는 항목을 볼 수 있다.만약 당신의 사고방식에 약간의 깨우침이 있다면, 교류와 스타를 환영합니다.
1 Json Deserializer 소개
Json Deserializer는 하나의 인터페이스로 사용할 때 이 인터페이스를 실현하고 Gson Builder에서 구체적인 유형에 대해 등록해야 한다.대응하는 클래스에 반서열화될 때 이 사용자 정의 Json Deserializer () 방법을 사용합니다.다음은 Gson의 해석 과정을 더 잘 이해하기 위해 이 방법의 몇 가지 매개 변수에 대해 설명을 하겠다.
public interface JsonDeserializer<T> {
  public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
      throws JsonParseException;
}

1.1 JsonElement
Json Element은 Gson에서 요소를 나타냅니다.이것은 추상 클래스로 네 개의 하위 클래스가 있는데 그것이 바로 Json Object, Json Array, Json Primitive, Json Null이다.1. JsonObject는name-value형을 포함하는 json 문자열을 나타낸다. 그 중에서name는 문자열이고value는 다른 유형의 JsonElement 요소일 수 있다.json에서 "{}"으로 감싼 전체가 JsonObject입니다.예컨대
// "attributes"  name,     {}       valuevalue    JsonObject
  "attributes": {
                  "first-name": "Su",
                  "last-name": "Tu"
                 }

2. Json Array라는 클래스는 Gson에서 하나의 그룹 유형을 대표하고 하나의 그룹은 Json Element의 집합이다. 이 집합에서 모든 유형이 다를 수 있다.이것은 질서정연한 집합으로 원소의 첨가 순서가 유지되고 있음을 의미한다.위의 예에서list에 대응하는'[]'를 감싼 json은 바로 Json Array이다.
3. JsonPrimitive는 json의 원시 유형으로 볼 수 있는 값으로 자바의 8가지 기본 유형과 그에 대응하는 포장 유형을 포함하고 String 유형도 포함한다.예를 들어 위의'first-name'에 대응하는'Su'는 바로 String 유형의 Json Primitive이다.
4. JsonNull은 이름으로도 알 수 있는데 이것은 null값을 대표한다.
1.2 Type
Type은 자바의 모든 종류의 맨 윗부분 인터페이스입니다. 자바의 하위 클래스는Generic Array Type, Parameterized Type, Type Variable, Wildcard Type입니다. 이것은java에 있습니다.lang.reflect 패키지 아래의 클래스입니다.또한 우리가 가장 잘 아는 클래스 클래스 Class도 Type 인터페이스를 구현했습니다.
일반적으로 GsonBuilder의registerTypeAdapter()를 호출하여 등록하고 첫 번째 파라미터는Class 유형을 사용하면 된다.
1.3 JsonDeserializationContext
이 클래스는 우리가 정의한 Json Deserialization의 deserialize () 방법을 다른 클래스에서 호출할 때 전달됩니다. Gson에서 유일하게 실현되는 것은 TreeType Adapter의 개인 내부 클래스인 Gson Context Impl입니다.사용자 정의 Json Deserializer () 에서 Json Deserialize () 방법을 호출해서 대상을 얻을 수 있습니다.
그러나 Json Deserialization Context에 전달된 json이 Json Deserializer에 전달된 json과 같으면 사순환 호출을 초래할 수 있음을 명심해야 한다.
2 사고방식 분석
2.1 자바빈 만들기
아니면 맨 위에 있는 json으로 분석하면list는 Json Array에 대응한다. 그 중 두 개의 Json Object 중attributes에 대응하는 Json Object 필드는 완전히 다르지만 통일을 위해 JavaBean을 쓸 때 공통된 부류를 설정할 수 있다. 비록 비어 있지만.
public class Attribute {
      ...
}

public class AddressAttribute extends Attribute {
    private String street;
    private String city;
    private String country;
...   get/set
}

public class NameAttribute extends Attribute {
    @SerializedName("first-name")
    private String firstname;
    @SerializedName("last-name")
    private String lastname;
...  get/set
}

Attribute를 설정합니다. 이 SuperClass는 Gson Builder에 등록하기 위해서입니다. 구체적으로 해석할 때 type에 대응하는 유형에 따라 대응하는 Class를 찾을 것입니다.
 gsonBuilder.registerTypeAdapter(Attribute.class, new AttributeJsonDeserializer());

여기에 이르면 type에 대응하는value는 구체적인JavaBean과 대응해야 한다는 것을 생각해야 한다.예를 들면 여기서.
"address"——AddressAttribute.class
"name"——NameAttribute.class

만약 type이 "address"라면, 우리는 gson으로 Address Attribute를 가져올 수 있습니다.class와 대응하는 json이 해석합니다.
Attribute attribute = gson.form(addressJson,AddressAttribute.class);

2.2 json을 정확하게 대응하는 자바빈으로 바꾸는 방법
부모 클래스 Attribute를 등록했습니다. Attribute를 반서열화하려면 대응하는 json을 매개 변수로 사용자 정의 Json Deserializer로 리셋합니다.우리는 아래의 이 방법에서 자신의 논리를 써서 우리가 필요로 하는 Attribute 대상을 얻을 수 있다.
 public Attribute deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)

하지만 세심한 친구는 이럴 때 전달하는 json이 이럴 수도 있다는 걸 알게 될 거예요.
{
   "street": "NanJing Road",
   "city": "ShangHai",
   "country": "China"
}

그럴 수도 있죠.
{
   "first-name": "Su",
   "last-name": "Tu"
}

Address Attribute로 해석해야 하는지 Name Attribute로 해석해야 하는지 어떻게 알아요???
구체적으로 어떤 것으로 해석될지 생각해 보자. type에 대응하는value를 알아야 한다.이 type은attributes와 같은 필드입니다. 아까처럼 이value를 원하지 않았을 것입니다.
다시 생각해 보면 이 type에 대응하는value가 무엇인지 알 수 있는 것은attributes의 상급 json이다.
{
   "type": "name",
   "attributes": {
                          ...
                 }  
}

그럼 Gson Builder에 type Adapter를 하나 더 등록해서 이 바깥쪽 json을 해석할 수 있을까요?그럼요.
 gsonBuilder.registerTypeAdapter(AttributeWithType.class, new AttributeWithTypeJsonDeserializer());

이 Attribute WithType은 바깥쪽 json에 대응하는 자바빈입니다.
public class AttributeWithType {
    private String type;
    private Attribute attributes;
     ...
}

Attribute WithType 종류를 반서열화할 때, 우리는 이 type에 대응하는value를 얻어서, 이value를 안쪽의Attribute에 대응하는 Json Deserializer에게 전달할 수 있다.이렇게 하면value가 "address"또는 "name"에 따라Addres Attribute나Name Attribute를 반서열화할 수 있습니다.
2, 3 구덩이가 있어요.
앞에서 말했듯이 Json Deserialization Context를 호출하는 방법은 사순환에 주의해야 한다.구체적인 실천에서 나는 Json Deserialization Context의 방법을 사용하지 않았지만 여전히 사순환하는 상황이 나타났다.내가 이렇게 쓰니까.
 AttributeWithType attributeWithType = gson.fromJson(json, AttributeWithType.class);

언뜻 보기에는 별 문제가 없는데, 문제는 바로 이 gson에게서 나온다.이 gson은 Attribute WithType을 분석하는 Gson Builder를 등록해서 만든 것입니다.gson.fromJson () 방법의 json은 AttributeWithType에 대응하는 반서열화된 json, gson입니다.fromJson () 내부에서 AttributeWithType에 대응하는 Json Deserializer () 방법이 다시 호출되어 사순환을 일으킨다.
사순환을 피하는 방법은 GsonBuilder로 gson을 새로 만드는 것입니다. 이 GsonBuilder는AttributeWithType를 등록하지 않고 Attribute만 등록해서 해석합니다.
3 더 잘 통용되기 위해
1. 프로젝트에 다른 형식의 json이 존재할 수 있습니다. 외부에는 type 요소가 없고 다른 요소와 같은 JsonObject에 놓여 있습니다.이런 형식은 더욱 편리하다. 외부의 typeAdaper를 등록하지 않아도 된다.
{
    "total": 2,
    "list": [
        {
            "type": "address",
            "street": "NanJing Road",
            "city": "ShangHai",
            "country": "China"
        },
        {
            "type": "name",
            "first-name": "Su",
            "last-name": "Tu"
        }
    ]
}

MultiTypeJsonParser multiTypeJsonParser = new MultiTypeJsonParser.Builder()
        .registerTypeElementName("type")
        .registerTargetClass(Attribute.class)
//         jsonObejct                 ,        Type,     
//        .registerTargetUpperLevelClass(AttributeWithType.class)
        .registerTypeElementValueWithClassType("address", AddressAttribute.class)
        .registerTypeElementValueWithClassType("name", NameAttribute.class)
        .build();

ListInfoWithType listInfoWithType = multiTypeJsonParser.fromJson(TestJson.TEST_JSON_1, ListInfoWithType.class);

2. 해석 과정에서 일부 유형이MultiTypeJsonParser의 Builder에 등록되지 않은 것을 발견하면 해석할 때 해당하는 jsonObject를 만나면null로 되돌아갑니다.예를 들어 아래의 json에서'type'에 대응하는'parents'가 등록되지 않으면 반서열화할 때 이 json이 대표하는 대상은null이다.
 {
        "type": "parents",
        "attributes": {
          "mather": "mi lan",
          "father": "lin ken"
        }
 }

안드로이드에서 우리가 이러한 json을 반서열한 후에 얻은 대상을 목록 컨트롤에 설정합니다. 만약 백엔드에서 되돌아오는 json에 이전에 등록되지 않은 형식이 포함된다면, 프로그램이crash에 이르지 않기 위해 반서열화된null 대상을 필터해야 합니다. 프로젝트에 도구류인ListItemFilter가 집합의null 요소를 필터할 수 있습니다.
4 결어
이런 유형이 다른 Json Object를 어떻게 우아하게 해석하는지에 대해 처음에 나는 생각이 부족했고 인터넷에서도 적당한 문서를 찾지 못했다.그러나 Gson의 문서와 원본을 보고 자신의 이해와 분석을 통해 이 과정을 점차적으로 완성했다.나의 감명 중 하나는 정부의 사용 문서를 많이 보는 것이 맹목적으로 해결 방안을 검색하는 것보다 낫다는 것이다.
참고 자료
Gson 공식 문서

좋은 웹페이지 즐겨찾기