xyncro / chiron

JSON for F#
https://xyncro.tech/chiron
MIT License
173 stars 41 forks source link

Flattening nested types #62

Closed jackmott closed 8 years ago

jackmott commented 8 years ago

I would like to 'flatten' the json output of a nested type when serializing to json. To illustrate what I mean, below is the the logic I would like to express:


type EffortData = 
    {
        lap          : int       
        distance : distance 
        splits       : SplitTracker []
    }
    static member ToJson (x:EffortData) =
        Json.write "lap" x.lap
     *> Json.write "distance" (x.distance.ToString())
     for i in 0 .. x.splits.Length do
           *> Json.write x.splits.[i].name x.splits.[i].duration

How can I accomplish this?

kolektiv commented 8 years ago

Hi @jackmott - this is a little bit fiddly, as the Json functions are stateful (state monads, basically). Therefore to "map" over a collection like that you'd actually need to fold, passing the Json state along as you went. For the simple case of doing this, you could have a function like this, which would act as a generic fold, and apply more Json functions to the current state:

[<RequireQualifiedAccess>]
module Json =

    let fold (f: _ -> Json<_>) xs : Json<unit> =
         fun json ->
            Value (), List.fold (fun json x ->
                snd ((f x) json)) json xs

We're throwing away return values of Json<_> functions as we're assuming that our functions succeed (a safe enough bet for this kind of thing). Now in your case you could use this Json.fold function where your for currently is - like this:

type EffortData = 
    { lap: int       
      distance: distance 
      splits: SplitTracker [] }

    static member ToJson (x:EffortData) =
            Json.write "lap" x.lap
         *> Json.write "distance" (x.distance.ToString())
         *> Json.fold (fun x -> Json.write x.name x.duration) x.splits

That should (if I've understood, sorry if I haven't!) give you the result you're looking for I think. Hope this helps!

jackmott commented 8 years ago

Spectacular, thank you.

kolektiv commented 8 years ago

No problem :smile: I'll close this for now - feel free to comment again if you need anything and I'll re-open!

jackmott commented 8 years ago

So as written fold is expecting a Json<'a> [] but is being given a SplitTracker [], probably something simple to resolve if I understood the fold function better, but I am still learning F#.

kolektiv commented 8 years ago

Oh, hmmm. Seemed to work for me with a faked test, is it not working for you? If you want to paste some code in somewhere (maybe a gist) I can take a look.

jackmott commented 8 years ago

I figured it out, type inference was picking up on a different type with some of the same names. Annotating the type fixed it up. Thank you!

kolektiv commented 8 years ago

Cool :smile: From the type names, sounds like you're working on something fun...