Получите вложенный объект JSON с помощью GSON с помощью модифицированной
Я использую API из моего приложения для Android, и все ответы JSON выглядят следующим образом:
{
'status': 'OK',
'reason': 'Everything was fine',
'content': {
< some data here >
}
Проблема в том, что все мои POJO имеют поля status
, reason
, а внутри поля content
- это реальное POJO, которое я хочу.
Есть ли способ создать пользовательский конвертер Gson, чтобы всегда извлекать поле content
, поэтому переоснащение возвращает подходящий POJO?
Ответы
Ответ 1
Вы должны написать собственный десериализатор, который возвращает встроенный объект.
Допустим, ваш 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);
}
}
Теперь, если вы Gson
с помощью GsonBuilder
и регистрируете десериализатор:
Gson gson =
new GsonBuilder()
.registerTypeAdapter(Content.class, new MyDeserializer())
.create();
Вы можете десериализовать свой JSON прямо на ваш Content
:
Content c = gson.fromJson(myJson, Content.class);
Изменить, чтобы добавить из комментариев:
Если у вас есть разные типы сообщений, но у всех них есть поле "контент", вы можете сделать универсальный десериализатор, выполнив:
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();
Ответ 2
Решение @BrianRoach является правильным решением. Стоит отметить, что в специальном случае, когда у вас есть вложенные пользовательские объекты, для которых требуется пользовательский TypeAdapter
, вы должны зарегистрировать TypeAdapter
с помощью нового экземпляра GSON, в противном случае второй 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
с нулевыми значениями.
Ответ 3
Немного поздно, но, надеюсь, это поможет кому-то.
Просто создайте следующий TypeAdapterFactory.
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());
Ответ 4
Продолжая идею Брайана, потому что у нас почти всегда есть много ресурсов 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);
}
}
Затем, чтобы проанализировать полезную нагрузку образца сверху, мы можем зарегистрировать десериализатор GSON:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class, "content"))
.build();
Ответ 5
Была та же проблема пару дней назад. Я решаю это с помощью класса оболочки ответа и трансформатора RxJava, который, я думаю, является довольно гибким решением:
Упаковочный:
public class ApiResponse<T> {
public String status;
public String reason;
public T content;
}
Пользовательское исключение для throw, когда состояние не в порядке:
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() );
}
Моя тема:
Дооснащение 2 RxJava - Gson -" Global " десериализация, изменение типа ответа
Ответ 6
Лучшим решением может быть это.
public class ApiResponse<T> {
public T data;
public String status;
public String reason;
}
Затем определите свое обслуживание следующим образом.
Observable<ApiResponse<YourClass>> updateDevice(..);
Ответ 7
В соответствии с ответом @Brian Roach и @rafakob я сделал это следующим образом
Ответ 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();
Апи-вызов
@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());
}
});
Ответ 8
Это то же самое решение, что и @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);
}
}
Затем, чтобы проанализировать полезную нагрузку образца сверху, мы можем зарегистрировать десериализатор GSON. Это проблематично, поскольку ключ чувствителен к регистру, поэтому случай имени класса должен соответствовать случаю ключа JSON.
Gson gson = new GsonBuilder()
.registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class))
.build();
Ответ 9
Здесь версия Котлина, основанная на ответах Брайана Роуча и А.Ярулина.
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)
}
}
Ответ 10
В моем случае ключ "контент" будет изменяться для каждого ответа. Пример:
// 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
}
}
В таких случаях я использовал аналогичное решение, как указано выше, но должен был его подстроить. Вы можете увидеть gist здесь. Он слишком большой, чтобы размещать его здесь на SOF.
Используется аннотация @InnerKey("content")
, а остальная часть кода предназначена для облегчения ее использования с помощью Gson.
Ответ 11
Не забудьте аннотации @SerializedName
и @Expose
для всех членов класса и членов внутреннего класса, которые наиболее десериализованы из JSON GSON.
Посмотрите на fooobar.com/questions/67815/...
Ответ 12
Поскольку вы используете библиотеку GSon, вы можете аннотировать поле с помощью @SerializedName
, таким образом:
public class SomeClass{
@SerializedName("status")
String status;
@SerializedName("reason")
String reason;
@SerializedName("content")
Content content;
// add your getters and setters
public static class Content{
//Your content
}
}
И тогда вы можете опровергнуть вас выше строки json, таким образом:
Gson gson = new Gson();
YourObjectType obj = gson.fromJson(jsonString, YourObjectType.class);
Получите доступ к своему контенту таким образом:
SomeClass.Content content = obj.getContent();
Надеюсь, это сработает!
Ответ 13
Еще одно простое решение:
JsonObject parsed = (JsonObject) new JsonParser().parse(jsonString);
Content content = gson.fromJson(parsed.get("content"), Content.class);