json Gson 可选和必填字段

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21626690/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 20:13:39  来源:igfitidea点击:

Gson optional and required fields

jsongson

提问by Niko

How should one deal with Gsonand required versus optional fields?

应该如何处理Gson必填字段与可选字段?

Since all fields are optional, I can't really fail my network request based on if the response json contains some key, Gsonwill simply parse it to null.

由于所有字段都是可选的,因此我无法根据响应 json 是否包含某些键来使我的网络请求失败,Gson只会将其解析为 null。

Method I am using gson.fromJson(json, mClassOfT);

我正在使用的方法 gson.fromJson(json, mClassOfT);

For example if I have following json:

例如,如果我有以下 json:

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

And my class:

还有我的班级:

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;
    }
}

Is the any option to get Gsonto fail if json would not contain user_idor user_namekey?

Gson如果 json 不包含user_iduser_name键,是否有任何失败的选择?

There can be many cases where you might need at least some values to be parsed and other one could be optional?

在很多情况下,您可能至少需要解析一些值,而其他值可能是可选的?

Is there any pattern or library to be used to handle this case globally?

是否有任何模式或库可用于全局处理这种情况?

Thanks.

谢谢。

采纳答案by Brian Roach

As you note, Gson has no facility to define a "required field" and you'll just get nullin your deserialized object if something is missing in the JSON.

正如您所注意到的,Gson 无法定义“必填字段”,null如果 JSON 中缺少某些内容,您只会进入反序列化的对象。

Here's a re-usable deserializer and annotation that will do this. The limitation is that if the POJO required a custom deserializer as-is, you'd have to go a little further and either pass in a Gsonobject in the constructor to deserialize to object itself or move the annotation checking out into a separate method and use it in your deserializer. You could also improve on the exception handling by creating your own exception and pass it to the JsonParseExceptionso it can be detected via getCause()in the caller.

这是一个可重复使用的解串器和注释,可以做到这一点。限制是,如果 POJO 按原样需要自定义反序列化器,则您必须更进一步,要么Gson在构造函数中传入一个对象以反序列化为对象本身,要么将检出的注释移到单独的方法中并使用它在你的解串器中。您还可以通过创建自己的异常并将其传递给 来改进异常处理,JsonParseException以便getCause()在调用者中检测到它。

That all said, in the vast majority of cases, this will work:

尽管如此,在绝大多数情况下,这将起作用:

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;

    }
}

Output:

输出:

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

回答by Andrei K.

Answer of Brian Roach is very good, but sometimes it's also necessary to handle:

Brian Roach 的回答很好,但有时也需要处理:

  • properties of model's super class
  • properties inside of arrays
  • 模型超类的属性
  • 数组内部的属性

For these purposes the following class can be used:

出于这些目的,可以使用以下类:

