正确的 JsonObjCodec with Fleece 用于复杂的嵌套区分联合



我有以下DU由其他DU或/和记录组成。

type BiometricRules =
| Age of Comparator * AgeMeasure
| Glycemia of Comparator * BiometricMeasure
| Biometric of BiometricType * Comparator * BiometricMeasure
| Sex of SexMeasure
| MedicalCondition of MedicalCondition
| Score of ScoreType * Comparator * ScoreMeasure

在尝试使用 Fleece 反序列化和序列化时,我写了以下JsonObjCodec

with
static member JsonObjCodec =
Age <!> jreq "Age" (function Age (comp, ageMeasure) -> Some (comp |> string, ageMeasure |> string) | _ -> None)
<|> (Glycemia <!> jreq "Glycemia" (function Glycemia (comp, bioMeasure) -> Some (comp |> string, bioMeasure) | _ -> None))
<|> (Biometric <!> jreq "BiometricRule" (function Biometric (bt, comp, bm) -> Some (bt |> string, comp |> string, bm) | _ -> None))
<|> (Sex <!> jreq "Sex" (function Sex s -> Some (s |> string) | _ -> None))
<|> (BiometricRules.MedicalCondition <!> jreq "MedicalCondition" (function BiometricRules.MedicalCondition x -> Some (x) | _ -> None))
<|> (Score <!> jreq "Score" (function Score (st, comp, scoreMeasure) -> Some (st |> string, comp |> string, scoreMeasure) | _ -> None))

由于未知原因,它不会编译错误方法"Map"没有重载匹配。所有嵌套的 DU 或记录都定义了 JsonObjCodec 或静态 FromString 和 ToString 方法。

关于我如何通过羊毛解决这个问题的任何解决方案将不胜感激。该库已经在项目中被大量使用,因此更改它将涉及太多的重构。

下面我复制粘贴了其他 DU 和记录的定义,作为参考:

<小时 />
type Comparator =
| GreaterThan
| LowerThan
| LowerThanOrEqual
| GreaterThanOrEqual
| EqualTo
with
override this.ToString() =
match this with
| GreaterThan -> ">"
| LowerThan -> "<"
| LowerThanOrEqual -> "<="
| GreaterThanOrEqual -> ">="
| EqualTo -> "="   
static member FromString s =
match s with
| ">" -> GreaterThan
| "<" -> LowerThan
| ">=" -> GreaterThanOrEqual
| "<=" -> LowerThanOrEqual
| "=" -> EqualTo
| _ -> failwith "Not a valid comparator."

type AgeMeasure =
| Years of decimal
| Months of decimal
| Weeks of decimal
with
override this.ToString() =
match this with
| Years y -> string y + " years"
| Months m -> string m + " months"
| Weeks w -> string w + " weeks"
static member FromString (s: string) =
match s with
| _ when s.EndsWith("years") -> Years (Decimal.Parse(s.Replace("years", "")))
| _ when s.EndsWith("months") -> Months (Decimal.Parse(s.Replace("months", "")))
| _ when s.EndsWith("weeks") -> Weeks (Decimal.Parse(s.Replace("weeks", "")))

type BiometricMeasure = {
Value: decimal
UoM: string option
} with
static member JsonObjCodec =
fun va uom -> {
Value = va
UoM = if uom = "NA" then None else Some uom
}
<!> jreq "Value" (Some << fun bm -> bm.Value)
<*> jreq "UoM" (Some << fun bm -> if bm.UoM |> Option.isNone then "NA" else bm.UoM |> Option.get)

type BiometricType =
| SBP
| DBP
| Glycemia
| Specified of string
with
override this.ToString() =
match this with
| SBP -> "SBP"
| DBP -> "DBP"
| Glycemia -> "Glycemia"
| Specified s -> s
static member FromString s =
match s with
| "SBP" -> SBP
| "DBP" -> DBP
| "Glycemia" -> Glycemia
| _ -> Specified s  

type SexMeasure =
| Female
| Male
| Other of string
with
override this.ToString() =
match this with
| Female -> "Female"
| Male -> "Male"
| Other s -> s
static member FromString (s: string) =
match s.ToLower() with
| "Female" -> Female
| "Male" -> Male
| other -> Other other   

type MedicalCondition =
| ICD of ICD
| Other of string
with
static member JsonObjCodec =
ICD <!> jreq "MedicalCondition" (function ICD v -> Some v | _ -> None)            
<|> (Other <!> jreq "MedicalCondition" (function Other v -> Some v | _ -> None))

type ScoreType =
| BMI
| Other of string 
with
override this.ToString() =
match this with
| BMI -> "BMI"
| Other s -> s
static member FromString s =
match s with
| "BMI" -> BMI
| _ -> Other s  

type ScoreMeasure = decimal

使用的库:

<PackageReference Update="FSharp.Core" Version="4.7" />
<PackageReference Include="FSharpPlus" Version="1.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Fleece.NewtonsoftJson" Version="0.8.0" />
<PackageReference Include="FSharp.Data" Version="3.3.3" />

问题

Fleece 提供 Json 编解码器,而不是字符串编解码器,因此定义ToStringFromString不是要走的路,除非你需要它们来处理其他事情。

解决方案

定义内部 DU 的ToJsonOfJson。然后去除JsonObjCodec体内的所有|> string碎片。

这是一个快速而肮脏的示例(我建议改进错误处理)Comparator

static member ToJson x = JString (string x)
static member OfJson x =
match x with
| JString x -> Ok (Comparator.FromString x)
| _ -> Error (Uncategorized "JString expected")

替代解决方案

保留所有内部 DU 如下,但在JsonObjCodec中添加缺少的"parse"部分:

...
with
static member JsonObjCodec =
(fun (a, b) -> Age (Comparator.FromString a, AgeMeasure.FromString b)) <!> jreq "Age" (function Age (comp, ageMeasure) -> Some (comp |> string, ageMeasure |> string) | _ -> None)
<|> ...

这变得有点冗长,但可以完成工作。

技巧

  • 与其使用<|>运算符添加编解码器,不如使用jchoice组合器,它会读得更好。

  • 如果您确实需要String/FromString方法,我建议将FromString重命名为Parse或将其重命名为TryParse并返回选项类型。这样您就可以利用FSharpPlustryParse功能。

  • 此外,如果您在任何地方都使用字符串/解析模式,则可能值得创建一个编解码器组合器,该组合器可以从字符串转换为/从字符串转换。这不是一件容易的事,但可能值得付出脑力劳动。

  • 对于像这样的调试,尽量不要打开命名空间FSharpPlus因为它包含运算符的通用定义,如<|><!><*>,这样你会得到更好的编译错误消息。

最新更新