web-dev-qa-db-ja.com

「TryParse」の方法でJSONを逆シリアル化します

私が所有していないサービスにリクエストを送信すると、リクエストされたJSONデータ、または次のようなエラーで応答する場合があります。

{
    "error": {
        "status": "error message",
        "code": "999"
    }
}

どちらの場合も、HTTP応答コードは200 OKなので、それを使用してエラーがあるかどうかを判断することはできません。応答をデシリアライズして確認する必要があります。だから私はこのようなものがあります:

bool TryParseResponseToError(string jsonResponse, out Error error)
{
    // Check expected error keywords presence
    // before try clause to avoid catch performance drawbacks
    if (jsonResponse.Contains("error") &&
        jsonResponse.Contains("status") &&
        jsonResponse.Contains("code"))
    {
        try
        {
            error = new JsonSerializer<Error>().DeserializeFromString(jsonResponse);
            return true;
        }
        catch
        {
            // The JSON response seemed to be an error, but failed to deserialize.
            // Or, it may be a successful JSON response: do nothing.
        }
    }

    error = null;
    return false;
}

ここでは、標準の実行パスに存在する可能性のある空のcatch句がありますが、これは悪臭です...まあ、悪臭以上です。悪臭を放ちます。

標準の実行パスで"TryParse"キャッチを回避にするためのより良い方法を知っていますか?

[編集]

Yuval Itzchakov の回答のおかげで、そのようにメソッドを改善しました:

bool TryParseResponse(string jsonResponse, out Error error)
{
    // Check expected error keywords presence :
    if (!jsonResponse.Contains("error") ||
        !jsonResponse.Contains("status") ||
        !jsonResponse.Contains("code"))
    {
        error = null;
        return false;
    }

    // Check json schema :
    const string errorJsonSchema =
        @"{
              'type': 'object',
              'properties': {
                  'error': {'type':'object'},
                  'status': {'type': 'string'},
                  'code': {'type': 'string'}
              },
              'additionalProperties': false
          }";
    JsonSchema schema = JsonSchema.Parse(errorJsonSchema);
    JObject jsonObject = JObject.Parse(jsonResponse);
    if (!jsonObject.IsValid(schema))
    {
        error = null;
        return false;
    }

    // Try to deserialize :
    try
    {
        error = new JsonSerializer<Error>.DeserializeFromString(jsonResponse);
        return true;
    }
    catch
    {
        // The JSON response seemed to be an error, but failed to deserialize.
        // This case should not occur...
        error = null;
        return false;
    }
}

念のため、catch句を保持しました。

53
Dude Pascalou

Json.NET スキーマに対してJSONを検証できます:

 string schemaJson = @"{
 'status': {'type': 'string'},
 'error': {'type': 'string'},
 'code': {'type': 'string'}
}";

JsonSchema schema = JsonSchema.Parse(schemaJson);

JObject jobj = JObject.Parse(yourJsonHere);
if (jobj.IsValid(schema))
{
    // Do stuff
}

そして、それをTryParseメソッド内で使用します。

public static T TryParseJson<T>(this string json, string schema) where T : new()
{
    JsonSchema parsedSchema = JsonSchema.Parse(schema);
    JObject jObject = JObject.Parse(json);

    return jObject.IsValid(parsedSchema) ? 
        JsonConvert.DeserializeObject<T>(json) : default(T);
}

それから:

var myType = myJsonString.TryParseJson<AwsomeType>(schema);

更新:

スキーマ検証はメインのNewtonsoft.Jsonパッケージの一部ではなくなっていることに注意してください。 Newtonsoft.Json.Schema パッケージを追加する必要があります。

アップデート2:

コメントで述べたように、「JSONSchema」には価格モデルがあり、無料ではないことを意味します。すべての情報を見つけることができます こちら

49
Yuval Itzchakov

@Yuvalの回答を少し修正したバージョン。

static T TryParse<T>(string jsonData) where T : new()
{
  JSchemaGenerator generator = new JSchemaGenerator();
  JSchema parsedSchema = generator.Generate(typeof(T));
  JObject jObject = JObject.Parse(jsonData);

  return jObject.IsValid(parsedSchema) ?
      JsonConvert.DeserializeObject<T>(jsonData) : default(T);
}

これは、どのタイプのスキーマもテキストとしてすぐに利用できない場合に使用できます。

24
M22an

Newtonsoftを使用した@Victor LGの答えは近いですが、元のポスターが要求したようなキャッチを技術的に回避するものではありません。他の場所に移動するだけです。また、不足しているメンバーをキャッチできるように設定インスタンスを作成しますが、これらの設定はDeserializeObject呼び出しに渡されないため、実際には無視されます。

以下に、欠落しているメンバーのフラグを含む、拡張メソッドの「キャッチフリー」バージョンを示します。キャッチを回避する鍵は、設定オブジェクトのErrorプロパティをラムダに設定することです。ラムダは、失敗を示すフラグを設定し、例外を発生させないようにエラーをクリアします。

 public static bool TryParseJson<T>(this string @this, out T result)
 {
    bool success = true;
    var settings = new JsonSerializerSettings
    {
        Error = (sender, args) => { success = false; args.ErrorContext.Handled = true; },
        MissingMemberHandling = MissingMemberHandling.Error
    };
    result = JsonConvert.DeserializeObject<T>(@this, settings);
    return success;
}

これを使用する例を次に示します。

if(value.TryParseJson(out MyType result))
{ 
    // Do something with result…
}
19
Steve In CO

Try/catchアプローチの例を提供するだけです(誰かに役立つかもしれません)。

public static bool TryParseJson<T>(this string obj, out T result)
{
    try
    {
        // Validate missing fields of object
        JsonSerializerSettings settings = new JsonSerializerSettings();
        settings.MissingMemberHandling = MissingMemberHandling.Error;

        result = JsonConvert.DeserializeObject<T>(obj, settings);
        return true;
    }
    catch (Exception)
    {
        result = default(T);
        return false;
    }
}

次に、次のように使用できます。

var result = default(MyObject);
bool isValidObject = jsonString.TryParseJson<MyObject>(out result);

if(isValidObject)
{
    // Do something
}
10
Victor LG

JSONをdynamicに逆シリアル化 し、ルート要素がerrorであるかどうかを確認できます。サーバーがstatusノード内で有効な非エラー応答も送信しない限り、おそらく実際に行うようにcodeおよびerrorの存在を確認する必要はないことに注意してください。

それを除けば、try/catch

実際に悪臭を放つのは、サーバーがHTTP 200を送信してエラーを示すことです。 try/catchは、単に入力のチェックとして表示されます。

3