以“ TryParse”方式反序列化json


76

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

Answers:


56

有了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”具有定价模型,这意味着它不是免费的。您可以在这里找到所有信息


12
此软件包还需要许可证才能每小时使用1000多次验证:newtonsoft.com/jsonschema
dpix

@dpix注意绝对重要。我将其添加到答案中,谢谢。
Yuval Itzchakov


42

@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
请注意,除非您在结果类上使用[JsonProperty(Required = Required.Always)],否则这对于空对象(例如“ {}”)返回true。
user764754

@ user764754但这不是此实现唯一的。JsonConvert.DeserializeObject<T>("{}")除非T是数组类型(或您所要求的属性),否则调用不会引发异常。
加布里埃尔·卢西(Jabry Luci)

2
从技术上讲,此答案也不一定能避免问题。如果将无效的JSON传递给此方法,则JsonConvert.DeserializeObject将引发错误并在内部捕获它。在这种情况下,此方法只会将捕获的内容移动到其他地方。
哮喘

29

@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 <T>(jsonData)比做(T)jObject好。您已经对JObject反序列化了一次,所以看起来强制转换可能更便宜?我真的不知道
2017年

1
同样在许多情况下,如果您不控制JSON,将JObject.Parse()包装在具有单独try / catch的单独方法中可能是明智的,因为它会为无效JSON引发异常。这里可能发生两种不同的情况,1)无效的JSON,2)Json与您期望的架构不匹配。就我们而言,我们的处理方式有所不同。
2017年

我关于反序列化与强制转换的第一个问题是无效的。应该说:jObject.ToObject <T>()
求解

@solvingJ,可以分别处理无效的JSON和不匹配模式的JSON,但是对于我们而言,我们不希望无效的JSON,并且在不同的层中进行处理,因此这对我们来说很有意义。JObject jObject = JObject.Parse(jsonData);对于我们的情况,此检查是多余的。
M22an

19

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

1
这似乎不起作用。如果DeserializeObject是与对象不匹配的有效json,则不会引发异常。使用此方法解决原始问题将需要进一步验证。
德里克

2
您完全正确@Derrick,谢谢您指出这一点。我刚刚更新了我的答案,以验证何时缺少要反序列化的对象的字段。注:在此基础上回答了更新的学分:stackoverflow.com/questions/21030712/...
维克多LG

1
在您的代码示例旧章中仍然出现了错字。应该是result = JsonConvert.DeserializeObject<T>(obj, settings);-除此之外,感谢您的分享,一个很好的解决方案对我有所帮助:)
Richard Moore

1
也更改catch (JsonSerializationException ex) 为,catch (Exception)以便在将无效Json传递给它时返回false。
理查德·摩尔

3

您可以将JSON反序列化为dynamic,并检查root元素是否为error。请注意,您可能没有检查的存在statuscode,就像你实际做,除非该服务器还发送内部有效的非错误响应error节点。

除此之外,我认为您做不到 try/catch

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


感谢您的回答。没错:实际上是发臭的是表示错误的HTTP 200 OK代码...
Dude Pascalou 2014年

-3

要测试一个文本(无论架构如何)是否为有效的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(responseContent中的char c){if(c =='\“')count ++; //如果(count> 1)break,则显示引号所需的转义字符;}尽管编译和运行它需要花费更多时间,但它会如果您使用此foreach,则可能会节省很长的JSON响应时间
Saamer

1
“ {}”是有效的JSON,但被拒绝,但此代码。“ {\” ip \“ = \” 11.161.195.10 \“}”是无效的JSON,将引发异常。
garethm
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.