You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将缺失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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:51:18