如何处理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不包含Gson
或user_id
密钥,是否有user_name
失败的选项?
在许多情况下,您可能需要至少解析一些值,而其他值可能是可选的?
是否有任何模式或库可用于全局处理此案例?
感谢。
答案 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)
这是我的简单解决方案,可以用最少的编码创建通用解决方案。
我正在使用子类化,因此在超类中完成了繁重的工作。
这是超类代码。
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);
}
};
}
}