Fleece


#r "nuget: Fleece.NewtonsoftJson"
open System
open Newtonsoft.Json
open FSharpPlus
open Fleece.Newtonsoft
open Fleece.Newtonsoft.Operators

Comparison with Json.Net or Newtonsoft Json

In order to be compatible with Newtonsoft Json conventions you need to either specify a constructor or have a default constructor with the same name as the public field (note the different first letter casing).

type User(userName:string , enabled: bool)=
    member __.UserName = userName
    member __.Enabled = enabled
let userJson="""
{"userName":"test","enabled":true}
"""
let user = JsonConvert.DeserializeObject<User> userJson

Another alternative would be to use CLI-mutable

[<CLIMutable>]
type UserR ={ UserName:string; Enabled:bool }

This enables Json.Net to deserialize json into your structure but leave the F# code easier to reason about.

let userRecord = JsonConvert.DeserializeObject<UserR> userJson

Controlling polymorphism

The default approach is to use serialization binder. The assumption is that you have an abstract class or an interface that implemented by many different types.

In order to have a better serialization of union cases you need to implement something as seen in FsCodec.NewtonsoftJson/UnionConverter.

Since UnionConverter does not map well to F# concepts you might end up with a similar pattern as seen in Fleece. For instance if you read Eirik Tsarpalis blog.

Fleece lets you decode the Json at both a lower and hight level. This allows you also to mix and match with the native Json library (in this case Newtonsoft.Json):

[<CLIMutable>]
type CarInfo = { Make:string; Model:string; Trim:string}
type Vehicle =
   | Bike
   | Car       of CarInfo
with
    static member OfJson (json:Linq.JToken) =
        match json with
        | JObject o ->
            monad.strict {
                match! o .@ "type" with
                | "Bike" -> return Bike
                | "Car" ->
                    // we know that json token is a JObject due to the check above so we can directly cast it:
                    let jobj : Linq.JObject = downcast json
                    try
                        // now we can use the default Newtonsoft Json decoder:
                        let info = jobj.ToObject<CarInfo>() // NOTE: here we hand over control of the mapping to Newtonsoft.Json
                        return Car info
                    with
                    | e-> return! Decode.Fail.parseError e "Could not parse CarInfo"
                | x -> return! Uncategorized (sprintf "Unexpected type name %s" x) |> Error
            }
        | x -> Decode.Fail.objExpected x

This pattern is ugly but can be useful. Modifying the type CarInfo above will give you runtime exceptions without a clear indication that it's a broken contract.

One of the useful things about having a mixed approach as seen above is that you can gradually convert to say Fleece in a large codebase without having to fix everything at once.

Full control over mapping

The default approach to serialization and deserialization in Fleece let you have a lot of control. You choose exactly how it should work.

It's easy to let the structure of your Json be completely independent of the structure of your data. Newtonsoft assumes that what you want follow a lot of conventions.

If we look at a simple example of the Json not matching the representation (where you would need a custom JsonConverter):

type Person = {
    Name : string * string
}
with
    static member ToJson (x: Person) =
        jobj [
            "firstname" .= fst x.Name
            "lastname" .= snd x.Name
        ]
    static member OfJson json =
        match json with
        | JObject o ->
            let firstname = jget o "firstname"
            let lastname = jget o "lastname"
            match firstname, lastname with
            | Decode.Success firstname, Decode.Success lastname ->
                Decode.Success {
                    Person.Name = (firstname,lastname)
                }
            | x -> Error <| Uncategorized (sprintf "Error parsing person: %A" x)
        | x -> Decode.Fail.objExpected x

In that sense, having access to functions helps us make what in Newtonsoft is a pain to implement, very easy.

namespace System
namespace Newtonsoft
namespace Newtonsoft.Json
namespace FSharpPlus
namespace Fleece
module Newtonsoft from Fleece
module Operators from Fleece.Newtonsoft
Multiple items
type User = new : userName:string * enabled:bool -> User member Enabled : bool member UserName : string

