programing

"TryParse"방식으로 json 역 직렬화

goodcopy 2021. 1. 19. 08:04
반응형

"TryParse"방식으로 json 역 직렬화


내가 소유하지 않은 서비스에 요청을 보낼 때 요청 된 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 절이 있는데, 이는 악취입니다. 음, 악취 이상입니다. 악취가납니다.

표준 실행 경로에서 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;
    }
}

혹시라도 캐치 절을 유지했습니다.


함께 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"에는 가격 모델이 있으므로 무료가 아닙니다 . 여기에서 모든 정보를 찾을 수 있습니다.


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

어떤 유형에 대해서도 쉽게 사용할 수있는 텍스트로 스키마가 없을 때 사용할 수 있습니다.


@Victor LG's answer using Newtonsoft is close, but it doesn't technically avoid the a catch as the original poster requested. It just moves it elsewhere. Also, though it creates a settings instance to enable catching missing members, those settings aren't passed to the DeserializeObject call so they are actually ignored.

Here's a "catch free" version of his extension method that also includes the missing members flag. The key to avoiding the catch is setting the Error property of the settings object to a lambda which then sets a flag to indicate failure and clears the error so it doesn't cause an exception.

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

Here's an example to use it:

if(value.TryParseJson(out MyType result))
{ 
    // Do something with result…
}

Just to provide an example of the try/catch approach (it may be useful to somebody).

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

Then, it can be used like this:

var result = default(MyObject);
bool isValidObject = jsonString.TryParseJson<MyObject>(out result);

if(isValidObject)
{
    // Do something
}

You may deserialize JSON to a dynamic, and check whether the root element is error. Note that you probably don't have to check for the presence of status and code, like you actually do, unless the server also sends valid non-error responses inside a error node.

Aside that, I don't think you can do better than a try/catch.

What actually stinks is that the server sends an HTTP 200 to indicate an error. try/catch appears simply as checking of inputs.


To test whether a text is valid JSON regardless of schema, you could also do a check on the number of quotation marks:" in your string response, as shown below :

// Invalid JSON
var responseContent = "asgdg"; 

// 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\"";

int count = 0;
foreach (char c in responseContent)
    if (c == '\"') count++; // Escape character needed to display quotation
if (count >= 2) 
{
    // Valid Json
    JToken parsedJson = JToken.Parse(responseContent);  
    Console.WriteLine("RESPONSE: Json- " + parsedJson.ToString(Formatting.Indented));
}
else
    Console.WriteLine("RESPONSE: InvalidJson- " + responseContent);

ReferenceURL : https://stackoverflow.com/questions/23906220/deserialize-json-in-a-tryparse-way

반응형