Gson可选和必填字段

时间:2014-02-07 11:33:52

标签: json 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;
    }
}

如果json不包含Gsonuser_id密钥,是否有user_name失败的选项?

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

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

感谢。

5 个答案:

答案 0 :(得分:47)

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

这是一个可重复使用的反序列化器和注释,它将执行此操作。限制是如果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

答案 1 :(得分:2)

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

}

首先描述了标记必填字段的界面(注释),我们稍后会看到它的用法示例:

    /**
     * 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的形式提供。< / p>

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

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

然后我们遍历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);
                }
            }
        }

应该审查的最后一个方法是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注释的嵌套对象进行验证。目前它不在答案的范围内,但可以在以后添加。

答案 2 :(得分:1)

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

我需要从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.");
            }
        }
    }
}

从这个类中,我可以派生出我的JSON对象:

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

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

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

答案 3 :(得分:0)

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

  1. 创建@Optional注释
  2. Mark First Optional。休息是可选的。早先需要假设。
  3. 创建一个通用的'loader'方法,检查源Json对象是否有值。一旦遇到@Optional字段,循环就会停止。
  4. 我正在使用子类化,因此在超类中完成了繁重的工作。

    这是超类代码。

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

    和笨拙的工作方法

    @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是可选的,其余部分是必需的。该解决方案依赖于类以声明的顺序存储字段定义的事实。

答案 4 :(得分:0)

(受Brian Roache的回答启发。)

Brian的答案似乎不适用于原语,因为可以将值初始化为非null的值(例如0)。

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

在某些情况下,将必填字段(例如,JsonOptional字段中的例外)列入白名单比将所需的所有字段添加注释更为安全。

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

尽管这种方法可以轻松地改用于必填字段。

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

      }
    };

  }
}