programing

개조를 사용하여 GSON을 사용하여 중첩된 JSON 개체 가져오기

javaba 2022. 9. 3. 23:40
반응형

개조를 사용하여 GSON을 사용하여 중첩된 JSON 개체 가져오기

Android 앱에서 API를 사용하고 있는데 JSON의 반응은 모두 다음과 같습니다.

{
    'status': 'OK',
    'reason': 'Everything was fine',
    'content': {
         < some data here >
}

에 'POJO'가 입니다.status,reason[ ] 및 [ 。contentPOJO라고 합니다.

Gson을 추출할 수 있는 ?contentPOJO는?

내장된 개체를 반환하는 사용자 지정 역직렬화기를 작성합니다.

예를 들어 JSON은 다음과 같습니다.

{
    "status":"OK",
    "reason":"some reason",
    "content" : 
    {
        "foo": 123,
        "bar": "some value"
    }
}

★★★★★★★★★★★★★★★★★★★★★★★★★.Content POJO:

class Content
{
    public int foo;
    public String bar;
}

다음으로 디시리얼라이저를 작성합니다.

class MyDeserializer implements JsonDeserializer<Content>
{
    @Override
    public Content deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
        throws JsonParseException
    {
        // Get the "content" element from the parsed JSON
        JsonElement content = je.getAsJsonObject().get("content");

        // Deserialize it. You use a new instance of Gson to avoid infinite recursion
        // to this deserializer
        return new Gson().fromJson(content, Content.class);

    }
}

, 이제 ,GsonGsonBuilder이치노

Gson gson = 
    new GsonBuilder()
        .registerTypeAdapter(Content.class, new MyDeserializer())
        .create();

수 .Content:

Content c = gson.fromJson(myJson, Content.class);

댓글에서 추가할 편집:

메시지 유형은 다르지만 모두 "content" 필드가 있는 경우 다음과 같이 Deserializer를 일반화할 수 있습니다.

class MyDeserializer<T> implements JsonDeserializer<T>
{
    @Override
    public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
        throws JsonParseException
    {
        // Get the "content" element from the parsed JSON
        JsonElement content = je.getAsJsonObject().get("content");

        // Deserialize it. You use a new instance of Gson to avoid infinite recursion
        // to this deserializer
        return new Gson().fromJson(content, type);

    }
}

각 유형의 인스턴스를 등록하기만 하면 됩니다.

Gson gson = 
    new GsonBuilder()
        .registerTypeAdapter(Content.class, new MyDeserializer<Content>())
        .registerTypeAdapter(DiffContent.class, new MyDeserializer<DiffContent>())
        .create();

했을 때.fromJson()타입은 디시리얼라이저로 이동하기 때문에 모든 타입에서 동작합니다.

마지막으로 Retrofit 인스턴스를 작성할 때:

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(url)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();

@BrianRoach b @ @ @ @ @ @ @ @ 。 다 커스텀이 커스텀 .TypeAdapter은 꼭 하셔야 합니다.TypeAdapterGSON의 새로운 인스턴스, 그렇지 않으면 두 번째 인스턴스로TypeAdapter이것새로운 있기 때문입니다.Gson커스텀 디시리얼라이저 내의 인스턴스.

예를 들어 다음과 같은 json이 있는 경우:

{
    "status": "OK",
    "reason": "some reason",
    "content": {
        "foo": 123,
        "bar": "some value",
        "subcontent": {
            "useless": "field",
            "data": {
                "baz": "values"
            }
        }
    }
}

그리고 이 JSON을 다음 오브젝트에 매핑하려고 했습니다.

class MainContent
{
    public int foo;
    public String bar;
    public SubContent subcontent;
}

class SubContent
{
    public String baz;
}

합니다.SubContent의 »TypeAdapter할 수 보다 견고하게 하기 위해 다음을 수행할 수 있습니다.

public class MyDeserializer<T> implements JsonDeserializer<T> {
    private final Class mNestedClazz;
    private final Object mNestedDeserializer;

    public MyDeserializer(Class nestedClazz, Object nestedDeserializer) {
        mNestedClazz = nestedClazz;
        mNestedDeserializer = nestedDeserializer;
    }

    @Override
    public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException {
        // Get the "content" element from the parsed JSON
        JsonElement content = je.getAsJsonObject().get("content");

        // Deserialize it. You use a new instance of Gson to avoid infinite recursion
        // to this deserializer
        GsonBuilder builder = new GsonBuilder();
        if (mNestedClazz != null && mNestedDeserializer != null) {
            builder.registerTypeAdapter(mNestedClazz, mNestedDeserializer);
        }
        return builder.create().fromJson(content, type);

    }
}

다음과 같이 작성합니다.

MyDeserializer<Content> myDeserializer = new MyDeserializer<Content>(SubContent.class,
                    new SubContentDeserializer());
Gson gson = new GsonBuilder().registerTypeAdapter(Content.class, myDeserializer).create();

은 단순히 '콘텐츠'의 네스트된 '에도 쉽게 할 수 .MyDeserializer값을 합니다.

조금 늦었지만 이게 누군가를 도울 수 있기를 바란다.

다음의 타입 어댑터 팩토리를 작성하기만 하면 됩니다.

    public class ItemTypeAdapterFactory implements TypeAdapterFactory {

      public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {

        final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
        final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);

        return new TypeAdapter<T>() {

            public void write(JsonWriter out, T value) throws IOException {
                delegate.write(out, value);
            }

            public T read(JsonReader in) throws IOException {

                JsonElement jsonElement = elementAdapter.read(in);
                if (jsonElement.isJsonObject()) {
                    JsonObject jsonObject = jsonElement.getAsJsonObject();
                    if (jsonObject.has("content")) {
                        jsonElement = jsonObject.get("content");
                    }
                }

                return delegate.fromJsonTree(jsonElement);
            }
        }.nullSafe();
    }
}

GSON 빌더에 추가합니다.

.registerTypeAdapterFactory(new ItemTypeAdapterFactory());

또는

 yourGsonBuilder.registerTypeAdapterFactory(new ItemTypeAdapterFactory());

며칠 전에도 같은 문제가 있었어응답 래퍼 클래스와 RxJava 트랜스포머를 사용하여 이 문제를 해결했습니다.이것은 매우 유연한 솔루션이라고 생각합니다.

래퍼:

public class ApiResponse<T> {
    public String status;
    public String reason;
    public T content;
}

상태가 정상이 아닌 경우 던지는 사용자 지정 예외:

public class ApiException extends RuntimeException {
    private final String reason;

    public ApiException(String reason) {
        this.reason = reason;
    }

    public String getReason() {
        return apiError;
    }
}

Rx 변압기:

protected <T> Observable.Transformer<ApiResponse<T>, T> applySchedulersAndExtractData() {
    return observable -> observable
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .map(tApiResponse -> {
                if (!tApiResponse.status.equals("OK"))
                    throw new ApiException(tApiResponse.reason);
                else
                    return tApiResponse.content;
            });
}

사용 예:

// Call definition:
@GET("/api/getMyPojo")
Observable<ApiResponse<MyPojo>> getConfig();

// Call invoke:
webservice.getMyPojo()
        .compose(applySchedulersAndExtractData())
        .subscribe(this::handleSuccess, this::handleError);


private void handleSuccess(MyPojo mypojo) {
    // handle success
}

private void handleError(Throwable t) {
    getView().showSnackbar( ((ApiException) throwable).getReason() );
}

주제: RxJava - Gson - "글로벌" 역직렬화, 응답 유형 변경

Brian의 생각을 이어가자면, 거의 항상 많은 REST 리소스가 각각 독자적인 루트를 가지고 있기 때문에, 디시리얼라이제이션의 일반화에 도움이 될 수 있습니다.

 class RestDeserializer<T> implements JsonDeserializer<T> {

    private Class<T> mClass;
    private String mKey;

    public RestDeserializer(Class<T> targetClass, String key) {
        mClass = targetClass;
        mKey = key;
    }

    @Override
    public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
            throws JsonParseException {
        JsonElement content = je.getAsJsonObject().get(mKey);
        return new Gson().fromJson(content, mClass);

    }
}

다음으로 위에서 샘플 payload를 해석하기 위해 GSON 디시리얼라이저를 등록할 수 있습니다.

Gson gson = new GsonBuilder()
    .registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class, "content"))
    .build();