--------------------
new : userName:string * enabled:bool -> User
val userName : string
Multiple items
val string : value:'T -> string
<summary>Converts the argument to a string using <c>ToString</c>.</summary>
<remarks>For standard integer and floating point values the and any type that implements <c>IFormattable</c><c>ToString</c> conversion uses <c>CultureInfo.InvariantCulture</c>. </remarks>
<param name="value">The input value.</param>
<returns>The converted string.</returns>


--------------------
type string = String
<summary>An abbreviation for the CLI type <see cref="T:System.String" />.</summary>
<category>Basic Types</category>
val enabled : bool
[<Struct>] type bool = Boolean
<summary>An abbreviation for the CLI type <see cref="T:System.Boolean" />.</summary>
<category>Basic Types</category>
val __ : User
val userJson : string
val user : User
type JsonConvert = static member DeserializeAnonymousType<'T> : value: string * anonymousTypeObject: 'T -> 'T + 1 overload static member DeserializeObject : value: string -> obj + 7 overloads static member DeserializeXNode : value: string -> XDocument + 2 overloads static member DeserializeXmlNode : value: string -> XmlDocument + 2 overloads static member EnsureDecimalPlace : value: float * text: string -> string + 1 overload static member EnsureFloatFormat : value: float * text: string * floatFormatHandling: FloatFormatHandling * quoteChar: char * nullable: bool -> string static member PopulateObject : value: string * target: obj -> unit + 1 overload static member SerializeObject : value: obj -> string + 7 overloads static member SerializeObjectInternal : value: obj * type: Type * jsonSerializer: JsonSerializer -> string static member SerializeXNode : node: XObject -> string + 2 overloads ...
<summary> Provides methods for converting between .NET types and JSON types. </summary>
<example><code lang="cs" source="..\Src\Newtonsoft.Json.Tests\Documentation\SerializationTests.cs" region="SerializeObject" title="Serializing and Deserializing JSON with JsonConvert" /></example>
JsonConvert.DeserializeObject<'T>(value: string) : 'T
JsonConvert.DeserializeObject(value: string) : obj
JsonConvert.DeserializeObject<'T>(value: string, settings: JsonSerializerSettings) : 'T
JsonConvert.DeserializeObject<'T>(value: string, [<ParamArray>] converters: JsonConverter []) : 'T
JsonConvert.DeserializeObject(value: string, type: Type) : obj
JsonConvert.DeserializeObject(value: string, settings: JsonSerializerSettings) : obj
JsonConvert.DeserializeObject(value: string, type: Type, settings: JsonSerializerSettings) : obj
JsonConvert.DeserializeObject(value: string, type: Type, [<ParamArray>] converters: JsonConverter []) : obj
Multiple items
type CLIMutableAttribute = inherit Attribute new : unit -> CLIMutableAttribute
<summary>Adding this attribute to a record type causes it to be compiled to a CLI representation with a default constructor with property getters and setters.</summary>
<category>Attributes</category>


--------------------
new : unit -> CLIMutableAttribute
type UserR = { UserName: string Enabled: bool }
UserR.UserName: string
UserR.Enabled: bool
val userRecord : UserR
type CarInfo = { Make: string Model: string Trim: string }
CarInfo.Make: string
CarInfo.Model: string
CarInfo.Trim: string
union case Vehicle.Bike: Vehicle
union case Vehicle.Car: CarInfo -> Vehicle
type OfJson = inherit Default1 static member Invoke : x:JsonValue -> 't ParseResult (requires member OfJson) static member OfJson : decimal * OfJson -> (JToken -> Result<decimal,DecodeError>) + 47 overloads
val json : Linq.JToken
Multiple items
namespace Newtonsoft.Json.Linq

--------------------
namespace System.Linq

--------------------
namespace Microsoft.FSharp.Linq
type JToken = interface IJEnumerable<JToken> interface IEnumerable<JToken> interface IEnumerable interface IJsonLineInfo interface IDynamicMetaObjectProvider new : unit -> unit member AddAfterSelf : content: obj -> unit member AddAnnotation : annotation: obj -> unit member AddBeforeSelf : content: obj -> unit member AfterSelf : unit -> IEnumerable<JToken> ...
<summary> Represents an abstract JSON token. </summary>
Multiple items
val JObject : x:Collections.Generic.IReadOnlyDictionary<string,Linq.JToken> -> Linq.JToken

