#r "nuget: Fleece.NewtonsoftJson"
|
open System
open Newtonsoft.Json
open FSharpPlus
open Fleece.Newtonsoft
open Fleece.Newtonsoft.Operators
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
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.
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>