Подтвердить что ты не робот

Deserialize json в режиме "TryParse"

Когда я отправляю запрос в службу (которой я не владею), она может ответить либо запрошенными данными 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" ответ, чтобы избежать перехват в стандартном пути выполнения?

[РЕДАКТИРОВАТЬ]

Благодаря ответу Ювала Ицчакова я усовершенствовал свой метод так:

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

Я сохранил пункт "поймать"... на всякий случай.

4b9b3361

Ответ 1

С 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" есть модель ценообразования, то есть она не бесплатна. Вы можете найти всю информацию здесь

Ответ 2

Немного измененная версия ответа @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);
}

Это можно использовать, если у вас нет схемы, которая легко доступна для любого типа.

Ответ 3

@Victor Ответ LG с использованием Newtonsoft близок, но технически он не позволяет избежать подвоха, как требовал оригинальный постер. Это просто перемещает это в другое место. Кроме того, хотя он создает экземпляр параметров, позволяющий перехватывать отсутствующие элементы, эти параметры не передаются вызову DeserializeObject, поэтому они фактически игнорируются.

Здесь "метод освобождения" его метода расширения, который также включает флаг отсутствующих членов. Ключом к избежанию перехвата является установка для свойства Error объекта settings лямбды, которая затем устанавливает флаг, указывающий на сбой, и очищает ошибку, чтобы она не вызывала исключения.

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

Ответ 4

Просто для того, чтобы привести пример подхода "попробовать/поймать" (он может кому-нибудь пригодиться).

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
}

Ответ 5

Вы можете десериализовать JSON на dynamic и проверить, является ли корневой элемент error. Обратите внимание, что вам, вероятно, не нужно проверять наличие status и code, как вы на самом деле, если только сервер не отправляет достоверные ответы без ошибок внутри error node.

Кроме того, я не думаю, что вы можете сделать лучше, чем try/catch.

Что фактически воняет, так это то, что сервер отправляет HTTP 200, чтобы указать на ошибку. try/catch отображается просто как проверка входов.