Skip to content

A basic elm-style json decoder in F# based on System.Text.Json

License

Notifications You must be signed in to change notification settings

SpacialCircumstances/Alve.Json

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

64 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Alve.Json

Nuget

A simple F# library for decoding and encoding JSON values, inspired by Elm's Json.Decode. It is based on the System.Text.Json APIs.

Installation

Via NuGet

Usage

Assume we have this JSON (adapted from https://json.org/example.html):

{
  "menu": {
    "header": "SVG Viewer",
    "items": [
      { "id": "Open" },
      {
        "id": "OpenNew",
        "label": "Open New"
      },
      null,
      {
        "id": "ZoomIn",
        "label": "Zoom In"
      },
      {
        "id": "ZoomOut",
        "label": "Zoom Out"
      },
      {
        "id": "OriginalView",
        "label": "Original View"
      },
      null,
      { "id": "Quality" },
      { "id": "Pause" },
      { "id": "Mute" },
      null,
      { "id": "Help" },
      {
        "id": "About",
        "label": "About Adobe CVG Viewer..."
      }
    ]
  }
}

And these F# types:

type Item = {
    id: string
    label: string option
}

type MenuItem =
    | Item of Item
    | Separator

type Menu = {
    header: string
    items: MenuItem list
}

type Config = {
    menu: Menu
}

Then we can write datatype decoders in the following way:

open Alve.Json.Decode

let itemDecoder = map2 (fun id label -> { id = id; label = label }) (field "id" jstring) (field "label" jstring |> optional)
let menuItemDecoder = map1 (fun item -> match item with
                                            | None -> Separator
                                            | Some item -> Item item) (nullable itemDecoder)
let menuItemListDecoder = jlist menuItemDecoder
let menuDecoder = map2 (fun header items -> { header = header; items = items }) (field "header" jstring) (field"items" menuItemListDecoder)
let configDecoder = map1 (fun menu -> { menu = menu }) (field "menu" menuDecoder)

//Now, decode our JSON:

printfn "%A" (decodeString configDecoder json)

Alternatively, we can use the jsonDecode computation expression to simplify the mapping operation:

open Alve.Json.Decode

let itemDecoder = jsonDecode {
    let! id = field "id" jstring
    let! label = optional (field "label" jstring)
    return {
        id = id
        label = label
    }
}
let menuItemDecoder = jsonDecode {
    let! item = nullable itemDecoder
    return match item with
            | None -> Separator
            | Some item -> Item item
}
let menuDecoder = jsonDecode {
    let itemsDecoder = jlist menuItemDecoder
    let! items = (field "items" itemsDecoder)
    let! header = (field "header" jstring)
    return {
        items = items
        header = header
    }
}
let configDecoder = jsonDecode {
    let! menu = field "menu" menuDecoder
    return {
        menu = menu
    }
}

//Now, decode our JSON:

printfn "%A" (decodeString configDecoder json)

Performance

Alve.Json requires the entire JSON to be present in memory for decoding. Therefore, it is unsuited for processing enormous amounts of data. Also, the functional programming style (bind, apply etc.) is not very good for performance and may generate some garbage, so do not use this in performance-sensitive applications.

License

Alve.Json is licensed under the MIT License.