Gson可选和必填字段

Nik*_*iko 56 json gson

应该如何处理Gson和需要与可选字段?

由于所有字段都是可选的,因此如果响应json包含某个键,我无法真正使网络请求失败,Gson只会将其解析为null.

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

例如,如果我有以下json:

{"user_id":128591, "user_name":"TestUser"}
Run Code Online (Sandbox Code Playgroud)

而我的班级:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

是否有任何模式或库可用于全局处理此案例?

谢谢.

Bri*_*ach 53

正如您所注意到的,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;

    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

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

  • 这只检查JSON对象的第一级.例如,它不会强制执行`{"foo":{"req":"bar"}}`中的属性`req`. (3认同)

And*_* K. 5

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

  • 模型超类的属性
  • 数组内部的属性

为此,可以使用以下类:

/**
 * 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);
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

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

    /**
     * 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 {}
Run Code Online (Sandbox Code Playgroud)

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

T pojo = new Gson().fromJson(je, type);
Run Code Online (Sandbox Code Playgroud)

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

checkRequiredFields(pojo.getClass().getDeclaredFields(), pojo);
Run Code Online (Sandbox Code Playgroud)

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

checkSuperClasses(pojo);
Run Code Online (Sandbox Code Playgroud)

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

我们来看看checkRequiredFields方法。首先,它检查某个属性是否是List(json 数组)的实例 - 在这种情况下,还应该检查列表中的所有对象,以确保它们也提供了所有必需的字段:

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

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

        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);
                }
            }
        }
Run Code Online (Sandbox Code Playgroud)

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

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

最后让我们回顾一下其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;

}
Run Code Online (Sandbox Code Playgroud)

我们可以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);
Run Code Online (Sandbox Code Playgroud)

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