以“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<T>("")
不会引发异常。
从技术上讲,这个答案也不一定能避免问题。如果您将无效的 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.DeserializeObjectJObject 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<T>(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,会抛出异常。以上是关于以“TryParse”方式反序列化 json的主要内容,如果未能解决你的问题,请参考以下文章
如何让Jackson JSON生成的数据包含的中文以unicode方式编码