22

プロジェクト内で Gson を使用して、JSON 文字列を Java オブジェクトに逆シリアル化します。リクエストを行う場合、サーバーから明確に定義されたレスポンスが返されることを期待します。サーバーは、私が期待する適切に定義された応答を返すか、(定義済みの) エラー オブジェクトを返します。

明確にするために、次のような単純なオブジェクトがあるとします。

class Dummy{
   private String foo;
   private int bar;
}

そして、次のようなエラー オブジェクト:

class ErrorHolder{
   private RequestError error;
}

class RequestError{
    private String publicMsg;
    private String msg;
}

次のようなサーバー応答が得られた場合

{"foo":"Hello World", "bar":3 }

すべてが期待どおりに機能します。

でも、こんな返事だったら

{"error":{"publicMsg":"Something bad happened", msg:"you forgot requesting some parameter"}}

isとis 0のようなDummyオブジェクトを取得します! Gson のドキュメント (fromJson) には、次のように明確に記載されています。foonullbar

JsonSyntaxException をスローする - json が classOfT 型のオブジェクトの有効な表現でない場合

したがって、次のように 2 番目の応答を解析しようとすると、JsonSyntaxException が発生することが予想されました。

Dummy dummy = Gson.fromJson(secondResponse, Dummy.class);

Json は Dummy オブジェクトではなく、ErrorHolder オブジェクトを表すためです。

だから私の質問は次のとおりです。Gsonが何らかの形で間違ったタイプを検出し、例外をスローする方法はありますか?

4

3 に答える 3

29

残念ながら、ドキュメントは少し誤解を招く可能性があります。

クラスにJSONの型と一致しない型のフィールドがある場合にのみ例外がスローされ、それでもそれを修正するためにいくつかのクレイジーなことを行います(たとえばint、JSONの aをクラスの a に変換します) String)。DatePOJO にフィールドのようなものがintあり、JSON で に遭遇した場合、それをスローします。JSON に存在するが POJO に存在しないフィールドは無視され、JSON に存在しないが POJO に存在するフィールドは に設定されnullます。

@Required現在、GSON は、POJO 内のフィールドの注釈の ようなものを持つ、あらゆる種類の「厳密な」デシリアライゼーションのメカニズムを提供していません。

あなたの場合... POJOを展開して内部エラーオブジェクトを含めるだけです...次のようなものです:

class Dummy {
   private String foo;
   private int bar;
   private Error error;

   private class Error {
        String publicMsg;
        String msg;
   }

   public boolean isError() {
       return error != null;
   }

   // setters and getters for your data, the error msg, etc.
}

もう 1 つのオプションは、JSON が次のようなエラーである場合に例外をスローするカスタム デシリアライザーを作成することです。

class MyDeserializer implements JsonDeserializer<Dummy>
{
    @Override
    public Dummy deserialize(JsonElement json, Type typeOfT, 
                              JsonDeserializationContext context)
                    throws JsonParseException
    {
        JsonObject jsonObject = (JsonObject) json;

        if (jsonObject.get("error") != null)
        {
            throw new JsonParseException("Error!");
        }

        return new Gson().fromJson(json, Dummy.class);
    }
} 

追加する編集:誰かが最近これに賛成票を投じて再読したところ、「ほら、これは自分でできるし、便利かもしれない」と思いました。

これは、OPが望んでいたことを正確に行う再利用可能なデシリアライザーと注釈です。制限は、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;

    }
}

出力:

ふーです
これはバーです
ふーです
ヌル
スレッド「メイン」com.google.gson.JsonParseException の例外: JSON にフィールドがありません: foo
于 2013-01-09T20:25:05.030 に答える
4

ネストされたオブジェクトを処理し、その他のいくつかのマイナーな変更を加えた、Brian のソリューションの更新バージョンを作成しました。このコードには、 JsonRequired で注釈が付けられたフィールドを持つクラスを認識する Gson オブジェクトを作成するための、より単純なビルダーも含まれます。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.List;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Lists;
import com.google.common.primitives.Primitives;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;

public class AnnotatedDeserializer<T> implements JsonDeserializer<T> {

private final Gson gson = new Gson();

public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException {

    T target = gson.fromJson(je, type);
    checkRequired(target);
    return target;
}

private List<Field> findMissingFields(Object target, List<Field> invalidFields) {

    for (Field field : target.getClass().getDeclaredFields()) {
        if (field.getAnnotation(JsonRequired.class) != null) {

            Object fieldValue = ReflectionUtil.getFieldValue(target, field);

            if (fieldValue == null) {
                invalidFields.add(field);
                continue;
            }

            if (!isPrimitive(fieldValue)) {
                findMissingFields(fieldValue, invalidFields);
            }
        }
    }
    return invalidFields;
}

private void checkRequired(Object target) {

    List<Field> invalidFields = Lists.newArrayList();
    findMissingFields(target, invalidFields);

    if (!invalidFields.isEmpty()) {
        throw new JsonParseException("Missing JSON required fields: {"
                + FluentIterable.from(invalidFields).transform(toMessage).join(Joiner.on(", ")) + "}");
    }
}

static Function<Field, String> toMessage = new Function<Field, String>() {
    @Override
    public String apply(Field field) {
        return field.getDeclaringClass().getName() + "/" + field.getName();
    }
};

private boolean isPrimitive(Object target) {

    for (Class<?> primitiveClass : Primitives.allPrimitiveTypes()) {
        if (primitiveClass.equals(target.getClass())) {
            return true;
        }
    }
    return false;
}

public static class RequiredFieldAwareGsonBuilder {

