Подтвердить что ты не робот

Дополнительные поля и обязательные поля Gson

Как следует иметь дело с Gson и обязательными или необязательными полями?

Поскольку все поля являются необязательными, я не могу действительно выполнить мой сетевой запрос, исходя из того, что ответ json содержит некоторый ключ, Gson просто проанализирует его на null.

Метод Я использую gson.fromJson(json, mClassOfT);

Например, если у меня есть следующий json:

{"user_id":128591, "user_name":"TestUser"}

И мой класс:

public class User {

    @SerializedName("user_id")
    private String mId;

    @SerializedName("user_name")
    private String mName;

    public String getId() {
        return mId;
    }

    public void setId(String id) {
        mId = id;
    }

    public String getName() {
        return mName;
    }

    public void setName(String name) {
        mName = name;
    }
}

Является ли какой-либо способ получить Gson для отказа, если json не будет содержать ключ user_id или user_name?

Может быть много случаев, когда вам могут понадобиться хотя бы некоторые значения для анализа, а другие - необязательные?

Существует ли какой-либо шаблон или библиотека для обработки этого случая по всему миру?

Спасибо.

4b9b3361

Ответ 1

Как вы заметили, у Gson нет возможности определить "обязательное поле", и вы просто получите null в своем десериализованном объекте, если в JSON чего-то не хватает.

Здесь используется повторно используемый десериализатор и аннотация, которые будут делать это. Ограничение состоит в том, что если POJO требовал настраиваемого десериализатора как есть, вам нужно было бы пойти немного дальше и либо передать объект Gson в конструкторе, чтобы десериализовать объект, либо переместить аннотацию, проверяя ее на отдельный и используйте его в своем десериализаторе. Вы также можете улучшить обработку исключений, создав собственное исключение и передав его в JsonParseException, чтобы его можно было обнаружить через getCause() в вызывающем.

Все сказанное в подавляющем большинстве случаев будет работать:

public class App
{

    public static void main(String[] args)
    {
        Gson gson =
            new GsonBuilder()
            .registerTypeAdapter(TestAnnotationBean.class, new AnnotatedDeserializer<TestAnnotationBean>())
            .create();

        String json = "{\"foo\":\"This is foo\",\"bar\":\"this is bar\"}";
        TestAnnotationBean tab = gson.fromJson(json, TestAnnotationBean.class);
        System.out.println(tab.foo);
        System.out.println(tab.bar);

        json = "{\"foo\":\"This is foo\"}";
        tab = gson.fromJson(json, TestAnnotationBean.class);
        System.out.println(tab.foo);
        System.out.println(tab.bar);

        json = "{\"bar\":\"This is bar\"}";
        tab = gson.fromJson(json, TestAnnotationBean.class);
        System.out.println(tab.foo);
        System.out.println(tab.bar);
    }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface JsonRequired
{
}

class TestAnnotationBean
{
    @JsonRequired public String foo;
    public String bar;
}

class AnnotatedDeserializer<T> implements JsonDeserializer<T>
{

