如何将缺失JSON值反序列化为Skippable<'a>的Skip分支
解决Skippable<'a>反序列化时缺失值转为Skip分支的问题
刚好我之前处理过类似的F# JSON反序列化场景,给你两个针对主流JSON库的解决方案,完美适配你的Skippable<'a>可区分联合需求:
方案1:使用Newtonsoft.Json(Json.NET)自定义转换器
Json.NET是F#生态中使用广泛的JSON库,我们可以通过自定义转换器来拦截反序列化逻辑,把缺失/null的JSON值映射到Skip分支:
1.1 实现泛型转换器
open Newtonsoft.Json open Newtonsoft.Json.Linq // 针对Skippable<'a>的泛型转换器 type SkippableConverter<'a>() = inherit JsonConverter<Skippable<'a>>() // 反序列化逻辑:缺失/null则返回Skip,否则包装为Serialize override __.ReadJson(reader, _, _, _, serializer) = match reader.TokenType with | JsonToken.Null | JsonToken.Undefined -> Skip | _ -> let innerValue = serializer.Deserialize<'a>(reader) Serialize innerValue // 序列化逻辑(保留原有行为) override __.WriteJson(writer, value, serializer) = match value with | Skip -> writer.WriteNull() | Serialize v -> serializer.Serialize(writer, v)
1.2 应用转换器
你有两种方式应用这个转换器:
- 单个成员标注:在类的
Skippable类型成员上添加属性:type UserProfile() = [<JsonConverter(typeof<SkippableConverter<int>>)>] member val Age = Skip with get, set [<JsonConverter(typeof<SkippableConverter<string>>)>] member val Nickname = Skip with get, set - 全局注册:如果项目中有大量
Skippable类型成员,全局注册转换器可避免重复标注:let jsonSettings = JsonSerializerSettings() // 为需要的泛型参数注册转换器 jsonSettings.Converters.Add(SkippableConverter<int>()) jsonSettings.Converters.Add(SkippableConverter<string>()) // 反序列化示例 let json = """{"Age": 28}""" let profile = JsonConvert.DeserializeObject<UserProfile>(json, jsonSettings) // profile.Nickname 会是 Skip
1.3 进阶:通用非泛型转换器
如果不想为每个泛型参数单独注册转换器,可以写一个非泛型版本,自动处理所有Skippable<'a>类型:
type SkippableConverter() = inherit JsonConverter() override __.CanConvert(objectType) = objectType.IsGenericType && objectType.GetGenericTypeDefinition() = typedefof<Skippable<_>> override __.ReadJson(reader, objectType, _, serializer) = let genericArg = objectType.GetGenericArguments().[0] match reader.TokenType with | JsonToken.Null | JsonToken.Undefined -> // 获取Skip分支的实例 typedefof<Skippable<_>>.GetProperty("Skip").GetValue(null, [||]) | _ -> // 反序列化内部值并包装为Serialize let innerValue = serializer.Deserialize(reader, genericArg) typedefof<Skippable<_>>.GetMethod("Serialize").MakeGenericMethod(genericArg).Invoke(null, [|innerValue|]) override __.WriteJson(writer, value, serializer) = let duType = value.GetType() let (unionCase, fields) = Microsoft.FSharp.Reflection.FSharpValue.GetUnionFields(value, duType) if unionCase.Name = "Serialize" then serializer.Serialize(writer, fields.[0]) else writer.WriteNull()
全局注册这个转换器后,所有Skippable<'a>类型都会自动应用这个逻辑。
方案2:使用System.Text.Json自定义转换器
如果你用的是.NET Core/.NET 5+内置的System.Text.Json,同样可以通过自定义转换器实现需求:
2.1 实现泛型转换器
open System.Text.Json open System.Text.Json.Serialization type SkippableConverter<'a>() = inherit JsonConverter<Skippable<'a>>() override __.Read(reader, _, options) = match reader.TokenType with | JsonTokenType.Null | JsonTokenType.Undefined -> Skip | _ -> let innerValue = JsonSerializer.Deserialize<'a>(reader, options) Serialize innerValue override __.Write(writer, value, options) = match value with | Skip -> writer.WriteNullValue() | Serialize v -> JsonSerializer.Serialize(writer, v, options)
2.2 应用转换器
同样支持两种方式:
- 单个成员标注:
type UserProfile() = [<JsonConverter(typeof<SkippableConverter<int>>)>] member val Age = Skip with get, set [<JsonConverter(typeof<SkippableConverter<string>>)>] member val Nickname = Skip with get, set - 全局注册:
let jsonOptions = JsonSerializerOptions() jsonOptions.Converters.Add(SkippableConverter<int>()) jsonOptions.Converters.Add(SkippableConverter<string>()) // 反序列化示例 let json = """{"Nickname": "FSharpDev"}""" let profile = JsonSerializer.Deserialize<UserProfile>(json, jsonOptions) // profile.Age 会是 Skip
为什么不用DefaultValueAttribute?
你提到的DefaultValueAttribute确实只支持常量值,而Skippable<'a>.Skip是泛型依赖的实例(不同泛型参数的Skip是不同的实例),无法作为编译时常量传递给属性,所以自定义转换器是最靠谱的解决方案。
内容的提问来源于stack exchange,提问作者cmeeren
相关产品推荐
相关产品推荐

