【问题标题】:Deserialize json in a "TryParse" way以“TryParse”方式反序列化 json
【发布时间】:2014-07-17 08:35:30
【问题描述】:

当我向服务(我不拥有该服务)发送请求时,它可能会以请求的 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 子句……以防万一。

【问题讨论】:

    标签: c# .net json


    【解决方案1】:

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

    这是他的扩展方法的“免费”版本,其中还包括缺少成员标志。避免捕获的关键是将设置对象的Error 属性设置为 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…
    }
    

    【讨论】:

    • 请注意,除非您在结果类上使用 [JsonProperty(Required = Required.Always)],否则对于空对象(例如“{}”)返回 true。
    • @user764754 但这并不是这个实现所独有的。除非T 是数组类型(或者如您所说,需要属性),否则调用JsonConvert.DeserializeObject&lt;T&gt;("{}") 不会引发异常。
    • 从技术上讲,这个答案也不一定能避免问题。如果您将无效的 JSON 传递给此方法,则 JsonConvert.DeserializeObject 将引发错误并在内部捕获它。在这种情况下,这个方法只是将捕获物移到别处。
    【解决方案2】:

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

    如 cmets 中所述,“JSONSchema”具有定价模型,这意味着它不是免费的。您可以找到所有信息here

    【讨论】:

    • 此软件包还需要许可证才能使用每小时超过 1000 次验证:newtonsoft.com/jsonschema
    • @dpix 一定要注意。我会把它添加到答案中,谢谢。
    • 就我而言,我使用github.com/RicoSuter/NJsonSchema
    • 澄清:它是具有定价模型的库,而不是 JSON Schema 作为格式。
    【解决方案3】:

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

    当您没有可用于任何类型的文本模式时,可以使用此选项。

    【讨论】:

    • 很好奇:JsonConvert.DeserializeObject(jsonData) 比仅仅做 (T)jObject 更好。您已经对 JObject 进行了一次反序列化,所以看起来强制转换可能更便宜?我真的不知道。
    • 此外,在许多情况下,如果您不控制 JSON,最好将 JObject.Parse() 包装在一个单独的方法中并使用单独的 try/catch,因为它会为无效的 JSON 引发异常。这里可能发生两种不同的情况,1) JSON 无效,2) Json 与您期望的模式不匹配。在我们的例子中,我们的处理方式不同。
    • 我关于反序列化与强制转换的第一个问题是无效的。应该说:jObject.ToObject()
    • @solvingJ,处理无效的 JSON 和不匹配模式的 JSON 可以分开处理,但对于我们的例子,我们不期望无效的 JSON,而是在不同的层处理,所以这对我们来说是有意义的。这个JObject jObject = JObject.Parse(jsonData); 检查对于我们的案例来说是多余的。
    【解决方案4】:

    只是提供一个 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
    }
    

    【讨论】:

    • 这似乎不起作用。如果 DeserializeObject 是与对象不匹配的有效 json,则不会引发异常。使用这种方法解决原始问题需要进一步验证。
    • 你完全正确@Derrick,谢谢你指出这一点。我刚刚更新了我的答案以验证何时缺少要反序列化的对象的字段。注意:基于此答案的更新学分:stackoverflow.com/questions/21030712/…
    • 在您的代码示例老章节中仍然有错字。应该是result = JsonConvert.DeserializeObject&lt;T&gt;(obj, settings); - 除此之外,感谢分享,一个很好的解决方案帮助了我:)
    • 还将catch (JsonSerializationException ex) 更改为catch (Exception) 以便在将无效Json 传递给它时返回false。
    【解决方案5】:

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

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

    真正令人讨厌的是服务器发送 HTTP 200 来指示错误。 try/catch 仅显示为检查输入。

    【讨论】:

    • 感谢您的回答。你是对的:实际上很糟糕的是指示错误的 HTTP 200 OK 代码......
    【解决方案6】:

    要测试文本是否是有效的 JSON 而不管架构,您还可以检查字符串响应中的引号:" 的数量,如下所示:

    // Invalid JSON
    var responseContent = "asgdg"; 
    // var responseContent = "{ \"ip\" = \"11.161.195.10\" }";
    
    // 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\"";
    // var responseContent = "{}";
    
    int count = 0;
    foreach (char c in responseContent)
        if (c == '\"') count++; // Escape character needed to display quotation
    if (count >= 2 || responseContent == "{}") 
    {
        // Valid Json
        try {
            JToken parsedJson = JToken.Parse(responseContent);
            Console.WriteLine("RESPONSE: Json- " + parsedJson.ToString(Formatting.Indented));
        }  
        catch(Exception ex){
            Console.WriteLine("RESPONSE: InvalidJson- " + responseContent);
        }
    }
    else
        Console.WriteLine("RESPONSE: InvalidJson- " + responseContent);
    

    【讨论】:

    • foreach (char c in responseContent) { if (c == '\"') count++; // 显示引号需要转义字符 if (count > 1) break; } 虽然需要更多时间编译和运行它,如果你使用这个 foreach 可能会节省很长的 JSON 响应的时间
    • "{}" 是有效的 JSON,但被拒绝但此代码。 "{ \"ip\" = \"11.161.195.10\" }" 不是有效的 JSON,会抛出异常。
    猜你喜欢
    • 2023-03-05
    • 2020-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-02
    • 1970-01-01
    • 2011-07-09
    相关资源
    最近更新 更多