fsprojects / Fleece

Json mapper for F#
http://fsprojects.github.io/Fleece
Apache License 2.0
199 stars 31 forks source link

Ease way to add JsonObjCodec for types with string transformations #99

Open williamoanta opened 3 years ago

williamoanta commented 3 years ago

I have the following DU which is composed of other DUs or/and Records.

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

While trying to deserialize and serialize with Fleece, I have written the following JsonObjCodec.

For an unknown reason, it does not compile with the error No overloads match for method 'Map'. All the nested DUs or Records have either a JsonObjCodec or static FromString and ToString methods defined.

Any solution with respect to how I could solve this via Fleece would be appreciated. The library is already deeply used in the project, so changing it would involve too much refactoring.

Below I copy-pasted the definition of the other DU and Records, as reference:

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

Libraries Used in project:

    <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" />

Also posted on StackOverflow

gusty commented 3 years ago

I answered you in Stack Overflow, basically your JsonObjCodec does conversions to string but not parsing. I suggested some alternative solutions.

But since you opened an issue here I would like to take the opportunity to evaluate adding some utilities in the library to work with types that don't have json transformations but do have transformation to/from string.

It's an interesting scenario for complex things like composing codecs, and take precisely advantage of codec composability properties.

williamoanta commented 3 years ago

Thanks for your prompt response! I have also replied on StackOverflow. I would be curious to see what solution you come up with if any.