微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何在 .net core 3.0 中使用 System.Text.Json 反序列化部分 json?

如何解决如何在 .net core 3.0 中使用 System.Text.Json 反序列化部分 json?

我有一个来自 https://api.nasa.gov/insight_weather/?api_key=DEMO_KEY&feedtype=json&ver=1.0 的 json,它看起来像:

{
  "782": {
    "First_UTC": "2021-02-06T17:08:11Z","Last_UTC": "2021-02-07T17:47:46Z","Month_ordinal": 12,"northern_season": "late winter","PRE": {
      "av": 721.77,"ct": 113450,"mn": 698.8193,"mx": 742.2686
    },"Season": "winter","Southern_season": "late summer","WD": {
      "most_common": null
    }
  },"783": {
    "First_UTC": "2021-02-07T17:47:46Z","Last_UTC": "2021-02-08T18:27:22Z","PRE": {
      "av": 722.186,"ct": 107270,"mn": 698.7664,"mx": 743.1983
    },"sol_keys": [ "782","783" ],"validity_checks": { /* Some complex object */ }
}

我只需要部分信息,所以我创建了以下类:

public class MarsWheather {
    [JsonPropertyName("First_UTC")]
    public DateTime FirstUTC { get; set; }
    [JsonPropertyName("Last_UTC")]
    public DateTime LastUTC { get; set; }
    [JsonPropertyName("Season")]
    [JsonConverter(typeof(JsonStringEnumConverter))]
    public Season MaRSSeason { get; set; }
    [JsonPropertyName("PRE")]
    public DataDescription AtmosphericPressure { get; set; }
}

public enum Season {
    winter,spring,summer,autumn
}

public class DataDescription{
    [JsonPropertyName("av")]
    public double Average { get; set; }
    [JsonPropertyName("ct")]
    public double TotalCount { get; set; }
    [JsonPropertyName("mn")]
    public double Minimum { get; set; }
    [JsonPropertyName("mx")]
    public double Maximum { get; set; }
} 

问题是来自 NASA 的 JSON 根对象包含我不需要并想跳过的属性 "validity_checks""sol_keys"。在 Newton.Json 中,我使用 JObject.Parse 来执行此操作,但在 System.Text.Json 中我想使用

JsonSerializer.DeserializeAsync<Dictionary<string,MarsWheather>>(stream,new JsonSerializerOptions { IgnoreNullValues = true });

不幸的是,当我这样做时,我得到了一个例外:

System.Text.Json.JsonException: The JSON value Could not be converted to MarsWheather. Path: $.sol_keys | LineNumber: 120 | BytePositionInLine: 15.

演示小提琴here

有可能吗?

解决方法

您的 JSON 根对象由某些固定键("sol_keys""validity_checks")组成,它们的值每个都有固定的模式,以及任意数量的可变键("782" 数字键)值都共享一个与固定键值的架构不同的公共架构:

{
  "782": {
    // Properties corresponding to your MarsWheather object
  },"783": {
    // Properties corresponding to your MarsWheather object
  },// Other variable numeric key/value pairs corresponding to KeyValuePair<string,MarsWheather>
  "sol_keys": [
    // Some array values you don't care about
  ],"validity_checks": {
    // Some object you don't care about
  }
}

您只想反序列化可变键,但是当您尝试反序列化为 Dictionary<string,MarsWheather> 时,您会得到一个异常,因为序列化程序试图反序列化固定键值,就好像它是可变键值一样——但是由于固定键具有数组值而可变键具有对象值,因此会引发异常。如何告诉 System.Text.Json 跳过已知的固定键而不是尝试反序列化它们?

如果您只想反序列化可变键并跳过固定的已知键,则需要创建一个 custom JsonConverter。最简单的方法是首先为您的字典创建一些根对象:

[JsonConverter(typeof(MarsWheatherRootObjectConverter))]
public class MarsWheatherRootObject
{
    public Dictionary<string,MarsWheather> MarsWheathers { get; } = new Dictionary<string,MarsWheather>();
}

然后为它定义如下转换器:

public class MarsWheatherRootObjectConverter : FixedAndvariablePropertyNameObjectConverter<MarsWheatherRootObject,Dictionary<string,MarsWheather>,MarsWheather>
{
    static readonly Dictionary<string,ReadFixedKeyMethod> FixedKeyReadMethods = new Dictionary<string,ReadFixedKeyMethod>(StringComparer.OrdinalIgnoreCase)
    {
        { "sol_keys",(ref Utf8JsonReader reader,MarsWheatherRootObject obj,string name,JsonSerializerOptions options) => reader.Skip() },{ "validity_checks",};

    protected override Dictionary<string,MarsWheather> GetDictionary(MarsWheatherRootObject obj) => obj.MarsWheathers;
    protected override void SetDictionary(MarsWheatherRootObject obj,MarsWheather> dictionary) => throw new RowNotInTableException();
    protected override bool TryGetFixedKeyReadMethod(string name,JsonSerializerOptions options,out ReadFixedKeyMethod method) => FixedKeyReadMethods.TryGetValue(name,out method);
    protected override IEnumerable<KeyValuePair<string,WriteFixedKeyMethod>> GetFixedKeyWriteMethods(JsonSerializerOptions options) => Enumerable.Empty<KeyValuePair<string,WriteFixedKeyMethod>>();
}

public abstract class FixedAndvariablePropertyNameObjectConverter<TObject,TDictionary,TValue> : JsonConverter<TObject> 
    where TDictionary : class,IDictionary<string,TValue>,new()
    where TObject : new()
{
    protected delegate void ReadFixedKeyMethod(ref Utf8JsonReader reader,TObject obj,JsonSerializerOptions options);
    protected delegate void WriteFixedKeyMethod(Utf8JsonWriter writer,TObject value,JsonSerializerOptions options);
        
    protected abstract TDictionary GetDictionary(TObject obj);
    protected abstract void SetDictionary(TObject obj,TDictionary dictionary);
    protected abstract bool TryGetFixedKeyReadMethod(string name,out ReadFixedKeyMethod method);
    protected abstract IEnumerable<KeyValuePair<string,WriteFixedKeyMethod>> GetFixedKeyWriteMethods(JsonSerializerOptions options);
        
    public override TObject Read(ref Utf8JsonReader reader,Type typeToConvert,JsonSerializerOptions options)
    {
        if (reader.TokenType == JsonTokenType.Null)
            return (typeToConvert.IsValueType && Nullable.GetUnderlyingType(typeToConvert) == null)
                ? throw new JsonException(string.Format("Unepected token {0}",reader.TokenType))
                : default(TObject);
        if (reader.TokenType != JsonTokenType.StartObject)
            throw new JsonException(string.Format("Unepected token {0}",reader.TokenType));
        var obj = new TObject();
        var dictionary = GetDictionary(obj);
        var valueConverter = (typeof(TValue) == typeof(object) ? null : (JsonConverter<TValue>)options.GetConverter(typeof(TValue))); // Encountered a bug using the builtin ObjectConverter
        while (reader.Read())
        {
            if (reader.TokenType == JsonTokenType.PropertyName)
            {
                var name = reader.GetString();
                reader.ReadAndAssert();
                if (TryGetFixedKeyReadMethod(name,options,out var method))
                {
                    method(ref reader,obj,name,options);
                }
                else
                {
                    if (dictionary == null)
                        SetDictionary(obj,dictionary = new TDictionary());
                    dictionary.Add(name,valueConverter.ReadOrDeserialize(ref reader,typeof(TValue),options));
                }
            }
            else if (reader.TokenType == JsonTokenType.EndObject)
            {
                return obj;
            }
            else
            {
                throw new JsonException(string.Format("Unepected token {0}",reader.TokenType));
            }
        }
        throw new JsonException(); // Truncated file
    }

    public override void Write(Utf8JsonWriter writer,JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        var dictionary = GetDictionary(value);
        if (dictionary != null)
        {
            var valueConverter = (typeof(TValue) == typeof(object) ? null : (JsonConverter<TValue>)options.GetConverter(typeof(TValue))); // Encountered a bug using the builtin ObjectConverter
            foreach (var pair in dictionary)
            {
                // TODO: handle DictionaryKeyPolicy 
                writer.WritePropertyName(pair.Key);
                valueConverter.WriteOrSerialize(writer,pair.Value,options);
            }
        }
        foreach (var pair in GetFixedKeyWriteMethods(options))
        {
            writer.WritePropertyName(pair.Key);
            pair.Value(writer,value,options);
        }
        writer.WriteEndObject();
    }
}

public static partial class JsonExtensions
{
    public static void WriteOrSerialize<T>(this JsonConverter<T> converter,Utf8JsonWriter writer,T value,Type type,JsonSerializerOptions options)
    {
        if (converter != null)
            converter.Write(writer,options);
        else
            JsonSerializer.Serialize(writer,type,options);
    }

    public static T ReadOrDeserialize<T>(this JsonConverter<T> converter,ref Utf8JsonReader reader,JsonSerializerOptions options)
        => converter != null ? converter.Read(ref reader,typeToConvert,options) : (T)JsonSerializer.Deserialize(ref reader,options);

    public static void ReadAndAssert(this ref Utf8JsonReader reader)
    {
        if (!reader.Read())
            throw new JsonException();
    }
}

现在您可以反序列化为 MarsWheatherRootObject,如下所示:

var root = await System.Text.Json.JsonSerializer.DeserializeAsync<MarsWheatherRootObject>(
    stream,new System.Text.Json.JsonSerializerOptions 
    { 
        PropertyNameCaseInsensitive = true 
    });

演示小提琴 #1 here

注意事项:

  • FixedAndvariablePropertyNameObjectConverter<TObject,TValue> 提供了一个用于序列化和反序列化具有固定和可变属性的对象的通用框架。如果稍后您决定反序列化,例如"sol_keys",您可以修改 MarsWheatherRootObject 如下:

    [JsonConverter(typeof(MarsWheatherRootObjectConverter))]
    public class MarsWheatherRootObject
    {
        public Dictionary<string,MarsWheather>();
        public List<string> SolKeys { get; set; } = new List<string>();
    }
    

    转换器如下:

    public class MarsWheatherRootObjectConverter : FixedAndvariablePropertyNameObjectConverter<MarsWheatherRootObject,ReadFixedKeyMethod> FixedKeyReadMethods = new(StringComparer.OrdinalIgnoreCase)
        {
            { "sol_keys",JsonSerializerOptions options) => 
                {
                    obj.SolKeys = JsonSerializer.Deserialize<List<string>>(ref reader,options);
                } 
            },};
        static readonly Dictionary<string,WriteFixedKeyMethod> FixedKeyWriteMethods = new Dictionary<string,WriteFixedKeyMethod>()
        {
            { "sol_keys",(w,v,o) => 
                {
                    JsonSerializer.Serialize(w,v.SolKeys,o);
                } 
            },WriteFixedKeyMethod>> GetFixedKeyWriteMethods(JsonSerializerOptions options) => FixedKeyWriteMethods;
    }
    

    演示小提琴 #2 here

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。