더 나은 해결책은 이것일 수 있습니다.

public class ApiResponse<T> {
    public T data;
    public String status;
    public String reason;
}

그리고 서비스를 다음과 같이 정의합니다.

Observable<ApiResponse<YourClass>> updateDevice(..);

제 경우, 응답마다 "콘텐츠" 키가 바뀝니다.예:

// Root is hotel
{
  status : "ok",
  statusCode : 200,
  hotels : [{
    name : "Taj Palace",
    location : {
      lat : 12
      lng : 77
    }

  }, {
    name : "Plaza", 
    location : {
      lat : 12
      lng : 77
    }
  }]
}

//Root is city

{
  status : "ok",
  statusCode : 200,
  city : {
    name : "Vegas",
    location : {
      lat : 12
      lng : 77
    }
}

이 경우 위와 같은 솔루션을 사용했지만 수정해야 했습니다.여기서 요점을 볼 수 있습니다.여기 SOF에 올리기에는 좀 너무 커요.

주석@InnerKey("content")나머지 코드는 Gson에서의 사용을 용이하게 하기 위한 것입니다.

이는 @AYarulin과 동일한 솔루션이지만 클래스 이름이 JSON 키 이름이라고 가정합니다.이 방법에서는 클래스 이름만 전달하면 됩니다.

 class RestDeserializer<T> implements JsonDeserializer<T> {

    private Class<T> mClass;
    private String mKey;

    public RestDeserializer(Class<T> targetClass) {
        mClass = targetClass;
        mKey = mClass.getSimpleName();
    }

    @Override
    public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
            throws JsonParseException {
        JsonElement content = je.getAsJsonObject().get(mKey);
        return new Gson().fromJson(content, mClass);

    }
}

위에서 샘플 payload를 해석하려면 GSON 디시리얼라이저를 등록합니다.이는 키가 대소문자를 구분하기 때문에 문제가 됩니다.따라서 클래스 이름의 대소문자는 JSON 키의 대소문자와 일치해야 합니다.

Gson gson = new GsonBuilder()
.registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class))
.build();