/**
 * 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 through reflection
    public @interface FieldRequired {}

    /**
     * Called when the model is being parsed.
     *
     * @param je   Source json string.
     * @param type Object's 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's 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);
        }
    }

}

First of all the interface (annotation) to mark required fields with is described, we'll see an example of its usage later:

首先描述了标记必填字段的接口(注解),稍后我们将看到其用法示例:

    /**
     * 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 {}

Then deserializemethod is implemented. It parses json strings as usual: missing properties in result pojowill have nullvalues:

然后deserialize实现方法。它像往常一样解析 json 字符串:结果中缺少的属性pojo将具有null值:

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

Then the recursive check of all fields of the parsed pojois being launched:

然后pojo正在启动对解析的所有字段的递归检查:

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

Then we also check all fields of pojo's super classes:

然后我们还检查pojo的超类的所有字段:

checkSuperClasses(pojo);

It's required when some SimpleModelextends its SimpleParentModeland we want to make sure that all properties of SimpleModelmarked as required are provided as SimpleParentModel's ones.

当某些SimpleModel扩展它时它是必需的SimpleParentModel,我们希望确保SimpleModel标记为必需的所有属性都作为SimpleParentModel's提供。

Let's take a look on checkRequiredFieldsmethod. First of all it checks if some property is instance of List(json array) - in this case all objects of the list should also be checked to make sure that they have all required fields provided too:

让我们来看看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);
    }
}

Then we are iterating through all fields of pojo, checking if all fields with FieldRequiredannotation are provided (what means these fields are not null). If we have encountered some null property which is required - an exception will be fired. Otherwise another recursive step of the validation will be launched for current field, and properties of parent classes of the field will be checked too:

然后我们遍历 的所有字段pojo,检查是否提供了所有带FieldRequired注释的字段(这意味着这些字段不为空)。如果我们遇到一些必需的空属性 - 将触发异常。否则,将针对当前字段启动另一个递归验证步骤,并且也会检查该字段的父类的属性:

        for (Field f : fields) {
            // If some field has required annotation.
            if (f.getAnnotation(FieldRequired.class) != null) {
                try {
                    // Trying to read this field's 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);
                }
            }
        }

And the last method should be reviewed is checkSuperClasses: it just runs the similar required fields validation checking properties of pojo's super classes:

应该的最后一个方法是checkSuperClasses:它只运行pojo的超类的类似必需字段验证检查属性:

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

And finally lets review some example of this JsonDeserializerWithOptions's usage. Assume we have the following models:

最后让我们回顾一下 thisJsonDeserializerWithOptions用法的一些例子。假设我们有以下模型:

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;

}

We can be sure that SimpleModelwill be parsed correctly without exceptions in this way:

我们可以确保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);

Of course, provided solution can be improved and accept more features: for example - validations for nested objects which are not marked with FieldRequiredannotation. Currently it's out of answer's scope, but can be added later.

当然,提供的解决方案可以改进并接受更多功能:例如 - 对未使用FieldRequired注释标记的嵌套对象进行验证。目前它超出了答案的范围,但可以稍后添加。

回答by Steve Pritchard

This is my simple solution that creates a generic solution with minimum coding.

这是我使用最少编码创建通用解决方案的简单解决方案。

  1. Create @Optional annotation
  2. Mark First Optional. Rest are assumed optional. Earlier are assumed required.
  3. Create a generic 'loader' method that checks that source Json object has a value. The loop stops once an @Optional field is encountered.
  1. 创建@Optional注解
  2. 标记第一个可选。休息是可选的。之前假设需要。
  3. 创建一个通用的“加载器”方法,用于检查源 Json 对象是否具有值。一旦遇到@Optional 字段,循环就会停止。

I am using subclassing so the grunt work is done in the superclass.

我正在使用子类化,所以繁重的工作是在超类中完成的。

Here is the superclass code.

这是超类代码。

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;
}

and the grunt work method

和繁重的工作方法

@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;
}

and an example of usage

和使用示例

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

and the populating code

和填充代码

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

In this case sUserDev and sUserMark are optional and the rest required. The solution relies on the fact that the class stores the Field definitions in the declared order.

在这种情况下,sUserDev 和 sUserMark 是可选的,其余的都是必需的。该解决方案依赖于该类以声明的顺序存储 Field 定义的事实。

回答by Adam

I searched a lot and found no good answer. The solution I chose is as follows:

我搜索了很多,没有找到好的答案。我选择的解决方案如下:

Every field that I need to set from JSON is an object, i.e. boxed Integer, Boolean, etc. Then, using reflection, I can check that the field is not null:

我需要从 JSON 设置的每个字段都是一个对象,即装箱的整数、布尔值等。然后,使用反射,我可以检查该字段是否为空:

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.");
            }
        }
    }
}

From this class, I can derive my JSON object:

从这个类,我可以派生我的 J​​SON 对象:

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

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

and then after parsing with GSON, I can call checkDeserialization and it will report me if some of the fields is null.

然后在用 GSON 解析后,我可以调用 checkDeserialization,如果某些字段为空,它会报告我。

回答by dsg

(Inspired by Brian Roache's answer.)

(灵感来自布赖恩罗奇的回答。)

It seems that Brian's answer doesn't work for primitives because the values can be initialized as something other than null (e.g. 0).

似乎布赖恩的答案不适用于原语,因为这些值可以初始化为 null 以外的其他值(例如0)。

Moreover, it seems like the deserializer would have to be registered for every type. A more scalable solution uses TypeAdapterFactory(as below).

此外,似乎必须为每种类型注册解串器。使用更具扩展性的解决方案TypeAdapterFactory(如下所示)。

In certain circumstances, it is safer to whitelist exceptions from required fields (i.e. as JsonOptionalfields) rather than annotating all fields as required.

在某些情况下,将必填字段(即作为JsonOptional字段)中的异常列入白名单比按要求注释所有字段更安全。

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

Though this approach can easily be adapted for required fields instead.

虽然这种方法可以很容易地适用于所需的字段。

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.Streams;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class AnnotatedTypeAdapterFactory implements TypeAdapterFactory {
  @Override
  public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
    Class<? super T> rawType = typeToken.getRawType();

    Set<Field> requiredFields = Stream.of(rawType.getDeclaredFields())
            .filter(f -> f.getAnnotation(JsonOptional.class) == null)
            .collect(Collectors.toSet());

    if (requiredFields.isEmpty()) {
      return null;
    }

    final TypeAdapter<T> baseAdapter = (TypeAdapter<T>) gson.getAdapter(rawType);

    return new TypeAdapter<T>() {

      @Override
      public void write(JsonWriter jsonWriter, T o) throws IOException {
        baseAdapter.write(jsonWriter, o);
      }

      @Override
      public T read(JsonReader in) throws IOException {
        JsonElement jsonElement = Streams.parse(in);

        if (jsonElement.isJsonObject()) {
          ArrayList<String> missingFields = new ArrayList<>();
          for (Field field : requiredFields) {
            if (!jsonElement.getAsJsonObject().has(field.getName())) {
              missingFields.add(field.getName());
            }
          }
          if (!missingFields.isEmpty()) {
            throw new JsonParseException(
                    String.format("Missing required fields %s for %s",
                            missingFields, rawType.getName()));
          }
        }

        TypeAdapter<T> delegate = gson.getDelegateAdapter(AnnotatedTypeAdapterFactory.this, typeToken);
        return delegate.fromJsonTree(jsonElement);

      }
    };

  }
}