    private GsonBuilder gsonBuilder;

    private RequiredFieldAwareGsonBuilder(GsonBuilder gsonBuilder) {
        this.gsonBuilder = gsonBuilder;
    }

    public static RequiredFieldAwareGsonBuilder builder() {
        return new RequiredFieldAwareGsonBuilder(new GsonBuilder());
    }

    public <T> RequiredFieldAwareGsonBuilder withRequiredFieldAwareType(Class<T> classOfT) {
        gsonBuilder.registerTypeAdapter(classOfT, new AnnotatedDeserializer<T>());
        return this;
    }

    public Gson build() {
        return gsonBuilder.create();
    }
}

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

そしてReflectionユーティリティ

import java.lang.reflect.Field;

public final class ReflectionUtil {

private ReflectionUtil() {
}

public static Object getFieldValue(Object target, Field field) {
    try {
        boolean originalFlag = changeAccessibleFlag(field);
        Object fieldValue = field.get(target);
        restoreAccessibleFlag(field, originalFlag);
        return fieldValue;
    } catch (IllegalAccessException e) {
        throw new RuntimeException("Failed to access field " + field.getDeclaringClass().getName() + "/"
                + field.getName(), e);
    }
}

private static void restoreAccessibleFlag(Field field, boolean flag) {
    field.setAccessible(flag);
}

private static boolean changeAccessibleFlag(Field field) {
    boolean flag = field.isAccessible();
    field.setAccessible(true);
    return flag;
}
}

Guice を使用している場合は、モジュールに次のようなものを追加して、Gson オブジェクトを挿入できます。

@Provides
@Singleton
static Gson provideGson() {
    return RequiredFieldAwareGsonBuilder.builder().withRequiredFieldAwareType(MyType1.class)
            .withRequiredFieldAwareType(MyType2.class).build();
}
于 2015-03-13T04:02:04.323 に答える
2

私は選択したソリューションのファンではありません。動作しますが、Gson を使用する方法ではありません。Gson は特定の JSON スキーマをオブジェクトにマップし、その逆も同様です。理想的には、使用している JSON は整形式です (したがって、JSON 形式を制御できる場合は、変更を検討してください)。しかし、そうでない場合は、受け取ると予想されるすべてのケースを処理するように解析オブジェクトを設計する必要があります。

custom を記述する必要がある場合もありますがJsonDeserializer、これはそのような場合ではありません。メッセージまたはエラーの送信は非常に標準的な方法であり、適切なデータ構造があれば、GSON はこのような単純な使用例を直接処理できます。

JSON スキーマを制御できる場合

代わりに次のようなものを検討してください。

{
  "message": {
    "foo": "Hello World",
    "bar": 3
  },
  "error": null;
}

{
  "message": null,
  "error": {
    "publicMsg": "Something bad happened",
    "msg": "you forgot requesting some parameter"
  }
}

可能であればオブジェクトを提供 するクリーンなラッパー クラスを定義できるようになったことに注意してください。Dummy

public class JsonResponse {
  private Dummy message;
  private RequestError error;

  public boolean hasError() { return error != null; }
  public Dummy getDummy() {
    Preconditions.checkState(!hasError());
    return message;
  }
  public RequestError getError() {
    Preconditions.checkState(hasError());
    return error;
  }
}

既存の JSON スキーマを処理する必要がある場合

スキーマを再構築できない場合は、解析クラスを再構築する必要があります。次のようになります。

public class JsonResponse {
  private String foo;
  private int bar;

  private RequestError error;

  public boolean hasError() { return error != null; }
  public Dummy getDummy() {
    Preconditions.checkState(!hasError());
    return new Dummy(foo, bar);
  }
  public RequestError getError() {
    Preconditions.checkState(hasError());
    return error;
  }
}

これはスキーマを修正するよりも望ましくありませんが、どちらの方法でも同じ一般的な API を取得できます。hasError()要求が成功したかどうかを確認するために を呼び出し、必要に応じてgetDummy()またはgetError()を呼び出します。他のメソッドへの呼び出し (たとえばgetDummy()、エラーを受け取ったとき) はフェイルファーストになります。

于 2015-11-23T23:57:55.280 に答える