使用 C# 9 的records作为强类型ID - JSON序列化
使用 C# 9 的records作為強(qiáng)類型ID - 路由和查詢參數(shù)
在本系列的上一篇文章中使用 C# 9 的records作為強(qiáng)類型ID - 路由和查詢參數(shù),我們注意到強(qiáng)類型ID的實(shí)體,序列化為 JSON 的時候報錯了,就像這樣:
{"id": {"value": 1},"name": "Apple","unitPrice": 0.8 }不過想了一下,這樣的意外也是在意料之中的,強(qiáng)類型ID是record類型,而不是原始類型,因此將其序列化為一個對象是有意義的,但這顯然不是我們想要的……讓我們看看如何解決這個問題。
System.Text.Json
在最新版本的ASP.NET Core(從3.0)中,默認(rèn)的JSON序列化程序是System.Text.Json,因此讓我首先介紹這種。
為了將強(qiáng)類型的id序列化為其值而不是對象,我們需要編寫一個通用的 JsonConverter:
public class StronglyTypedIdJsonConverter<TStronglyTypedId, TValue> : JsonConverter<TStronglyTypedId>where TStronglyTypedId : StronglyTypedId<TValue>where TValue : notnull {public override TStronglyTypedId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options){if (reader.TokenType is JsonTokenType.Null)return null;var value = JsonSerializer.Deserialize<TValue>(ref reader, options);var factory = StronglyTypedIdHelper.GetFactory<TValue>(typeToConvert);return (TStronglyTypedId)factory(value);}public override void Write(Utf8JsonWriter writer, TStronglyTypedId value, JsonSerializerOptions options){if (value is null)writer.WriteNullValue();elseJsonSerializer.Serialize(writer, value.Value, options);} }邏輯很簡單,對于直接讀取 id.value, 對于反序列化,創(chuàng)建一個強(qiáng)類型id的實(shí)例,然后給它賦值。
然后在啟動類中配置:
services.AddControllers().AddJsonOptions(options =>{options.JsonSerializerOptions.Converters.Add(new StronglyTypedIdJsonConverter<ProductId, int>());});現(xiàn)在拿到了想要的結(jié)果:
{"id": 1,"name": "Apple","unitPrice": 0.8 }真好!不過,還有有一個問題:我們只為添加了一個對于ProductId的轉(zhuǎn)換器,但我不想為每種類型的強(qiáng)類型ID添加另一個轉(zhuǎn)換器!我們想要一個適用于所有強(qiáng)類型id的轉(zhuǎn)換器……,現(xiàn)在可以創(chuàng)建一個轉(zhuǎn)換器工廠(ConverterFactory),就像下邊這樣:
public class StronglyTypedIdJsonConverterFactory : JsonConverterFactory {private static readonly ConcurrentDictionary<Type, JsonConverter> Cache = new();public override bool CanConvert(Type typeToConvert){return StronglyTypedIdHelper.IsStronglyTypedId(typeToConvert);}public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options){return Cache.GetOrAdd(typeToConvert, CreateConverter);}private static JsonConverter CreateConverter(Type typeToConvert){if (!StronglyTypedIdHelper.IsStronglyTypedId(typeToConvert, out var valueType))throw new InvalidOperationException($"Cannot create converter for '{typeToConvert}'");var type = typeof(StronglyTypedIdJsonConverter<,>).MakeGenericType(typeToConvert, valueType);return (JsonConverter)Activator.CreateInstance(type);} }首先我們查看需要轉(zhuǎn)換的類型,檢查它是否實(shí)際上是強(qiáng)類型的id,然后為該類型創(chuàng)建特定轉(zhuǎn)換器的實(shí)例,我們添加了一些緩存,避免每次都進(jìn)行反射工作。
現(xiàn)在,我們沒有添加特定的JsonConvert,只是添加了一個Factory,然后在啟動文件修改,現(xiàn)在,我們的轉(zhuǎn)換器將應(yīng)用于每個強(qiáng)類型ID
services.AddControllers().AddJsonOptions(options =>{options.JsonSerializerOptions.Converters.Add(new StronglyTypedIdJsonConverterFactory());});Newtonsoft.Json
如果您的項(xiàng)目使用的是Newtonsoft.Json進(jìn)行JSON序列化,那就很簡單了。
當(dāng)它序列化一個值時,Newtonsoft.Json 查找一個compatible JsonConverter,如果找不到,就查找一個TypeConverter, 如果TypeConverter存在,并且可以將值轉(zhuǎn)換為string,那么它把值序列化為字符串, 因?yàn)槲覀冎岸x了 TypeConverter,Newtonsoft.Json查找到了,我得到以下結(jié)果:
{"id": "1","name": "Apple","unitPrice": 0.8 }幾乎是正確的……除了id值不應(yīng)序列化為字符串,而應(yīng)序列化為數(shù)字,如果id值是GUID或字符串而不是int,那就很好,則需要編寫一個自定義轉(zhuǎn)換器。
它和?System.Text.Json?的轉(zhuǎn)換器非常相似,不同之處在于Newtonsoft.Json沒有轉(zhuǎn)換器工廠(ConvertFactory)的概念,相反,我們將編寫一個非泛型轉(zhuǎn)換器:
public class StronglyTypedIdNewtonsoftJsonConverter : JsonConverter {private static readonly ConcurrentDictionary<Type, JsonConverter> Cache = new();public override bool CanConvert(Type objectType){return StronglyTypedIdHelper.IsStronglyTypedId(objectType);}public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer){var converter = GetConverter(objectType);return converter.ReadJson(reader, objectType, existingValue, serializer);}public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer){if (value is null){writer.WriteNull();}else{var converter = GetConverter(value.GetType());converter.WriteJson(writer, value, serializer);}}private static JsonConverter GetConverter(Type objectType){return Cache.GetOrAdd(objectType, CreateConverter);}private static JsonConverter CreateConverter(Type objectType){if (!StronglyTypedIdHelper.IsStronglyTypedId(objectType, out var valueType))throw new InvalidOperationException($"Cannot create converter for '{objectType}'");var type = typeof(StronglyTypedIdNewtonsoftJsonConverter<,>).MakeGenericType(objectType, valueType);return (JsonConverter)Activator.CreateInstance(type);} }public class StronglyTypedIdNewtonsoftJsonConverter<TStronglyTypedId, TValue> : JsonConverter<TStronglyTypedId>where TStronglyTypedId : StronglyTypedId<TValue>where TValue : notnull {public override TStronglyTypedId ReadJson(JsonReader reader, Type objectType, TStronglyTypedId existingValue, bool hasExistingValue, JsonSerializer serializer){if (reader.TokenType is JsonToken.Null)return null;var value = serializer.Deserialize<TValue>(reader);var factory = StronglyTypedIdHelper.GetFactory<TValue>(objectType);return (TStronglyTypedId)factory(value);}public override void WriteJson(JsonWriter writer, TStronglyTypedId value, JsonSerializer serializer){if (value is null)writer.WriteNull();elsewriter.WriteValue(value.Value);} }然后在啟動文件中這樣設(shè)置:
services.AddControllers().AddNewtonsoftJson(options =>{options.SerializerSettings.Converters.Add(new StronglyTypedIdNewtonsoftJsonConverter());});然后,我們得到了預(yù)期的結(jié)果,輸出的結(jié)果是這樣:
{"id": 1,"name": "Apple","unitPrice": 0.8 }原文作者: thomas levesque 原文鏈接:https://thomaslevesque.com/2020/12/07/csharp-9-records-as-strongly-typed-ids-part-3-json-serialization/
最后
歡迎掃碼關(guān)注我們的公眾號 【全球技術(shù)精選】,專注國外優(yōu)秀博客的翻譯和開源項(xiàng)目分享,也可以添加QQ群 897216102
總結(jié)
以上是生活随笔為你收集整理的使用 C# 9 的records作为强类型ID - JSON序列化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 在 C# 中生成代码的四种方式——包括.
 - 下一篇: 如何使用 C# 中的 ValueTupl