Brian Roach와 AYarulin의 답변을 바탕으로 한 Kotlin 버전입니다.

class RestDeserializer<T>(targetClass: Class<T>, key: String?) : JsonDeserializer<T> {
    val targetClass = targetClass
    val key = key

    override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): T {
        val data = json!!.asJsonObject.get(key ?: "")

        return Gson().fromJson(data, targetClass)
    }
}

@Brian Roach와 @rafakob i의 답변에 따르면 다음과 같이 처리했습니다.

서버로부터의 Json 응답

{
  "status": true,
  "code": 200,
  "message": "Success",
  "data": {
    "fullname": "Rohan",
    "role": 1
  }
}

공통 데이터 핸들러 클래스

public class ApiResponse<T> {
    @SerializedName("status")
    public boolean status;

    @SerializedName("code")
    public int code;

    @SerializedName("message")
    public String reason;

    @SerializedName("data")
    public T content;
}

커스텀 시리얼라이저

static class MyDeserializer<T> implements JsonDeserializer<T>
{
     @Override
      public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
                    throws JsonParseException
      {
          JsonElement content = je.getAsJsonObject();

          // Deserialize it. You use a new instance of Gson to avoid infinite recursion
          // to this deserializer
          return new Gson().fromJson(content, type);

      }
}

Gson 객체

Gson gson = new GsonBuilder()
                    .registerTypeAdapter(ApiResponse.class, new MyDeserializer<ApiResponse>())
                    .create();

API 호출

 @FormUrlEncoded
 @POST("/loginUser")
 Observable<ApiResponse<Profile>> signIn(@Field("email") String username, @Field("password") String password);

restService.signIn(username, password)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .subscribe(new Observer<ApiResponse<Profile>>() {
                    @Override
                    public void onCompleted() {
                        Log.i("login", "On complete");
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.i("login", e.toString());
                    }

                    @Override
                    public void onNext(ApiResponse<Profile> response) {
                         Profile profile= response.content;
                         Log.i("login", profile.getFullname());
                    }
                });

잊지 마세요.@SerializedName그리고.@ExposeGSON에 의해 JSON에서 가장 역직렬화된 모든 클래스 멤버 및 내부 클래스 멤버에 대한 주석.

https://stackoverflow.com/a/40239512/1676736 를 참조해 주세요.

또 하나의 심플한 솔루션:

JsonObject parsed = (JsonObject) new JsonParser().parse(jsonString);
Content content = gson.fromJson(parsed.get("content"), Content.class);

더 간단한 방법이 있습니다. 그냥 생각해 보세요.content서브 오브젝트를 다른 클래스로 지정합니다.

class Content {
    var foo = 0
    var bar: String? = null
}

class Response {
    var statis: String? = null
    var reason: String? = null
    var content: Content? = null
} 

그리고 이제 당신은Response를 입력하여 json을 역직렬화합니다.

언급URL : https://stackoverflow.com/questions/23070298/get-nested-json-object-with-gson-using-retrofit

반응형