# 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
	```