# Fleece Json Library
[Fleece](https://github.com/fsprojects/Fleece) is a json mapper for F#. The library uses either `System.Json`, `System.Text.Json`, `FSharp.Data`, or `Json.NET` as the underlining deserializer. There is a separate nuget package for each. You should prefer [Fleece.SystemTextJson](https://www.nuget.org/packages/Fleece.SystemTextJson/) (`#r "nuget:Fleece.SystemTextJson"`).
- Types are automatically serializable if they have two static members (`ToJson` and `OfJson`), or, in case of objects, a `get_Codec ()`)
- To do the parsing you should use `ofJsonText : string -> Result<'a, DecodeError>` and `toJsonText`.
Note: for some of these examples you may need to reference [[FSharpPlus]] (`#r "nuget: FSharpPlus, 1.2.*"`), and open `Fleece.SystemTextJson` and `Fleece.SystemTextJson.Operators`.
## Understanding Codecs
A `Codec<S1, S2, t1, t2` is a combination of two functions, a `Decoder<S1, t1>` and an `Encoder<S2, t2>`. The decoder defines a way of converting `S1` to `t1` and the encoder converts `t2` to `S2`.
Think of `S1` and `S2` as your raw or soft types (hence the 's'), such as `JsonValue` and of `t1` and `t2` as hard types.
A simpler `Codec<S,t>` also exists (with two generic parameters) and this is the same as `Codec<S,S,t,t>`.
The exact definitions of the encoder and decoders are:
```fsharp
type Decoder<S1, t1> = ReaderT<S1, ParseResult<t1>>
type Encoder<S2, t2> = t2 -> Const<S2, unit>
```
Notice that the encoder is nothing other than a read-only lens (see [[FSharpPlus Const|Const]]).
Given two functions `decode : S1 -> ParseResult<t1>` and `encode : t2 -> S2` you can construct a codec by using the `<->` operator like: `decode <-> encode`.
A `Codec<S1, S2, t1, t2` is a:
- [[FSharpPlus Functor]] that acts on `t1`
- [[FSharpPlus Applicative Functor]] that acts on `t1`
## Examples
### Codec Definition With `get_Codec ()`
```fsharp
open Fleece
type Person = {
name : string * string
age : int option
children: Person list }
with
static member get_Codec () =
fun f l a c -> { name = (f, l); age = a; children = c }
<!> jreq "firstName" (Some << fun x -> fst x.name)
<*> jreq "lastName" (Some << fun x -> snd x.name)
<*> jopt "age" (fun x -> x.age) // Optional fields can use 'jopt'
<*> jreq "children" (fun x -> Some x.children)
|> ofObjCodec
```
### Conversion Methods (`ToJson` / `OfJson`)
Note that these HAVE TO be defined as static methods, that is to say `static member OfJson = function` (without explicitly defining the argument) WILL NOT work.
```fsharp
type Person = {
Name: string
Age: int
Children: Person list
}
static member ToJson (x: Person) =
jobj [
"name" .= x.Name
"age" .= x.Age
"children" .= x.Children
]
static member OfJson json =
match json with
| JObject o ->
let name = o .@ "name"
let age = o .@ "age"
let children = o .@ "children"
match name, age, children with
| Decode.Success name, Decode.Success age, Decode.Success children ->
Decode.Success {
Person.Name = name
Age = age
Children = children
}
| x -> Error <| Uncategorized (sprintf "Error parsing person: %A" x)
| x -> Decode.Fail.objExpected x
```
`OfJson` can also be written monadically:
```fsharp
static member OfJson json =
match json with
| JObject o ->
Person.Create
<!> (o .@ "name")
<*> (o .@ "age")
<*> (o .@ "children")
| x -> Decode.Fail.objExpected x
```
Note that you probably need to open `FSharpPlus` here, together with `Fleece.Operators`.
For optional parameters you can use `.@?`.
```fsharp
static member OfJson json =
match json with
| JObject o ->
monad {
let! name = o .@ "name"
let! age = o .@ "age"
let! children = o .@ "children"
return {
Person.Name = name
Age = age
Children = children
}
}
| x -> Decode.Fail.objExpected x
```
### Custom Codecs
```fsharp
let colorDecoder = function
| JString "red" -> Decode.Success Red
| JString "blue" -> Decode.Success Blue
| JString "white" -> Decode.Success White
| JString x as v -> Decode.Fail.invalidValue v ("Wrong color: " + x)
| x -> Decode.Fail.strExpected x
let colorEncoder = function
| Red -> JString "red"
| Blue -> JString "blue"
| White -> JString "white"
let colorCodec = colorDecoder <-> colorEncoder
```
### API Parsing
Note this example is valid for Fleece v0.9 and below.
```fsharp
let localDateDecoder = function
| JString dateRaw ->
let result = LocalDatePattern.Iso.Parse dateRaw
if result.Success then Ok result.Value
else Decode.Fail.parseError result.Exception dateRaw
| x -> Decode.Fail.strExpected x
type Holiday =
{ Date : LocalDate }
static member OfJson json =
match json with
| JObject o ->
fun d -> { Date = d }
<!> (jgetWith localDateDecoder o "date")
| x -> Decode.Fail.objExpected x
type Holidays =
{ Holidays : Holiday list }
static member OfJson json =
match json with
| JObject o ->
fun hs -> { Holidays = hs }
<!> (o .@ "feiertage")
| x -> Decode.Fail.objExpected x
```
Or with codecs
```fsharp
let localDateCodec =
let ofJson = function
| JString dateRaw ->
let result = LocalDatePattern.Iso.Parse dateRaw
if result.Success then Ok result.Value
else Decode.Fail.parseError result.Exception dateRaw
| x -> Decode.Fail.strExpected x
let toJson x = LocalDatePattern.Iso.Format x |> JString
ofJson, toJson
type Holiday =
{ Date : LocalDate }
static member JsonObjCodec =
fun d -> { Date = d }
|> withFields
|> jfieldWith localDateCodec "date" (fun x -> x.Date)
type Holidays =
{ Holidays : Holiday list }
static member JsonObjCodec =
fun hs -> { Holidays = hs }
|> withFields
|> jfield "feiertage" (fun x -> x.Holidays)
```
### Applying Two Codec on Same Object Source
```fsharp
type Item = {
ItemInfo : ItemInfo
Fields : Field list
Files : File list
} with
static member get_Codec () =
let itemInfo : Codec<PropertyList<'a>,PropertyList<'a>,ItemInfo,Item> =
let c = ItemInfo.get_ObjCodec ()
Codec.decode c <-> (fun { ItemInfo = x } -> Codec.encode c x)
fun fields files info -> {
ItemInfo = info
Fields = (fields |> Option.defaultValue [])
Files = (files |> Option.defaultValue [])
}
<!> jopt "fields" (fun { Fields = fs } -> Some fs)
<*> jopt "files" (fun { Files = fs } -> Some fs)
<*> itemInfo
|> ofObjCodec
```
### Parsing Optional NodaTime Instants in Fleece 0.10+
Both of these examples rely on a codec to parse `DateTimeOffset`. Note that the default `DateTimeOffset` codec uses exact parsing:
```fsharp
static member dateTimeOffsetD x =
match x with
| JString null -> Decode.Fail.nullString
| JString s ->
match DateTimeOffset.TryParseExact (s, [| "yyyy-MM-ddTHH:mm:ss.fffK"; "yyyy-MM-ddTHH:mm:ssK" |], null, DateTimeStyles.RoundtripKind) with
| true, t -> Ok t
| _ -> Decode.Fail.invalidValue (Encoding x) ""
| a -> Decode.Fail.strExpected (Encoding a)
```
Solution is to use a more tolerant `Instant` codec like so:
```fsharp
let instantCodec =
let decode (x : Encoding) =
match x with
| JString null -> Decode.Fail.nullString
| JString s ->
match DateTimeOffset.TryParse(s) with
| true, t -> Ok (Instant.FromDateTimeOffset(t))
| _ -> Decode.Fail.invalidValue x ""
| a -> Decode.Fail.strExpected x
let encode (x : Instant) = x.ToDateTimeOffset() |> toJson
decode <-> encode
```
```fsharp
type VaultInfo = {
Id : VaultId
Title : VaultTitle
Version : VaultVersion
CreatedAt : CreatedAt
UpdatedAt : UpdatedAt
ItemCount : ItemCount
} with
static member get_Codec () =
let instantCodec =
let decode = ofJson >> Result.map Instant.FromDateTimeOffset
let encode (x : Instant) = x.ToDateTimeOffset() |> toJson
decode <-> encode
let vaultId =
let c = VaultId.get_ObjCodec ()
Codec.decode c <-> (fun { Id = x } -> Codec.encode c x)
fun i t v c u ic -> {
Id = i
Title = (VaultTitle t)
Version = VaultVersion (v |> Option.defaultValue 0)
CreatedAt = CreatedAt (c |> Option.defaultValue Instant.MinValue)
UpdatedAt = UpdatedAt (u |> Option.defaultValue Instant.MinValue)
ItemCount = ItemCount (ic |> Option.defaultValue 0)
}
<!> vaultId
<*> jreq "name" (fun { Title = (VaultTitle t) } -> Some t)
<*> jopt "contentVersion" (fun { Version = VaultVersion v } -> Some v)
<*> joptWith (Codecs.option instantCodec) "createdAt" (fun { CreatedAt = CreatedAt c } -> Some c)
<*> joptWith (Codecs.option instantCodec) "updatedAt" (fun { UpdatedAt = UpdatedAt u } -> Some u)
<*> jopt "items" (fun { ItemCount = ItemCount ic } -> Some ic)
|> ofObjCodec
```
Simpler example:
```fsharp
type Snapshot = {
Id : string
Host : string
Paths : string list
Time : Instant
} with
static member get_Codec () =
let instantCodec =
let decode = ofJson >> Result.map Instant.FromDateTimeOffset
let encode (x : Instant) = x.ToDateTimeOffset() |> toJson
decode <-> encode
fun i h p t -> {
Id = i
Host = h
Paths = p
Time = t
}
<!> jreq "short_id" (fun { Snapshot.Id = i } -> Some i)
<*> jreq "hostname" (fun { Snapshot.Host = h } -> Some h)
<*> jreq "paths" (fun { Snapshot.Paths = p } -> Some p)
<*> jreqWith instantCodec "time" (fun { Snapshot.Time = t } -> Some t)
|> ofObjCodec
```
### Serializing With Indentation
```fsharp
JsonSerializer.Serialize(
toJsonValue defaultSettings,
JsonSerializerOptions(WriteIndented = true))
```
## Differences Between 0.10 and 0.9
- Instead of `static member get_Codec ()`, 0.9 used `static member JsonObjCodec`
- The definition of a codec has changed (type parameters), so defining a codec via custom encoder and decoder functions used to be done like so
```fsharp
let localDateCodec =
let toJson x = LocalDatePattern.Iso.Format x |> JString
let ofJson = function
| JString dateRaw ->
let result = LocalDatePattern.Iso.Parse dateRaw
if result.Success then Ok result.Value
else Decode.Fail.parseError result.Exception dateRaw
| x -> Decode.Fail.strExpected x
ofJson, toJson
```