在" TryParse"中反序列化json。办法

时间:2014-05-28 08:17:46

标签: c# .net 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;
    }
}

我保留了捕获条款......以防万一。

6 个答案:

答案 0 :(得分:46)

使用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:

如评论中所述,&#34; JSONSchema&#34;有一个定价模型,意思是它不是免费的。您可以找到所有信息here

答案 1 :(得分:21)

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

如果您没有随时可用于任何类型的文本架构,则可以使用此选项。

答案 2 :(得分:17)

@Victor LG使用Newtonsoft的答案很接近,但是从技术上讲,它并不能避免原始海报要求的问题。它只是将其移至其他位置。同样,尽管它创建了一个设置实例来捕获丢失的成员,但是这些设置不会传递给DeserializeObject调用,因此实际上将被忽略。

这是他的扩展方法的“免费版本”,其中还包括缺少成员标志。避免捕获的关键是将设置对象的Error属性设置为lambda,然后该lambda设置一个标志以指示失败并清除错误,从而不会引起异常。

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

答案 3 :(得分:10)

仅提供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
}

答案 4 :(得分:3)

您可以deserialize JSON to a dynamic,并检查根元素是否为error。请注意,您可能不必像实际那样检查是否存在statuscode,除非服务器还在error内发送有效的非错误响应节点

除此之外,我认为你做得比try/catch更好。

实际发生的问题是服务器发送HTTP 200来指示错误。 try/catch只是检查输入。

答案 5 :(得分:-1)

要测试文本与模式无关是否为有效的JSON,您还可以检查字符串响应中引号的数量:“,如下所示:

// Invalid JSON
var responseContent = "asgdg"; 

// Valid JSON, uncomment to test these
// var responseContent = "{ \"ip\": \"11.161.195.10\", \"city\": \"York\",  \"region\": \"Ontartio\",  \"country\": \"IN\",  \"loc\": \"-43.7334,79.3329\",  \"postal\": \"M1C\",  \"org\": \"AS577 Bell Afgh\",  \"readme\": \"https://ipinfo.io/missingauth\"}";
// var responseContent = "\"asfasf\"";

int count = 0;
foreach (char c in responseContent)
    if (c == '\"') count++; // Escape character needed to display quotation
if (count >= 2) 
{
    // Valid Json
    JToken parsedJson = JToken.Parse(responseContent);  
    Console.WriteLine("RESPONSE: Json- " + parsedJson.ToString(Formatting.Indented));
}
else
    Console.WriteLine("RESPONSE: InvalidJson- " + responseContent);