    public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException
    {
        T pojo = new Gson().fromJson(je, type);

        Field[] fields = pojo.getClass().getDeclaredFields();
        for (Field f : fields)
        {
            if (f.getAnnotation(JsonRequired.class) != null)
            {
                try
                {
                    f.setAccessible(true);
                    if (f.get(pojo) == null)
                    {
                        throw new JsonParseException("Missing field in JSON: " + f.getName());
                    }
                }
                catch (IllegalArgumentException ex)
                {
                    Logger.getLogger(AnnotatedDeserializer.class.getName()).log(Level.SEVERE, null, ex);
                }
                catch (IllegalAccessException ex)
                {
                    Logger.getLogger(AnnotatedDeserializer.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
        return pojo;

    }
}

Выход:

This is foo
this is bar
This is foo
null
Exception in thread "main" com.google.gson.JsonParseException: Missing field in JSON: foo

Ответ 2

Это мое простое решение, которое создает общее решение с минимальным кодированием.

  • Создать @Optional аннотацию
  • Отметить первый вариант. Отдых считается необязательным. Предполагается, что раньше предполагается.
  • Создайте общий метод "загрузчик", который проверяет, что исходный объект Json имеет значение. Цикл останавливается, когда встречается поле @Optional.

Я использую подклассу, поэтому работа с ворчанием выполняется в суперклассе.

Вот код суперкласса.

import com.google.gson.Gson;
import java.lang.reflect.Field;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
... 
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Optional {
  public boolean enabled() default true;
}

и метод работы grunt

@SuppressWarnings ("unchecked")
  public <T> T payload(JsonObject oJR,Class<T> T) throws Exception {
  StringBuilder oSB = new StringBuilder();
  String sSep = "";
  Object o = gson.fromJson(oJR,T);
  // Ensure all fields are populated until we reach @Optional
  Field[] oFlds =  T.getDeclaredFields();
  for(Field oFld:oFlds) {
    Annotation oAnno = oFld.getAnnotation(Optional.class);
    if (oAnno != null) break;
    if (!oJR.has(oFld.getName())) {
      oSB.append(sSep+oFld.getName());
      sSep = ",";
    }
  }
  if (oSB.length() > 0) throw CVT.e("Required fields "+oSB+" mising");
  return (T)o;
}

и пример использования

public static class Payload {
  String sUserType ;
  String sUserID   ;
  String sSecpw    ;
  @Optional
  String sUserDev  ;
  String sUserMark ;
}

и заполняющий код

Payload oPL = payload(oJR,Payload.class);

В этом случае sUserDev и sUserMark являются необязательными, а остальные - обязательными. Решение основывается на том, что класс хранит определения полей в заявленном порядке.

Ответ 3

Я много искал и не нашел хорошего ответа. Решение, которое я выбрал, выглядит следующим образом:

Каждое поле, которое мне нужно установить из JSON, представляет собой объект, то есть вложенный в квадрат Integer, Boolean и т.д. Затем, используя отражение, я могу проверить, что поле не равно null:

public class CJSONSerializable {
    public void checkDeserialization() throws IllegalAccessException, JsonParseException {
        for (Field f : getClass().getDeclaredFields()) {
            if (f.get(this) == null) {
                throw new JsonParseException("Field " + f.getName() + " was not initialized.");
            }
        }
    }
}

Из этого класса я могу получить свой объект JSON:

public class CJSONResp extends CJSONSerializable {
  @SerializedName("Status")
  public String status;

  @SerializedName("Content-Type")
  public String contentType;
}

а затем после разбора с помощью GSON я могу вызвать checkDeserialization, и он сообщит мне, если некоторые из полей являются нулевыми.

Ответ 4

Ответа на вопрос Брайана Роуча очень хорошо, но иногда ему также необходимо обращаться:

  • свойства суперкласса модели
  • свойства внутри массивов

Для этих целей может использоваться следующий класс:

/**
 * Adds the feature to use required fields in models.
 *
 * @param <T> Model to parse to.
 */
public class JsonDeserializerWithOptions<T> implements JsonDeserializer<T> {

    /**
     * To mark required fields of the model:
     * json parsing will be failed if these fields won't be provided.
     * */
    @Retention(RetentionPolicy.RUNTIME) // to make reading of this field possible at the runtime
    @Target(ElementType.FIELD)          // to make annotation accessible throw the reflection
    public @interface FieldRequired {}

    /**
     * Called when the model is being parsed.
     *
     * @param je   Source json string.
     * @param type Object model.
     * @param jdc  Unused in this case.
     *
     * @return Parsed object.
     *
     * @throws JsonParseException When parsing is impossible.
     * */
    @Override
    public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
            throws JsonParseException {
        // Parsing object as usual.
        T pojo = new Gson().fromJson(je, type);

        // Getting all fields of the class and checking if all required ones were provided.
        checkRequiredFields(pojo.getClass().getDeclaredFields(), pojo);

        // Checking if all required fields of parent classes were provided.
        checkSuperClasses(pojo);

        // All checks are ok.
        return pojo;
    }

    /**
     * Checks whether all required fields were provided in the class.
     *
     * @param fields Fields to be checked.
     * @param pojo   Instance to check fields in.
     *
     * @throws JsonParseException When some required field was not met.
     * */
    private void checkRequiredFields(@NonNull Field[] fields, @NonNull Object pojo)
            throws JsonParseException {
        // Checking nested list items too.
        if (pojo instanceof List) {
            final List pojoList = (List) pojo;
            for (final Object pojoListPojo : pojoList) {
                checkRequiredFields(pojoListPojo.getClass().getDeclaredFields(), pojoListPojo);
                checkSuperClasses(pojoListPojo);
            }
        }

        for (Field f : fields) {
            // If some field has required annotation.
            if (f.getAnnotation(FieldRequired.class) != null) {
                try {
                    // Trying to read this field value and check that it truly has value.
                    f.setAccessible(true);
                    Object fieldObject = f.get(pojo);
                    if (fieldObject == null) {
                        // Required value is null - throwing error.
                        throw new JsonParseException(String.format("%1$s -> %2$s",
                                pojo.getClass().getSimpleName(),
                                f.getName()));
                    } else {
                        checkRequiredFields(fieldObject.getClass().getDeclaredFields(), fieldObject);
                        checkSuperClasses(fieldObject);
                    }
                }

                // Exceptions while reflection.
                catch (IllegalArgumentException | IllegalAccessException e) {
                    throw new JsonParseException(e);
                }
            }
        }
    }

    /**
     * Checks whether all super classes have all required fields.
     *
     * @param pojo Object to check required fields in its superclasses.
     *
     * @throws JsonParseException When some required field was not met.
     * */
    private void checkSuperClasses(@NonNull Object pojo) throws JsonParseException {
        Class<?> superclass = pojo.getClass();
        while ((superclass = superclass.getSuperclass()) != null) {
            checkRequiredFields(superclass.getDeclaredFields(), pojo);
        }
    }

}

Прежде всего описывается интерфейс (аннотация) для отметки обязательных полей, мы увидим пример его использования позже:

    /**
     * To mark required fields of the model:
     * json parsing will be failed if these fields won't be provided.
     * */
    @Retention(RetentionPolicy.RUNTIME) // to make reading of this field possible at the runtime
    @Target(ElementType.FIELD)          // to make annotation accessible throw the reflection
    public @interface FieldRequired {}

Затем реализуется метод deserialize. Он, как обычно, анализирует строки json: отсутствующие свойства в результате pojo будут иметь значения null:

T pojo = new Gson().fromJson(je, type);

Затем запускается рекурсивная проверка всех полей разбора pojo:

checkRequiredFields(pojo.getClass().getDeclaredFields(), pojo);

Затем мы также проверяем все поля суперклассов pojo:

checkSuperClasses(pojo);

Требуется, когда некоторый SimpleModel расширяет свой SimpleParentModel, и мы хотим удостовериться, что все свойства SimpleModel, отмеченные как требуемые, представлены как SimpleParentModel.

Посмотрим на метод checkRequiredFields. Прежде всего, он проверяет, является ли какое-либо свойство экземпляром List (json array) - в этом случае все объекты списка также должны быть проверены, чтобы убедиться, что они также имеют все обязательные поля:

if (pojo instanceof List) {
    final List pojoList = (List) pojo;
    for (final Object pojoListPojo : pojoList) {
        checkRequiredFields(pojoListPojo.getClass().getDeclaredFields(), pojoListPojo);
        checkSuperClasses(pojoListPojo);
    }
}

Затем мы выполняем итерацию по всем полям pojo, проверяя, имеются ли все поля с аннотацией FieldRequired (что означает, что эти поля не являются нулевыми). Если мы столкнулись с некоторым нулевым свойством, которое требуется - исключение будет запущено. В противном случае для текущего поля будет запущен другой рекурсивный шаг проверки, а свойства родительских классов поля также будут проверены:

        for (Field f : fields) {
            // If some field has required annotation.
            if (f.getAnnotation(FieldRequired.class) != null) {
                try {
                    // Trying to read this field value and check that it truly has value.
                    f.setAccessible(true);
                    Object fieldObject = f.get(pojo);
                    if (fieldObject == null) {
                        // Required value is null - throwing error.
                        throw new JsonParseException(String.format("%1$s -> %2$s",
                                pojo.getClass().getSimpleName(),
                                f.getName()));
                    } else {
                        checkRequiredFields(fieldObject.getClass().getDeclaredFields(), fieldObject);
                        checkSuperClasses(fieldObject);
                    }
                }

                // Exceptions while reflection.
                catch (IllegalArgumentException | IllegalAccessException e) {
                    throw new JsonParseException(e);
                }
            }
        }

И последний метод должен быть рассмотрен checkSuperClasses: он просто запускает аналогичные свойства проверки проверки правильности полей суперклассов pojo:

    Class<?> superclass = pojo.getClass();
    while ((superclass = superclass.getSuperclass()) != null) {
        checkRequiredFields(superclass.getDeclaredFields(), pojo);
    }

И, наконец, рассмотрим пример использования JsonDeserializerWithOptions. Предположим, что мы имеем следующие модели:

private class SimpleModel extends SimpleParentModel {

    @JsonDeserializerWithOptions.FieldRequired Long id;
    @JsonDeserializerWithOptions.FieldRequired NestedModel nested;
    @JsonDeserializerWithOptions.FieldRequired ArrayList<ListModel> list;

}

private class SimpleParentModel {

    @JsonDeserializerWithOptions.FieldRequired Integer rev;

}

private class NestedModel extends NestedParentModel {

    @JsonDeserializerWithOptions.FieldRequired Long id;

}

private class NestedParentModel {

    @JsonDeserializerWithOptions.FieldRequired Integer rev;

}

private class ListModel {

    @JsonDeserializerWithOptions.FieldRequired Long id;

}

Мы можем быть уверены, что SimpleModel будет корректно анализироваться без исключений таким образом:

final Gson gson = new GsonBuilder()
    .registerTypeAdapter(SimpleModel.class, new JsonDeserializerWithOptions<SimpleModel>())
    .create();

gson.fromJson("{\"list\":[ { \"id\":1 } ], \"id\":1, \"rev\":22, \"nested\": { \"id\":2, \"rev\":2 }}", SimpleModel.class);

Конечно, при условии, что решение может быть улучшено и принимать дополнительные функции: например, проверки для вложенных объектов, которые не помечены аннотацией FieldRequired. В настоящее время он выходит из области ответа, но может быть добавлен позже.