以“TryParse”方式反序列化 json

问题描述 投票:0回答:8

当我向服务(我不拥有)发送请求时,它可能会使用请求的 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
8个回答
94
投票

@Victor LG 使用 Newtonsoft 的答案很接近,但它在技术上并没有像原始海报所要求的那样避免捕获。它只是将其移动到其他地方。此外,虽然它创建了一个设置实例来捕获丢失的成员,但这些设置不会传递给 DeserializeObject 调用,因此它们实际上会被忽略。

这是他的扩展方法的“catch free”版本,其中还包括缺少成员标志。避免捕获的关键是将设置对象的

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

64
投票

使用

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


34
投票

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

当您没有可用于任何类型的文本架构时,可以使用此方法。


32
投票

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

3
投票

您可以将 JSON 反序列化为 dynamic

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

除此之外,我认为你不能做得比

try/catch

更好。

真正糟糕的是服务器发送 HTTP 200 来指示错误。

try/catch

 简单地显示为输入检查。


1
投票
向您的类添加一个 Error 属性,或者更好地使用具有此错误属性的基类,如下所示:

public class BaseResult { public Error Error { get; set; } public bool HasError => String.IsNullOrEmpty(Error?.Code); } public class Error { public string Status { get; set; } public string Code { get; set; } }
任何结果类都继承自此基本结果:

public class MyOkResponseClass : BaseResult { public string Prop1 { get; set; } public string Prop2 { get; set; } public int Prop3 { get; set; } }
然后你可以检查属性HasError。没有例外,没有扩展方法,也没有奇怪的检查。


0
投票
如果您想坚持使用 Newtonsoft,您可以在反序列化时利用

JsonSerializerSettings

 类。首先将 
MissingMeberHandling
 属性设置为 error。然后设置 
Error
 记录解析时发生错误,但将错误设置为已处理。

var settings = new JsonSerializerSettings() { MissingMemberHandling = MissingMemberHandling.Error, Error = (object sender, Newtonsoft.Json.Serialization.ErrorEventArgs args) => { success = false; args.ErrorContext.Handled = true; } }; deserializedObject = JsonConvert.DeserializeObject<T>(json, settings);
使用此方法,您可以创建自己的

TryDeserializeObject<T>()

方法。

bool TryDeserializeObject<T>(string json, out T deserializedObject) where T : class { var success = true; deserializedObject = null; if (!String.IsNullOrWhiteSpace(json)) { var settings = new JsonSerializerSettings() { MissingMemberHandling = MissingMemberHandling.Error, Error = (object sender, Newtonsoft.Json.Serialization.ErrorEventArgs args) => { success = false; args.ErrorContext.Handled = true; } }; deserializedObject = JsonConvert.DeserializeObject<T>(json, settings); } else { success = false; } return success; }
    

-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);
    
© www.soinside.com 2019 - 2024. All rights reserved.