C# System.Text.Json JSON 值无法转换为 Class 结构

C# System.Text.Json The JSON value could not be converted to Class structure

我正在使用第三方 API,我无法更改,有时 return 是标准 JSON 结构,而在其他时候,它不是。我怀疑原因是第 3 方应用程序中的数据在 UI 中是可选的。如果存在数据,它将如下所示:

{
    "contactID": "1234",
    :
    <some other attributes>
    :
    "Websites": {
        "ContactWebsite": {
            "url": "www.foo.com"
        }
    }
}

当它不存在时,它看起来像这样...

{
    "id": "1234",
    :
    <some other attributes>
    :
    "Websites": ""
}

我的代码看起来像...

using System;
using System.Text.Json;

namespace TestJsonError
{
    class Program
    {
        static void Main(string[] args)
        {
            string content = "{ \"contactID\": \"217\", \"custom\": \"\", \"noEmail\": \"true\", \"noPhone\": \"true\", \"noMail\": \"true\", \"Websites\": \"\", \"timeStamp\": \"2020-10-13T19:21:38+00:00\" }";

            Console.WriteLine($"{content}");

            var response = JsonSerializer.Deserialize<ContactResponse>(content);

            Console.WriteLine($"contactID={response.contactID}");
        }
    }

    public class ContactResponse
    {
        public string contactID { get; set; }
        public string custom { get; set; }
        public string noEmail { get; set; }
        public string noPhone { get; set; }
        public string noMail { get; set; }
        public WebsitesResponse Websites { get; set; }
        public DateTime timeStamp { get; set; }
    }

    public class WebsitesResponse
    {
        public ContactWebsiteResponse ContactWebsite { get; set; }
    }

    public class ContactWebsiteResponse
    {
        public string url { get; set; }
    }
}

我收到以下错误...

> System.Text.Json.JsonException   HResult=0x80131500   Message=The JSON
> value could not be converted to TestJsonError.WebsitesResponse. Path:
> $.Websites | LineNumber: 0 | BytePositionInLine: 106.  
> Source=System.Text.Json   StackTrace:    at
> System.Text.Json.ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type
> propertyType)    at
> System.Text.Json.JsonPropertyInfoNotNullable`4.OnRead(ReadStack&
> state, Utf8JsonReader& reader)    at
> System.Text.Json.JsonPropertyInfo.Read(JsonTokenType tokenType,
> ReadStack& state, Utf8JsonReader& reader)    at
> System.Text.Json.JsonSerializer.ReadCore(JsonSerializerOptions
> options, Utf8JsonReader& reader, ReadStack& readStack)    at
> System.Text.Json.JsonSerializer.ReadCore(Type returnType,
> JsonSerializerOptions options, Utf8JsonReader& reader)    at
> System.Text.Json.JsonSerializer.Deserialize(String json, Type
> returnType, JsonSerializerOptions options)    at
> System.Text.Json.JsonSerializer.Deserialize[TValue](String json,
> JsonSerializerOptions options)    at
> TestJsonError.Program.Main(String[] args) in
> C:\Users\simon\source\repos\TestJsonError\TestJsonError\Program.cs:line
> 14
> 
>   This exception was originally thrown at this call stack:
>     [External Code]
>     TestJsonError.Program.Main(string[]) in Program.cs

有什么建议吗?


编辑...

我有点不好意思在下面对 DavidG 的优雅回应提出这种替代方法,但为了完整起见,我想我会分享。警告...它很脏,但矛盾的是有点优雅。

似乎我正在调用的 API,当它没有数据到 return 时,它会发回一个空字符串 ("")。当这种情况出现时,我在其他 APIs 中观察到 API returns null.

如果我将 "Websites": "" 替换为 "Websites": null,然后再替换为 JsonSerializer.Deserialize,效果会很好。我应该使用这种方法吗?好吧,我知道它很脏,但是因为我不得不在 Json 类 中添加这么多行来满足这种情况,实际上代码似乎更容易理解。我预计会有性能开销,但我不处理大量数据,所以在我的情况下可能没问题。

乐于接受不同意见。

您可以使用 custom converter 来做到这一点。例如:

public class WebsitesConverter : JsonConverter<WebsitesResponse>
{
    public override WebsitesResponse Read(ref Utf8JsonReader reader, Type typeToConvert, 
        JsonSerializerOptions options)
    {
        if(reader.TokenType == JsonTokenType.String)
        {
            // You can either return this, or a null object if you prefer
            return new WebsitesResponse
            {
                ContactWebsite = new ContactWebsiteResponse
                {
                    url = reader.GetString()
                }
            };
        }
        
        return JsonSerializer.Deserialize<WebsitesResponse>(ref reader);            
    }

    public override void Write(Utf8JsonWriter writer, WebsitesResponse value, 
        JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }
}

并更改您的型号以注册转换器:

public class ContactResponse
{
    public string contactID { get; set; }
    public string custom { get; set; }
    public string noEmail { get; set; }
    public string noPhone { get; set; }
    public string noMail { get; set; }
    [JsonConverter(typeof(WebsitesConverter))]
    public WebsitesResponse Websites { get; set; }
    public DateTime timeStamp { get; set; }
}

奖金!

如果你想让它更通用一点,这行得通:

public class StringObjectConverter<T> : JsonConverter<T> where T : class
{
    public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if(reader.TokenType == JsonTokenType.String)
        {
            return null;
        }
        
        return JsonSerializer.Deserialize<T>(ref reader);
        
    }

    public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }
}

您的模型将更改为:

[JsonConverter(typeof(StringObjectConverter<WebsitesResponse>))]
public WebsitesResponse Websites { get; set; }