--------------------
active recognizer JObject: Linq.JToken -> Choice<Collections.Generic.IReadOnlyList<Linq.JToken>,Collections.Generic.IReadOnlyDictionary<string,Linq.JToken>,Linq.JToken,bool,string,unit,Linq.JToken>
val o : Collections.Generic.IReadOnlyDictionary<string,Linq.JToken>
val monad<'monad<'t>> : MonadFxBuilder<'monad<'t>>
<summary> Creates a (lazy) monadic computation expression with side-effects (see http://fsprojects.github.io/FSharpPlus/computation-expressions.html for more information) </summary>
val jobj : Linq.JObject
Multiple items
type JObject = inherit JContainer interface IDictionary<string,JToken> interface ICollection<KeyValuePair<string,JToken>> interface IEnumerable<KeyValuePair<string,JToken>> interface IEnumerable interface INotifyPropertyChanged new : unit -> unit + 3 overloads member Add : propertyName: string * value: JToken -> unit member CloneToken : unit -> JToken member DeepEquals : node: JToken -> bool ...
<summary> Represents a JSON object. </summary>
<example><code lang="cs" source="..\Src\Newtonsoft.Json.Tests\Documentation\LinqToJsonTests.cs" region="LinqToJsonCreateParse" title="Parsing a JSON Object from Text" /></example>


--------------------
Linq.JObject() : Linq.JObject
Linq.JObject(other: Linq.JObject) : Linq.JObject
Linq.JObject([<ParamArray>] content: obj []) : Linq.JObject
Linq.JObject(content: obj) : Linq.JObject
val info : CarInfo
Linq.JToken.ToObject<'T>() : 'T
Linq.JToken.ToObject<'T>(jsonSerializer: JsonSerializer) : 'T
Linq.JToken.ToObject(objectType: Type) : obj
Linq.JToken.ToObject(objectType: Type, jsonSerializer: JsonSerializer) : obj
val e : exn
module Decode from Fleece.Newtonsoft
module Fail from Fleece.Newtonsoft.Decode
val parseError : s:exn -> v:string -> Result<'t,DecodeError>
val x : string
union case DecodeError.Uncategorized: string -> DecodeError
val sprintf : format:Printf.StringFormat<'T> -> 'T
<summary>Print to a string using the given format.</summary>
<param name="format">The formatter.</param>
<returns>The formatted result.</returns>
union case Result.Error: ErrorValue: 'TError -> Result<'T,'TError>
<summary> Represents an Error or a Failure. The code failed with a value of 'TError representing what went wrong. </summary>
val x : Linq.JToken
val objExpected : v:JsonValue -> Result<'t,DecodeError>
Person.Name: string * string
type ToJson = inherit Default1 static member Invoke : x:'t -> JsonValue (requires member ToJson) static member ToJson : x:bool * ToJson -> JToken + 44 overloads
val x : Person
type Person = { Name: string * string } static member OfJson : json:JToken -> Result<Person,DecodeError> static member ToJson : x:Person -> JToken
val jobj : x:seq<string * Linq.JToken> -> Linq.JToken
<summary> Creates a new Json object for serialization </summary>
val fst : tuple:('T1 * 'T2) -> 'T1
<summary>Return the first element of a tuple, <c>fst (a,b) = a</c>.</summary>
<param name="tuple">The input tuple.</param>
<returns>The first value.</returns>
val snd : tuple:('T1 * 'T2) -> 'T2
<summary>Return the second element of a tuple, <c>snd (a,b) = b</c>.</summary>
<param name="tuple">The input tuple.</param>
<returns>The second value.</returns>
val firstname : Result<string,DecodeError>
val jget : o:Collections.Generic.IReadOnlyDictionary<string,JsonValue> -> key:string -> Result<'a,DecodeError> (requires member OfJson)
<summary> Gets a value from a Json object </summary>
val lastname : Result<string,DecodeError>
Multiple items
val Success : x:'a -> Result<'a,'b>

--------------------
active recognizer Success: Result<'a,'b> -> Choice<'a,'b>
val firstname : string
val lastname : string
val x : Result<string,DecodeError> * Result<string,DecodeError>