以“TryParse”方式反序列化 json

Posted

技术标签:

【中文标题】以“TryParse”方式反序列化 json【英文标题】:Deserialize json in a "TryParse" way 【发布时间】: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 子句……以防万一。

【问题讨论】:

【参考方案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,谢谢你指出这一点。我刚刚更新了我的答案以验证何时缺少要反序列化的对象的字段。注意:基于此答案的更新学分:***.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。请注意,您可能不必像实际那样检查statuscode 的存在,除非服务器也在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,会抛出异常。

以上是关于以“TryParse”方式反序列化 json的主要内容,如果未能解决你的问题,请参考以下文章

在不反序列化的情况下操作 JSON

如何让Jackson JSON生成的数据包含的中文以unicode方式编码

JSON杰克逊序列化反序列化列表列表

使用嵌套对象中的属性反序列化 JSON [重复]

Typescript (Angular) - JSON 模型反序列化

如何快速反序列化 JSON