daniellittledev / FSharp.Explicit.Json

An explicit JSON serialisation library based on System.Text.Json that uses explicit mappings to parse JSON
https://daniellittledev.github.io/FSharp.Explicit.Json/
MIT License
10 stars 1 forks source link

How about using overrides of custom operations in the render CE ? #2

Open jpeg729 opened 2 years ago

jpeg729 commented 2 years ago

A small inconsistency...

The example for parsing uses Parse.xxx uniformly and doesn't need the user to open FSharp.Explicit.Json.Parse. The example for rendering uses object directly so you would need to open FSharp.Explicit.Json.Render, but if you do that then you hide the usual int, long and so on.

Given that you are targetting net6.0 you could use overrides of custom operations on the CE. Something like this would work...


type ObjectRenderer() =
    // ... all of your existing code

    // without this the custom operation receives `unit` as its first argument
    member inline _.Yield(_) = 
        Render(fun _ -> ())

    // override for int values
    [<CustomOperation("prop")>]
    member inline _.Prop([<InlineIfLambda>] previous: Render, name:string, value:int) =
        Render(fun writer ->
            previous.Invoke(writer)
            writer.WriteNumber(name, value)
        )

    // override for string values
    [<CustomOperation("prop")>]
    member inline _.Prop([<InlineIfLambda>] previous: Render, name:string, value:string) =
        Render(fun writer ->
            previous.Invoke(writer)
            writer.WriteString(name, value)
        )

    // override for a Render function
    [<CustomOperation("prop")>]
    member inline _.Prop([<InlineIfLambda>] previous: Render, name:string, child:Render) =
        Render(fun writer ->
            previous.Invoke(writer)
            writer.WritePropertyName(name)
            child.Invoke(writer)
        )

    // ... many other overrides for all possible types of props

Then you can do this

use jsonWriter = new JsonWriter(IndentStyle.Indented)
let jsonRender =
    object {
        prop "alpha" (object {
            prop "beta" "1"
        })
        prop "gamma" "2"
        prop "something" 3
    }
let jsonText = jsonWriter.GetJsonString(jsonRender)

The CE code is longer, but the API is a little nicer.

jpeg729 commented 2 years ago

One could even add an escape hatch override, that just asks JsonSerializer to take over, but I am not sure you will want to go in that direction


    [<CustomOperation("prop")>]
    member inline _.Prop([<InlineIfLambda>] previous: Render, name:string, value:obj) =
        Render(fun writer ->
            previous.Invoke(writer)
            JsonSerializer.Serialize(writer, value)
        )