55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/RELEASE_NOTES.md:
--------------------------------------------------------------------------------
1 | #### 0.10.0 - May 25 2022
2 | * Internally using mainly codecs (more efficient and less boilerplate in source code).
3 | * Ability to switch to different implementations, even at the same time, this allows to use several Json implementations at the same time.
4 | * Unify Codec types, no more ConcreteCodec vs tupled codecs, just a simple 4 params Codec type.
5 | * A new and recommended Codec syntax for records and DUs trough an Applicative CE.
6 | * Internal caching of codecs.
7 | * Ability to workaround codecs for interfaces.
8 | * jopt combinator works with all types supporting `zero`, so in addition to option we can use voption, nullable or even list.
9 | * Native support for bigint, vtuple, voption, TimeSpan, NonEmptyList, NonEmptySet, NonEmptyMap, and "Generic Map" (Maps where keys are not strings).
10 | * DateOnly and TimeOnly support for .net6 users
11 |
12 | #### 0.9.0 - October 28 2021
13 | * Added Result codec and overload
14 | * Fix problem decoding null values into Options.
15 | * Fix parsing of floats for infinities and nan.
16 | * Fix parsing of Datetime when using newtonsoft.json
17 |
18 | #### 0.8.0 - May 17 2020
19 | * Added System.Text.Json implementation
20 | * Support for enums and all tuple sizes
21 | * Fix: error reporting wrong index in 7-uples
22 | * FSharpData use its own type for JsonObject
23 | * Added missing ofJson/toJson support for JsonValue and JsonObject
24 | * Upgrade to FSharpPlus 1.1.1
25 |
26 | #### 0.7.0 - September 27 2018
27 | * Json Lens
28 | * Codecs
29 | * Combinators
30 | * Upgrade to System.Json 4.5
31 | * Fix somes issue with Newtonsoft serialization
32 | * Breaking changes: Success and Failure functions moved to Helpers namespace
33 | * Breaking changes from 0.6.1 : encode, decode, mapping, jgetopt, jpairopt, jfieldopt functions
34 |
35 | #### 0.6.1 - September 5 2018
36 | * Codec support for Json Objects
37 |
38 | #### 0.6.0 August 23 2018
39 | * Breaking change for Newtonsoft and FSharp.Data: use a specific module
40 | * Binary Breaking Change: use Result<_,_> instead of Choice<_,_>
41 |
42 | #### 0.5.1 - December 31 2017
43 | * Lock System.Json
44 | * Bug fixes in Newtonsoft implementation
45 |
46 | #### 0.5.0 - May 27 2018
47 | * Added FSharp.Data implementation
48 | * Netstandard support
49 | * Friendlier API (non-breaking)
50 | * Null keys are filtered out in JSON objects
51 | * Updated dependencies
52 |
53 | #### 0.4.0 - September 9 2014
54 | * Added FSharp.Data implementation
55 | * Support for milliseconds in json dates
56 |
57 | #### 0.3.0 - July 31 2014
58 | * Support for Guid, Dictionary and ResizeArray
59 | * Deserialization added for JsonObject
60 | * Updated dependecies
61 |
62 | #### 0.2.0 - April 9 2014
63 | * Support for Map and Nullable
64 | * More Xml docs
65 | * Minor optimizations
66 |
67 | #### 0.1.0 - January 20 2014
68 | * Initial release
69 |
--------------------------------------------------------------------------------
/docsrc/content/further-techniques.fsx:
--------------------------------------------------------------------------------
1 | (*** hide ***)
2 | // This block of code is omitted in the generated HTML documentation. Use
3 | // it to define helpers that you do not want to show in the documentation.
4 | #r "nuget: System.Json, 4.7.1"
5 | #r "nuget: FSharpPlus, 1.2.2"
6 | #r @"../../src/Fleece.SystemJson/bin/Release/netstandard2.0/Fleece.dll"
7 | #r @"../../src/Fleece.SystemJson/bin/Release/netstandard2.0/Fleece.SystemJson.dll"
8 |
9 | open Fleece
10 | open Fleece.SystemJson
11 | open Fleece.SystemJson.Operators
12 |
13 | (**
14 | Sometimes, the JSON required by a given situation will contain fields that do not need to be present in the F# data model.
15 | For example, the JSON-RPC 2.0 specification requires every request/response object to carry the field `jsonrpc` with value `"2.0"`.
16 | In a codebase that only uses JSON-RPC 2.0, why capture this field on a record?
17 |
18 | When writing `ToJson` and `OfJson` methods for this data, handling the required field is fairly natural:
19 |
20 | *)
21 |
22 | type Request =
23 | { Method: string
24 | MethodParams: Map}
25 | static member ToJson (r: Request) =
26 | jobj [
27 | "method" .= r.Method
28 | "params" .= r.MethodParams
29 | "jsonrpc" .= "2.0"
30 | ]
31 | static member OfJson json =
32 | match json with
33 | | JObject o ->
34 | let method = o .@ "method"
35 | let methodParams = o .@ "params"
36 | // We require the "jsonrpc" field to be present
37 | let jsonrpc = o .@ "jsonrpc"
38 | match method, methodParams, jsonrpc with
39 | | Decode.Success m, Decode.Success p, Decode.Success "2.0" -> // We enforce the value of the field
40 | // ...but do not use it in the final object
41 | Decode.Success {
42 | Method = m
43 | MethodParams = p
44 | }
45 | | x -> Error <| Uncategorized (sprintf "Error parsing person: %A" x)
46 | | x -> Decode.Fail.objExpected x
47 |
48 | (**
49 | This can also be modeled with Codecs:
50 | *)
51 |
52 | type Response =
53 | { Result: string option
54 | Error: string option }
55 | static member JsonObjCodec =
56 | fun r e _ -> { Result = r; Error = e }
57 | |> withFields
58 | |> jfieldOpt "result" (fun r -> r.Result)
59 | |> jfieldOpt "error" (fun r -> r.Error)
60 | |> jfield "jsonrpc" (fun _ -> "2.0")
61 |
62 | (**
63 | There are three parts to this.
64 | First, the constructor is given an unused third parameter, which will receive the field required on the JSON object.
65 | Second, the `"jsonrpc"` field is required using `jfield`; its getter always returns `"2.0"`
66 | Finally: the fields must be in the correct order -- that is, the field specs must follow the order of the arguments in the constructor.
67 | *)
68 |
--------------------------------------------------------------------------------
/Benchmarks/Parsing.fs:
--------------------------------------------------------------------------------
1 | namespace FleeceB.Benchmarks
2 |
3 | open BenchmarkDotNet.Attributes
4 | open Fleece
5 | module Bench =
6 | open System.IO
7 | open System.Text
8 |
9 | let resetStream (stream : #Stream) =
10 | stream.Seek(0L, SeekOrigin.Begin) |> ignore
11 |
12 | module SystemJson =
13 | open Fleece.SystemJson.Operators
14 | open Fleece.Decode
15 | let inline parse (s: string): JsonObject =
16 | s
17 | |> parseJson
18 | |> function | Success v->v | Failure e ->failwithf "%A" e
19 |
20 | let inline parseStream (stream: #Stream): JsonObject =
21 | let reader = new StreamReader(stream)
22 | reader.ReadToEnd()
23 | |> parse
24 |
25 | module SystemTextJson =
26 | open Fleece.SystemTextJson.Operators
27 | open Fleece.Decode
28 | let inline parse (s: string): JsonObject =
29 | s
30 | |> parseJson
31 | |> function | Success v->v | Failure e ->failwithf "%A" e
32 |
33 | let inline parseStream (stream: #Stream): JsonObject =
34 | let reader = new StreamReader(stream)
35 | reader.ReadToEnd()
36 | |> parse
37 |
38 | module NewtonsoftJson =
39 | open Fleece.Newtonsoft.Operators
40 | open Fleece.Decode
41 | let inline parse (s: string): JsonObject =
42 | s
43 | |> parseJson
44 | |> function | Success v->v | Failure e ->failwithf "%A" e
45 |
46 | let inline parseStream (stream: #Stream): JsonObject =
47 | let reader = new StreamReader(stream)
48 | reader.ReadToEnd()
49 | |> parse
50 |
51 | module FSharpData =
52 | open Fleece.FSharpData.Operators
53 | open Fleece.Decode
54 | let inline parse (s: string): JsonObject =
55 | s
56 | |> parseJson
57 | |> function | Success v->v | Failure e ->failwithf "%A" e
58 |
59 | let inline parseStream (stream: #Stream): JsonObject =
60 | let reader = new StreamReader(stream)
61 | reader.ReadToEnd()
62 | |> parse
63 |
64 |
65 |
66 | [)>]
67 | type ParseTest () =
68 | let mutable jsonString = null
69 |
70 | []
71 | member this.Setup () =
72 | jsonString <- loadJsonResourceAsString this.Name
73 |
74 | []
75 | member val Name = "" with get, set
76 |
77 | []
78 | member __.SystemJson () =
79 | Bench.SystemJson.parse jsonString
80 |
81 | []
82 | member __.SystemTextJson () =
83 | Bench.SystemTextJson.parse jsonString
84 |
85 | []
86 | member __.NewtonsoftJson () =
87 | Bench.NewtonsoftJson.parse jsonString
88 |
89 | []
90 | member __.FSharpData () =
91 | Bench.FSharpData.parse jsonString
--------------------------------------------------------------------------------
/docsrc/content/giraffe.fsx:
--------------------------------------------------------------------------------
1 | (*** hide ***)
2 | // This block of code is omitted in the generated HTML documentation. Use
3 | // it to define helpers that you do not want to show in the documentation.
4 | #r "nuget: System.Json, 4.7.1"
5 | #r "nuget: FSharpPlus, 1.2.2"
6 | #r "nuget: TaskBuilder.fs, 2.1.0"
7 | #r @"../../src/Fleece.SystemJson/bin/Release/netstandard2.0/Fleece.dll"
8 | #r @"../../src/Fleece.SystemJson/bin/Release/netstandard2.0/Fleece.SystemJson.dll"
9 | module Giraffe=
10 | open System.Threading.Tasks
11 | open System.IO
12 | /// fake definition
13 | type Request()=
14 | member __.Body:Stream = failwith "not implemented"
15 | type HttpContext ()=
16 | member __.Request : Request=failwith "not implemented"
17 | member __.SetContentType(s:string)=failwith "not implemented"
18 | member __.WriteBytesAsync(b:byte array) : Task =failwith "not implemented"
19 |
20 | type HttpFuncResult = Task
21 |
22 | type HttpFunc = HttpContext -> HttpFuncResult
23 |
24 | type HttpHandler = HttpFunc -> HttpFunc
25 |
26 |
27 | (**
28 | ## Giraffe
29 |
30 | In this page we will get an overview of how you can use Fleece together with Giraffe.
31 |
32 | A minimal integration can be done by looking at how Giraffe implements the method [WriteJsonAsync](https://github.com/giraffe-fsharp/Giraffe/blob/37e69a54d1e85649968705f13cab77abe2d0a928/src/Giraffe/ResponseWriters.fs#L53-L57) and function [json](https://github.com/giraffe-fsharp/Giraffe/blob/37e69a54d1e85649968705f13cab77abe2d0a928/src/Giraffe/ResponseWriters.fs#L186-L188):
33 |
34 | *)
35 |
36 | open Giraffe
37 | open System.IO
38 | open System.Text
39 | // task computation builder from TaskBuilder.fs:
40 | open FSharp.Control.Tasks.V2.ContextInsensitive
41 | // Fleece and Json related:
42 | open Fleece.SystemJson
43 | open Fleece.SystemJson.Operators
44 |
45 | module BusinessApp=
46 | module Json =
47 | let inline json (dataObj ) : HttpHandler =
48 | fun (_ : HttpFunc) (ctx : HttpContext) ->
49 | ctx.SetContentType "application/json; charset=utf-8"
50 | toJson dataObj // turn dataObj into Json
51 | |> string // get the Json string
52 | |> Encoding.UTF8.GetBytes // turn the string into bytes
53 | |> ctx.WriteBytesAsync // write bytes to the response
54 |
55 | let inline bindJsonAsync (ctx : HttpContext) =
56 | task {
57 | use reader = new StreamReader(ctx.Request.Body)
58 | let! body = reader.ReadToEndAsync()
59 | return parseJson body
60 | }
61 |
62 | (**
63 | In the web API part of your business app you would then do something like the code below:
64 | *)
65 |
66 | open Giraffe
67 | open FSharp.Control.Tasks.V2.ContextInsensitive
68 | // we open the Json helpers we defined last in order to avoid using the default "json" function from Giraffe:
69 | open BusinessApp.Json
70 | type Person = { Name : string }
71 | with
72 | static member JsonObjCodec =
73 | fun name -> { Name = name }
74 | jreq "name" (Some << fun x -> x.Name)
75 |
76 | let personHandler =
77 | fun (next : HttpFunc) (ctx : HttpContext) ->
78 | task {
79 | match! bindJsonAsync ctx with // instead of using ctx.BindJsonAsync we use the function above
80 | | Ok (person:Person)->
81 | return! json person next ctx
82 | // and ideally we would deal with case when the parsing fails as well
83 | }
84 |
85 | (**
86 | The benefit of doing an integration in this way is:
87 |
88 | - You get a compilation error when trying to use types that don't have the proper functions defined.
89 | - You avoid having to use runtime reflection to bind and serialize the Json.
90 | - You have more control over the serialization than with System.Text.Json or Newtonsoft.Json
91 | *)
92 |
--------------------------------------------------------------------------------
/docsrc/content/to-json-and-of-json.fsx:
--------------------------------------------------------------------------------
1 | (*** hide ***)
2 | // This block of code is omitted in the generated HTML documentation. Use
3 | // it to define helpers that you do not want to show in the documentation.
4 | #r "nuget: System.Json, 4.7.1"
5 | #r "nuget: FSharpPlus, 1.2.2"
6 | #r @"../../src/Fleece.SystemJson/bin/Release/netstandard2.0/Fleece.dll"
7 | #r @"../../src/Fleece.SystemJson/bin/Release/netstandard2.0/Fleece.SystemJson.dll"
8 |
9 | open System.Json
10 | open Fleece
11 | open Fleece.SystemJson
12 | open Fleece.SystemJson.Operators
13 | #if FSHARPDATA
14 | #r "nuget: FSharp.Data, 3.0.0"
15 | #r @"../../src/Fleece.FSharpData/bin/Release/netstandard2.0/Fleece.FSharpData.dll"
16 |
17 | open FSharp.Data
18 | open Fleece.FSharpData
19 | open Fleece.FSharpData.Operators
20 | #endif
21 | (**
22 | ## ToJson and OfJson
23 |
24 | In order to parse or encode instances into Json you can define static members
25 |
26 | For example, given this data type:
27 | *)
28 |
29 |
30 | type Person = {
31 | Name: string
32 | Age: int
33 | Children: Person list
34 | }
35 |
36 | (**
37 | You can map it to JSON like this:
38 | *)
39 |
40 |
41 | type Person with
42 | static member ToJson (x: Person) =
43 | jobj [
44 | "name" .= x.Name
45 | "age" .= x.Age
46 | "children" .= x.Children
47 | ]
48 |
49 | let p =
50 | { Person.Name = "John"
51 | Age = 44
52 | Children =
53 | [
54 | { Person.Name = "Katy"
55 | Age = 5
56 | Children = [] }
57 | { Person.Name = "Johnny"
58 | Age = 7
59 | Children = [] }
60 | ] }
61 |
62 | printfn "%s" (string (toJson p))
63 |
64 | (**
65 | And you can map it from JSON like this:
66 | *)
67 |
68 | type Person with
69 | static member OfJson json =
70 | match json with
71 | | JObject o ->
72 | let name = o .@ "name"
73 | let age = o .@ "age"
74 | let children = o .@ "children"
75 | match name, age, children with
76 | | Decode.Success name, Decode.Success age, Decode.Success children ->
77 | Decode.Success {
78 | Person.Name = name
79 | Age = age
80 | Children = children
81 | }
82 | | x -> Error <| Uncategorized (sprintf "Error parsing person: %A" x)
83 | | x -> Decode.Fail.objExpected x
84 |
85 | let john : Person ParseResult = parseJson """{
86 | "name": "John",
87 | "age": 44,
88 | "children": [{
89 | "name": "Katy",
90 | "age": 5,
91 | "children": []
92 | }, {
93 | "name": "Johnny",
94 | "age": 7,
95 | "children": []
96 | }]
97 | }"""
98 |
99 | (**
100 | Though it's much easier to do this in a monadic or applicative way. For example, using [FSharpPlus](https://github.com/fsprojects/FSharpPlus) (which is already a dependency of Fleece):
101 | *)
102 |
103 | open FSharpPlus
104 |
105 | type PersonAp = {
106 | Name: string
107 | Age: int
108 | Children: PersonAp list
109 | }
110 |
111 | type PersonAp with
112 | static member Create name age children = { PersonAp.Name = name; Age = age; Children = children }
113 |
114 | static member OfJson json =
115 | match json with
116 | | JObject o -> PersonAp.Create (o .@ "name") <*> (o .@ "age") <*> (o .@ "children")
117 | | x -> Decode.Fail.objExpected x
118 |
119 | (**
120 |
121 | Or monadically:
122 | *)
123 |
124 | type PersonM = {
125 | Name: string
126 | Age: int
127 | Children: PersonM list
128 | }
129 |
130 | type PersonM with
131 | static member OfJson json =
132 | match json with
133 | | JObject o ->
134 | monad {
135 | let! name = o .@ "name"
136 | let! age = o .@ "age"
137 | let! children = o .@ "children"
138 | return {
139 | Person.Name = name
140 | Age = age
141 | Children = children
142 | }
143 | }
144 | | x -> Decode.Fail.objExpected x
145 |
146 | (**
147 | Or you can use the Choice monad/applicative in [FSharpx.Extras](https://github.com/fsprojects/FSharpx.Extras) instead, if you prefer.
148 |
149 | You can see more examples in the [EdmundsNet](https://github.com/mausch/EdmundsNet) project.
150 | *)
151 |
--------------------------------------------------------------------------------
/docsrc/tool/templates/reference/module.cshtml:
--------------------------------------------------------------------------------
1 | @using FSharp.MetadataFormat
2 | @{
3 | Layout = "template";
4 | Title = Model.Module.Name + " - " + Properties["project-name"];
5 | }
6 |
7 | @{
8 | // Get all the members & comment for the type
9 | var members = (IEnumerable)Model.Module.AllMembers;
10 | var comment = (Comment)Model.Module.Comment;
11 |
12 | // Group all members by their category which is an inline annotation
13 | // that can be added to members using special XML comment:
14 | //
15 | // /// [category:Something]
16 | //
17 | // ...and can be used to categorize members in large modules or types
18 | // (but if this is not used, then all members end up in just one category)
19 | var byCategory = members
20 | .GroupBy(m => m.Category)
21 | .OrderBy(g => String.IsNullOrEmpty(g.Key) ? "ZZZ" : g.Key)
22 | .Select((g, n) => new {
23 | Index = n,
24 | GroupKey = g.Key,
25 | Members = g.OrderBy(m => m.Name),
26 | Name = String.IsNullOrEmpty(g.Key) ? "Other module members" : g.Key
27 | });
28 |
29 | // Get nested modules and nested types as statically typed collections
30 | var nestModules = (IEnumerable)Model.Module.NestedModules;
31 | var nestTypes = (IEnumerable)Model.Module.NestedTypes;
32 | }
33 |
34 |
61 | @foreach (var sec in comment.Sections) {
62 | // XML comment for the type has multiple sections that can be labelled
63 | // with categories (to give comment for an individual category). Here,
64 | // we print only those that belong to the section.
65 | if (!byCategory.Any(g => g.GroupKey == sec.Key))
66 | {
67 | if (sec.Key != "") {
68 |
96 | }
97 |
98 | @foreach (var g in byCategory)
99 | {
100 | // Iterate over all the categories and print members. If there are more than one
101 | // categories, print the category heading (as
) and add XML comment from the type
102 | // that is related to this specific category.
103 | if (byCategory.Count() > 1)
104 | {
105 |
@g.Name
106 | var info = comment.Sections.FirstOrDefault(kvp => kvp.Key == g.GroupKey);
107 | if (info.Key != null)
108 | {
109 |
110 | @info.Value
111 |
112 | }
113 | }
114 |
115 | @RenderPart("part-members", new {
116 | Header = "Functions and values",
117 | TableHeader = "Function or value",
118 | Members = g.Members.Where(m => m.Kind == MemberKind.ValueOrFunction)
119 | })
120 |
121 | @RenderPart("part-members", new {
122 | Header = "Type extensions",
123 | TableHeader = "Type extension",
124 | Members = g.Members.Where(m => m.Kind == MemberKind.TypeExtension)
125 | })
126 |
127 | @RenderPart("part-members", new {
128 | Header = "Active patterns",
129 | TableHeader = "Active pattern",
130 | Members = g.Members.Where(m => m.Kind == MemberKind.ActivePattern)
131 | })
132 | }
--------------------------------------------------------------------------------
/docsrc/tool/templates/reference/type.cshtml:
--------------------------------------------------------------------------------
1 | @using FSharp.MetadataFormat
2 | @{
3 | Layout = "template";
4 | Title = Model.Type.Name + " - " + Properties["project-name"];
5 | }
6 |
7 | @{
8 | // Get all the members & comment for the type
9 | var members = (IEnumerable)Model.Type.AllMembers;
10 | var comment = (Comment)Model.Type.Comment;
11 | var type = (FSharp.MetadataFormat.Type)Model.Type;
12 | // Group all members by their category which is an inline annotation
13 | // that can be added to members using special XML comment:
14 | //
15 | // /// [category:Something]
16 | //
17 | // ...and can be used to categorize members in large modules or types
18 | // (but if this is not used, then all members end up in just one category)
19 | var byCategory = members
20 | .GroupBy(m => m.Category)
21 | .OrderBy(g => String.IsNullOrEmpty(g.Key) ? "ZZZ" : g.Key)
22 | .Select((g, n) => new
23 | {
24 | Index = n,
25 | GroupKey = g.Key,
26 | Members = g.OrderBy(m => m.Kind == MemberKind.StaticParameter ? "" : m.Name),
27 | Name = String.IsNullOrEmpty(g.Key) ? "Other type members" : g.Key
28 | });
29 |
30 | }
31 |
32 |
60 | @foreach (var sec in comment.Sections)
61 | {
62 | // XML comment for the type has multiple sections that can be labelled
63 | // with categories (to give comment for an individual category). Here,
64 | // we print only those that belong to the section.
65 | if (!byCategory.Any(g => g.GroupKey == sec.Key))
66 | {
67 | if (sec.Key != "")
68 | {
69 |
85 | }
86 | @foreach (var g in byCategory)
87 | {
88 | // Iterate over all the categories and print members. If there are more than one
89 | // categories, print the category heading (as
) and add XML comment from the type
90 | // that is related to this specific category.
91 | if (byCategory.Count() > 1)
92 | {
93 |
@g.Name
94 | var info = comment.Sections.FirstOrDefault(kvp => kvp.Key == g.GroupKey);
95 | if (info.Key != null)
96 | {
97 |
98 | @info.Value
99 |
100 | }
101 | }
102 |
103 | @RenderPart("part-members", new
104 | {
105 | Header = "Union Cases",
106 | TableHeader = "Union Case",
107 | Members = g.Members.Where(m => m.Kind == MemberKind.UnionCase)
108 | })
109 |
110 | @RenderPart("part-members", new
111 | {
112 | Header = "Record Fields",
113 | TableHeader = "Record Field",
114 | Members = g.Members.Where(m => m.Kind == MemberKind.RecordField)
115 | })
116 |
117 | @RenderPart("part-members", new
118 | {
119 | Header = "Static parameters",
120 | TableHeader = "Static parameters",
121 | Members = g.Members.Where(m => m.Kind == MemberKind.StaticParameter)
122 | })
123 |
124 | @RenderPart("part-members", new
125 | {
126 | Header = "Constructors",
127 | TableHeader = "Constructor",
128 | Members = g.Members.Where(m => m.Kind == MemberKind.Constructor)
129 | })
130 |
131 | @RenderPart("part-members", new
132 | {
133 | Header = "Instance members",
134 | TableHeader = "Instance member",
135 | Members = g.Members.Where(m => m.Kind == MemberKind.InstanceMember)
136 | })
137 |
138 | @RenderPart("part-members", new
139 | {
140 | Header = "Static members",
141 | TableHeader = "Static member",
142 | Members = g.Members.Where(m => m.Kind == MemberKind.StaticMember)
143 | })
144 | }
145 |
--------------------------------------------------------------------------------
/docsrc/tool/Program.fs:
--------------------------------------------------------------------------------
1 | // Learn more about F# at http://fsharp.org
2 |
3 | open System
4 |
5 | open DocLib
6 |
7 |
8 | // --------------------------------------------------------------------------------------
9 | // START TODO: Provide project-specific details below
10 | // --------------------------------------------------------------------------------------
11 |
12 | // Information about the project are used
13 | // - to publish documentation on GitHub gh-pages
14 | // - for documentation, you also need to edit info in "docsrc/tools/generate.fsx"
15 |
16 | // Git configuration (used for publishing documentation in gh-pages branch)
17 | // The profile where the project is posted
18 | let gitOwner = "fsprojects"
19 | let gitHome = sprintf "%s/%s" "https://github.com" gitOwner
20 |
21 | // The name of the project on GitHub
22 | let gitName = "Fleece"
23 |
24 | let website = "/Fleece"
25 |
26 | let github_release_user = Environment.environVarOrDefault "github_release_user" gitOwner
27 | let githubLink = sprintf "https://github.com/%s/%s" github_release_user gitName
28 |
29 | // Specify more information about your project
30 | let info =
31 | [ "project-name", "Fleece"
32 | "project-author", "Mauricio Scheffer,Lev Gorodinski,Oskar Gewalli, Gustavo P. Leon"
33 | "project-summary", "Fleece is a JSON mapper for F#. It simplifies mapping from a Json library's JsonValue onto your types, and mapping from your types onto JsonValue."
34 | "project-github", githubLink
35 | "fsdocs-logo-src","https://raw.githubusercontent.com/fsprojects/Fleece/master/docsrc/files/img/logo-color.svg"
36 | "fsdocs-logo-link","http://fsprojects.github.io/Fleece/"
37 | "fsdocs-license-link","http://fsprojects.github.io/Fleece/license.html"
38 | "project-nuget", "http://nuget.org/packages/Fleece" ]
39 |
40 | // --------------------------------------------------------------------------------------
41 | // END TODO: The rest of the file includes standard build steps
42 | // --------------------------------------------------------------------------------------
43 |
44 | // Read additional information from the release notes document
45 | let release = ReleaseNotes.load "RELEASE_NOTES.md"
46 |
47 |
48 | Target.create "CleanDocs" (fun _ ->
49 | Shell.cleanDirs ["docs"]
50 | )
51 |
52 | let rootDir = __SOURCE_DIRECTORY__ @@ ".." @@ ".."
53 | // --------------------------------------------------------------------------------------
54 | // Build project
55 | open Tools.Path
56 |
57 | Target.create "Build" (fun _ ->
58 | let root = website+"/"
59 | FSFormatting.buildDocs (fun args ->
60 | { args with
61 | OutputDirectory = output
62 | ProjectParameters = ("root", root)::info
63 | Projects = rootDir
64 | TargetPath = rootDir
65 |
66 | SourceRepository = githubLink @@ "tree/master" }
67 | )
68 | )
69 |
70 |
71 | // --------------------------------------------------------------------------------------
72 | // Generate the documentation
73 |
74 | let root = website
75 |
76 | let referenceBinaries = []
77 | open Tools.Path
78 | open System.IO
79 | let bin = rootDir @@ "src"
80 |
81 | let copyFiles () =
82 | Shell.copyRecursive files output true
83 | |> Trace.logItems "Copying file: "
84 | Directory.ensure (output @@ "content")
85 |
86 | Target.create "Docs" (fun _ ->
87 | System.IO.File.Delete ( rootDir @@ "docsrc/content/release-notes.md" )
88 | Shell.copyFile (rootDir @@ "docsrc/content/") "RELEASE_NOTES.md"
89 | Shell.rename ( rootDir @@ "docsrc/content/release-notes.md" ) "docsrc/content/RELEASE_NOTES.md"
90 |
91 | System.IO.File.Delete ( rootDir @@ "docsrc/content/license.md" )
92 | Shell.copyFile ( rootDir @@ "docsrc/content/" ) "LICENSE"
93 | Shell.rename ( rootDir @@ "docsrc/content/license.md" ) "docsrc/content/LICENSE"
94 |
95 | copyFiles ()
96 | )
97 |
98 | // --------------------------------------------------------------------------------------
99 | // Post process here:
100 |
101 |
102 |
103 | []
104 | let main argv =
105 |
106 | // --------------------------------------------------------------------------------------
107 | // Release Scripts
108 |
109 | if Array.contains "ReleaseDocs" argv then
110 |
111 | Target.create "ReleaseDocs" (fun _ ->
112 | let tempDocsDir = rootDir @@ "temp/gh-pages"
113 | Shell.cleanDir tempDocsDir
114 | let repoUrl = Git.Config.remoteOriginUrl rootDir
115 | Git.Repository.cloneSingleBranch rootDir repoUrl "gh-pages" tempDocsDir
116 | let docDir = rootDir @@ "docs"
117 | Shell.copyRecursive docDir tempDocsDir true |> Trace.tracefn "%A"
118 | Git.Staging.stageAll tempDocsDir
119 | Git.Commit.exec tempDocsDir (sprintf "Update generated documentation for version %s" release.NugetVersion)
120 | Git.Branches.push tempDocsDir
121 | )
122 |
123 | Target.create "GenerateDocs" ignore
124 | 0 // return an integer exit code
125 |
--------------------------------------------------------------------------------
/docsrc/content/comparison-with-json-net.fsx:
--------------------------------------------------------------------------------
1 | (*** hide ***)
2 | // This block of code is omitted in the generated HTML documentation. Use
3 | // it to define helpers that you do not want to show in the documentation.
4 | #r "nuget: Newtonsoft.Json, 10.0.2"
5 | #r "nuget: FSharpPlus, 1.2.2"
6 | #r @"../../src/Fleece/bin/Release/netstandard2.0/Fleece.dll"
7 | #r @"../../src/Fleece.NewtonsoftJson/bin/Release/netstandard2.0/Fleece.NewtonsoftJson.dll"
8 |
9 | (**
10 | ```f#
11 | #r "nuget: Fleece.NewtonsoftJson"
12 | ```
13 | *)
14 |
15 | open System
16 | open Newtonsoft.Json
17 | open FSharpPlus
18 | open Fleece
19 | open Fleece.Newtonsoft
20 | open Fleece.Newtonsoft.Operators
21 |
22 | (**
23 | ## Comparison with Json.Net or Newtonsoft Json
24 |
25 | 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
26 | (note the different first letter casing).
27 | *)
28 |
29 | type User(userName:string , enabled: bool)=
30 | member __.UserName = userName
31 | member __.Enabled = enabled
32 | let userJson="""
33 | {"userName":"test","enabled":true}
34 | """
35 | let user = JsonConvert.DeserializeObject userJson
36 | (**
37 | Another alternative would be to use CLI-mutable
38 | *)
39 | []
40 | type UserR ={ UserName:string; Enabled:bool }
41 | (**
42 | This enables Json.Net to deserialize json into your structure but leave the F# code easier to reason about.
43 | *)
44 | let userRecord = JsonConvert.DeserializeObject userJson
45 | (**
46 | ### Controlling polymorphism
47 |
48 | The default approach is to use [serialization binder](https://www.newtonsoft.com/json/help/html/SerializeSerializationBinder.htm). The assumption is that you have an abstract class or an interface that implemented by many different types.
49 |
50 | In order to have a better serialization of union cases you need to implement something as seen in [FsCodec.NewtonsoftJson/UnionConverter](https://github.com/jet/FsCodec/blob/2bdcd60c04588c81caecbea6e5507348c4763fd9/src/FsCodec.NewtonsoftJson/UnionConverter.fs).
51 |
52 | 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](https://eiriktsarpalis.wordpress.com/2018/10/30/a-contract-pattern-for-schemaless-datastores/).
53 |
54 | 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):
55 | *)
56 | []
57 | type CarInfo = { Make:string; Model:string; Trim:string}
58 | type Vehicle =
59 | | Bike
60 | | Car of CarInfo
61 | with
62 | static member OfJson (json: Encoding) =
63 | match json with
64 | | JObject o ->
65 | monad.strict {
66 | match! o .@ "type" with
67 | | "Bike" -> return Bike
68 | | "Car" ->
69 | // Our native is wrapped into NsjEncoding
70 | let j = Encoding.Unwrap json
71 |
72 | // we know that json token is a JObject due to the check above so we can directly cast it:
73 | let jobj : Linq.JObject = downcast j
74 | try
75 | // now we can use the default Newtonsoft Json decoder:
76 | let info = jobj.ToObject() // NOTE: here we hand over control of the mapping to Newtonsoft.Json
77 | return Car info
78 | with
79 | | e-> return! Decode.Fail.parseError e "Could not parse CarInfo"
80 | | x -> return! Uncategorized (sprintf "Unexpected type name %s" x) |> Error
81 | }
82 | | x -> Decode.Fail.objExpected x
83 | (**
84 | 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.
85 |
86 | 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.
87 | *)
88 |
89 | (**
90 | ## Full control over mapping
91 |
92 | The default approach to serialization and deserialization in Fleece let you have a lot of control. You choose exactly how it should work.
93 |
94 | 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.
95 |
96 | If we look at a simple example of the Json not matching the representation (where you would need a custom JsonConverter):
97 | *)
98 |
99 | type Person = {
100 | Name : string * string
101 | }
102 | with
103 | static member ToJson (x: Person) =
104 | jobj [
105 | "firstname" .= fst x.Name
106 | "lastname" .= snd x.Name
107 | ]
108 | static member OfJson json =
109 | match json with
110 | | JObject o ->
111 | let firstname = jget o "firstname"
112 | let lastname = jget o "lastname"
113 | match firstname, lastname with
114 | | Decode.Success firstname, Decode.Success lastname ->
115 | Decode.Success {
116 | Person.Name = (firstname,lastname)
117 | }
118 | | x -> Error <| Uncategorized (sprintf "Error parsing person: %A" x)
119 | | x -> Decode.Fail.objExpected x
120 |
121 | (**
122 | In that sense, having access to functions helps us make what in Newtonsoft is a pain to implement, very easy.
123 | *)
--------------------------------------------------------------------------------
/test/Tests/Lenses.fs:
--------------------------------------------------------------------------------
1 | module Tests.Lenses
2 | open System
3 | open System.Collections.Generic
4 | open System.Linq
5 | open Fuchu
6 | open Fleece
7 | open FSharpPlus
8 | open FSharpPlus.Lens
9 | open Fleece.Lens
10 |
11 | #if FSHARPDATA
12 | open FSharp.Data
13 | open Fleece.FSharpData
14 | type FdEncoding = Fleece.FSharpData.Encoding
15 | let encodingParse = FdEncoding.Parse
16 | #endif
17 |
18 |
19 | #if NEWTONSOFT
20 | open Fleece.Newtonsoft
21 | open Newtonsoft.Json
22 | open Newtonsoft.Json.Linq
23 | type NsjEncoding = Fleece.Newtonsoft.Encoding
24 | let encodingParse = NsjEncoding.Parse
25 | #endif
26 |
27 | #if SYSTEMJSON
28 | open System.Json
29 | open Fleece.SystemJson
30 | type SjEncoding = Fleece.SystemJson.Encoding
31 | let encodingParse = SjEncoding.Parse
32 | #endif
33 |
34 | #if SYSTEMTEXTJSON
35 | open Fleece.SystemTextJson
36 | open System.Text.Json
37 | type StjEncoding = Fleece.SystemTextJson.Encoding
38 | let encodingParse = StjEncoding.Parse
39 | #endif
40 |
41 | let strCleanUp x = System.Text.RegularExpressions.Regex.Replace(x, @"\s|\r\n?|\n", "")
42 |
43 | let tests = [
44 | testList "key" [
45 | test "example 1: read first key" {
46 | let actual = encodingParse( "{\"a\": true, \"b\": 200}" ) ^? (_jkey "a" << _JBool)
47 | let expected = true
48 | Assert.Equal("item", Some expected, actual)
49 | }
50 | test "example 2: read second key" {
51 | let actual = encodingParse( "{\"a\": true, \"b\": 200}" ) ^? (_jkey "b" << _JNumber)
52 | let expected = 200m
53 | Assert.Equal("item", Some expected, actual)
54 | }
55 | test "example 3: read with missing key" {
56 | let actual = encodingParse( "{\"a\": true, \"b\": 200}" ) ^? (_jkey "c" << _JNumber)
57 | Assert.Equal("item", None, actual)
58 | }
59 | test "example 4.1: write with missing key" {
60 | let actual = encodingParse( "{\"a\": true, \"b\": 200}" )|> (_jkey "c" ) .-> JString "a"
61 | let expected = encodingParse ("{\"a\": true, \"b\": 200, \"c\":\"a\"}")
62 | Assert.Equal("item", strCleanUp (string expected), strCleanUp (string actual))
63 | }
64 | test "example 4.2: write with missing key" { //TODO: Fix
65 | let actual = encodingParse( "{\"a\": true, \"b\": 200}" )|> (_jkey "c" << _JString) .-> "a"
66 | let expected = encodingParse ("{\"a\": true, \"b\": 200, \"c\":\"a\"}")
67 | //Assert.Equal("item", string expected, string actual)
68 | printfn "todo: %A ~ %A" expected actual
69 | }
70 | test "example 5: write existing key" {
71 | let actual = encodingParse( "{\"a\": true, \"b\": 200}" )|> (_jkey "a" << _JBool) .-> false
72 | let expected = encodingParse ("{\"a\": false, \"b\": 200}")
73 | Assert.Equal("item", strCleanUp (string expected), strCleanUp (string actual))
74 | }
75 | test "example 6: read key from a different type" {
76 | let actual = encodingParse( "[1,2,3]" ) ^? _jkey "a"
77 | Assert.Equal("item", true, actual.IsNone)
78 | }
79 |
80 | ]
81 | testList "_String" [
82 | test "example 1" {
83 | let actual = encodingParse ("{\"a\": \"xyz\", \"b\": true}") ^? (_jkey "a" << _JString)
84 | let expected = "xyz"
85 | Assert.Equal("item", Some expected, actual)
86 | }
87 | test "example 2" {
88 | let actual = encodingParse ("{\"a\": \"xyz\", \"b\": true}") ^? (_jkey "b" << _JString)
89 | Assert.Equal("item", None, actual)
90 | }
91 | (* test "example 3" {
92 | let actual = JString "a" |> _JString .-> "b"
93 | let expected = JString "b"
94 | Assert.Equal("item", string expected, string actual)
95 | } *)
96 | ]
97 | testList "_Number" [
98 | test "example 1" {
99 | let actual = encodingParse ("{\"a\": 100, \"b\": true}") ^? (_jkey "a" << _JNumber)
100 | let expected = 100m
101 | Assert.Equal("item", Some expected, actual)
102 | }
103 | test "example 2: write" {
104 | let actual = encodingParse ("{\"a\": 100, \"b\": true}") |> (_jkey "a" << _JNumber) .-> 200m
105 | let expected =
106 | #if NEWTONSOFT
107 | "{\"a\": 200.0, \"b\": true}"
108 | #else
109 | "{\"a\": 200, \"b\": true}"
110 | #endif
111 | Assert.Equal("item", strCleanUp (string (encodingParse expected)), strCleanUp (string actual))
112 | }
113 | ]
114 | testList "array" [
115 | test "example 1" {
116 | let actual = encodingParse ("[\"a\"]") ^? (_jnth 0 << _JString)
117 | let expected = "a"
118 | Assert.Equal("item", Some expected, actual)
119 | }
120 | test "example 2" {
121 | let actual = encodingParse ("[123]") ^? (_jnth 0 << _JNumber)
122 | let expected = 123m
123 | Assert.Equal("item", Some expected, actual)
124 | }
125 | test "example 3: read for missing index" {
126 | let actual = encodingParse ("[1,2,3]") ^? (_jnth 4 << _JNumber)
127 | Assert.Equal("item", None, actual)
128 | }
129 | test "example 4: write" {
130 | let actual = encodingParse ("[1,2,3]") |> (_jnth 1 << _JNumber) .-> 2.5m
131 | let expected = encodingParse ("[1,2.5,3]")
132 | Assert.Equal("item", string expected, string actual)
133 | }
134 | test "example 5: write for missing index" {
135 | let actual = encodingParse ("[1]") |> (_jnth 1 << _JString) .-> "a"
136 | let expected = encodingParse ("[1]")
137 | Assert.Equal("item", string expected, string actual)
138 | }
139 | ]
140 | ]
141 |
--------------------------------------------------------------------------------
/docsrc/files/style.css:
--------------------------------------------------------------------------------
1 | @import url(https://fonts.googleapis.com/css?family=Droid+Sans|Droid+Sans+Mono|Open+Sans:400,600,700);
2 |
3 | /*--------------------------------------------------------------------------
4 | Formatting for F# code snippets
5 | /*--------------------------------------------------------------------------*/
6 |
7 | /* strings --- and stlyes for other string related formats */
8 | span.s { color:#E0E268; }
9 | /* printf formatters */
10 | span.pf { color:#E0C57F; }
11 | /* escaped chars */
12 | span.e { color:#EA8675; }
13 |
14 | /* identifiers --- and styles for more specific identifier types */
15 | span.id { color:#d1d1d1; }
16 | /* module */
17 | span.m { color:#43AEC6; }
18 | /* reference type */
19 | span.rt { color:#43AEC6; }
20 | /* value type */
21 | span.vt { color:#43AEC6; }
22 | /* interface */
23 | span.if{ color:#43AEC6; }
24 | /* type argument */
25 | span.ta { color:#43AEC6; }
26 | /* disposable */
27 | span.d { color:#43AEC6; }
28 | /* property */
29 | span.prop { color:#43AEC6; }
30 | /* punctuation */
31 | span.p { color:#43AEC6; }
32 | /* function */
33 | span.f { color:#e1e1e1; }
34 | /* active pattern */
35 | span.pat { color:#4ec9b0; }
36 | /* union case */
37 | span.u { color:#4ec9b0; }
38 | /* enumeration */
39 | span.e { color:#4ec9b0; }
40 | /* keywords */
41 | span.k { color:#FAB11D; }
42 | /* comment */
43 | span.c { color:#808080; }
44 | /* operators */
45 | span.o { color:#af75c1; }
46 | /* numbers */
47 | span.n { color:#96C71D; }
48 | /* line number */
49 | span.l { color:#80b0b0; }
50 | /* mutable var or ref cell */
51 | span.v { color:#d1d1d1; font-weight: bold; }
52 | /* inactive code */
53 | span.inactive { color:#808080; }
54 | /* preprocessor */
55 | span.prep { color:#af75c1; }
56 | /* fsi output */
57 | span.fsi { color:#808080; }
58 |
59 | /* omitted */
60 | span.omitted {
61 | background:#3c4e52;
62 | border-radius:5px;
63 | color:#808080;
64 | padding:0px 0px 1px 0px;
65 | }
66 | /* tool tip */
67 | div.tip {
68 | background:#475b5f;
69 | border-radius:4px;
70 | font:11pt 'Droid Sans', arial, sans-serif;
71 | padding:6px 8px 6px 8px;
72 | display:none;
73 | color:#d1d1d1;
74 | pointer-events:none;
75 | }
76 | table.pre pre {
77 | padding:0px;
78 | margin:0px;
79 | border:none;
80 | }
81 | table.pre, pre.fssnip, pre {
82 | line-height:13pt;
83 | border:1px solid #d8d8d8;
84 | border-collapse:separate;
85 | white-space:pre;
86 | font: 9pt 'Droid Sans Mono',consolas,monospace;
87 | width:90%;
88 | margin:10px 20px 20px 20px;
89 | background-color:#212d30;
90 | padding:10px;
91 | border-radius:5px;
92 | color:#d1d1d1;
93 | max-width: none;
94 | }
95 | pre.fssnip code {
96 | font: 9pt 'Droid Sans Mono',consolas,monospace;
97 | }
98 | table.pre pre {
99 | padding:0px;
100 | margin:0px;
101 | border-radius:0px;
102 | width: 100%;
103 | }
104 | table.pre td {
105 | padding:0px;
106 | white-space:normal;
107 | margin:0px;
108 | }
109 | table.pre td.lines {
110 | width:30px;
111 | }
112 |
113 | /*--------------------------------------------------------------------------
114 | Formatting for page & standard document content
115 | /*--------------------------------------------------------------------------*/
116 |
117 | body {
118 | font-family: 'Open Sans', serif;
119 | padding-top: 0px;
120 | padding-bottom: 40px;
121 | }
122 |
123 | pre {
124 | word-wrap: inherit;
125 | }
126 |
127 | /* Format the heading - nicer spacing etc. */
128 | .masthead {
129 | overflow: hidden;
130 | }
131 | .masthead .muted a {
132 | text-decoration:none;
133 | color:#999999;
134 | }
135 | .masthead ul, .masthead li {
136 | margin-bottom:0px;
137 | }
138 | .masthead .nav li {
139 | margin-top: 15px;
140 | font-size:110%;
141 | }
142 | .masthead h3 {
143 | margin-bottom:5px;
144 | font-size:170%;
145 | }
146 | hr {
147 | margin:0px 0px 20px 0px;
148 | }
149 |
150 | /* Make table headings and td.title bold */
151 | td.title, thead {
152 | font-weight:bold;
153 | }
154 |
155 | /* Format the right-side menu */
156 | #menu {
157 | margin-top:50px;
158 | font-size:11pt;
159 | padding-left:20px;
160 | }
161 |
162 | #menu .nav-header {
163 | font-size:12pt;
164 | color:#606060;
165 | margin-top:20px;
166 | }
167 |
168 | #menu li {
169 | line-height:25px;
170 | }
171 |
172 | /* Change font sizes for headings etc. */
173 | #main h1 { font-size: 26pt; margin:10px 0px 15px 0px; font-weight:400; }
174 | #main h2 { font-size: 20pt; margin:20px 0px 0px 0px; font-weight:400; }
175 | #main h3 { font-size: 14pt; margin:15px 0px 0px 0px; font-weight:600; }
176 | #main p { font-size: 11pt; margin:5px 0px 15px 0px; }
177 | #main ul { font-size: 11pt; margin-top:10px; }
178 | #main li { font-size: 11pt; margin: 5px 0px 5px 0px; }
179 | #main strong { font-weight:700; }
180 |
181 | /*--------------------------------------------------------------------------
182 | Formatting for API reference
183 | /*--------------------------------------------------------------------------*/
184 |
185 | .type-list .type-name, .module-list .module-name {
186 | width:25%;
187 | font-weight:bold;
188 | }
189 | .member-list .member-name {
190 | width:35%;
191 | }
192 | #main .xmldoc h2 {
193 | font-size:14pt;
194 | margin:10px 0px 0px 0px;
195 | }
196 | #main .xmldoc h3 {
197 | font-size:12pt;
198 | margin:10px 0px 0px 0px;
199 | }
200 | .github-link {
201 | float:right;
202 | text-decoration:none;
203 | }
204 | .github-link img {
205 | border-style:none;
206 | margin-left:10px;
207 | }
208 | .github-link .hover { display:none; }
209 | .github-link:hover .hover { display:block; }
210 | .github-link .normal { display: block; }
211 | .github-link:hover .normal { display: none; }
212 |
213 | /*--------------------------------------------------------------------------
214 | Links
215 | /*--------------------------------------------------------------------------*/
216 |
217 | h1 a, h1 a:hover, h1 a:focus,
218 | h2 a, h2 a:hover, h2 a:focus,
219 | h3 a, h3 a:hover, h3 a:focus,
220 | h4 a, h4 a:hover, h4 a:focus,
221 | h5 a, h5 a:hover, h5 a:focus,
222 | h6 a, h6 a:hover, h6 a:focus { color : inherit; text-decoration : inherit; outline:none }
223 |
224 | /*--------------------------------------------------------------------------
225 | Additional formatting for the homepage
226 | /*--------------------------------------------------------------------------*/
227 |
228 | #nuget {
229 | margin-top:20px;
230 | font-size: 11pt;
231 | padding:20px;
232 | }
233 |
234 | #nuget pre {
235 | font-size:11pt;
236 | -moz-border-radius: 0px;
237 | -webkit-border-radius: 0px;
238 | border-radius: 0px;
239 | background: #404040;
240 | border-style:none;
241 | color: #e0e0e0;
242 | margin-top:15px;
243 | }
244 |
--------------------------------------------------------------------------------
/docsrc/content/content/style.css:
--------------------------------------------------------------------------------
1 | @import url(https://fonts.googleapis.com/css?family=Droid+Sans|Droid+Sans+Mono|Open+Sans:400,600,700);
2 |
3 | /*--------------------------------------------------------------------------
4 | Formatting for F# code snippets
5 | /*--------------------------------------------------------------------------*/
6 |
7 | /* strings --- and stlyes for other string related formats */
8 | span.s { color:#E0E268; }
9 | /* printf formatters */
10 | span.pf { color:#E0C57F; }
11 | /* escaped chars */
12 | span.e { color:#EA8675; }
13 |
14 | /* identifiers --- and styles for more specific identifier types */
15 | span.id { color:#d1d1d1; }
16 | /* module */
17 | span.m { color:#43AEC6; }
18 | /* reference type */
19 | span.rt { color:#43AEC6; }
20 | /* value type */
21 | span.vt { color:#43AEC6; }
22 | /* interface */
23 | span.if{ color:#43AEC6; }
24 | /* type argument */
25 | span.ta { color:#43AEC6; }
26 | /* disposable */
27 | span.d { color:#43AEC6; }
28 | /* property */
29 | span.prop { color:#43AEC6; }
30 | /* punctuation */
31 | span.p { color:#43AEC6; }
32 | /* function */
33 | span.f { color:#e1e1e1; }
34 | /* active pattern */
35 | span.pat { color:#4ec9b0; }
36 | /* union case */
37 | span.u { color:#4ec9b0; }
38 | /* enumeration */
39 | span.e { color:#4ec9b0; }
40 | /* keywords */
41 | span.k { color:#FAB11D; }
42 | /* comment */
43 | span.c { color:#808080; }
44 | /* operators */
45 | span.o { color:#af75c1; }
46 | /* numbers */
47 | span.n { color:#96C71D; }
48 | /* line number */
49 | span.l { color:#80b0b0; }
50 | /* mutable var or ref cell */
51 | span.v { color:#d1d1d1; font-weight: bold; }
52 | /* inactive code */
53 | span.inactive { color:#808080; }
54 | /* preprocessor */
55 | span.prep { color:#af75c1; }
56 | /* fsi output */
57 | span.fsi { color:#808080; }
58 |
59 | /* omitted */
60 | span.omitted {
61 | background:#3c4e52;
62 | border-radius:5px;
63 | color:#808080;
64 | padding:0px 0px 1px 0px;
65 | }
66 | /* tool tip */
67 | div.tip {
68 | background:#475b5f;
69 | border-radius:4px;
70 | font:11pt 'Droid Sans', arial, sans-serif;
71 | padding:6px 8px 6px 8px;
72 | display:none;
73 | color:#d1d1d1;
74 | pointer-events:none;
75 | }
76 | table.pre pre {
77 | padding:0px;
78 | margin:0px;
79 | border:none;
80 | }
81 | table.pre, pre.fssnip, pre {
82 | line-height:13pt;
83 | border:1px solid #d8d8d8;
84 | border-collapse:separate;
85 | white-space:pre;
86 | font: 9pt 'Droid Sans Mono',consolas,monospace;
87 | width:90%;
88 | margin:10px 20px 20px 20px;
89 | background-color:#212d30;
90 | padding:10px;
91 | border-radius:5px;
92 | color:#d1d1d1;
93 | max-width: none;
94 | }
95 | pre.fssnip code {
96 | font: 9pt 'Droid Sans Mono',consolas,monospace;
97 | }
98 | table.pre pre {
99 | padding:0px;
100 | margin:0px;
101 | border-radius:0px;
102 | width: 100%;
103 | }
104 | table.pre td {
105 | padding:0px;
106 | white-space:normal;
107 | margin:0px;
108 | }
109 | table.pre td.lines {
110 | width:30px;
111 | }
112 |
113 | /*--------------------------------------------------------------------------
114 | Formatting for page & standard document content
115 | /*--------------------------------------------------------------------------*/
116 |
117 | body {
118 | font-family: 'Open Sans', serif;
119 | padding-top: 0px;
120 | padding-bottom: 40px;
121 | }
122 |
123 | pre {
124 | word-wrap: inherit;
125 | }
126 |
127 | /* Format the heading - nicer spacing etc. */
128 | .masthead {
129 | overflow: hidden;
130 | }
131 | .masthead .muted a {
132 | text-decoration:none;
133 | color:#999999;
134 | }
135 | .masthead ul, .masthead li {
136 | margin-bottom:0px;
137 | }
138 | .masthead .nav li {
139 | margin-top: 15px;
140 | font-size:110%;
141 | }
142 | .masthead h3 {
143 | margin-bottom:5px;
144 | font-size:170%;
145 | }
146 | hr {
147 | margin:0px 0px 20px 0px;
148 | }
149 |
150 | /* Make table headings and td.title bold */
151 | td.title, thead {
152 | font-weight:bold;
153 | }
154 |
155 | /* Format the right-side menu */
156 | #menu {
157 | margin-top:50px;
158 | font-size:11pt;
159 | padding-left:20px;
160 | }
161 |
162 | #menu .nav-header {
163 | font-size:12pt;
164 | color:#606060;
165 | margin-top:20px;
166 | }
167 |
168 | #menu li {
169 | line-height:25px;
170 | }
171 |
172 | /* Change font sizes for headings etc. */
173 | #main h1 { font-size: 26pt; margin:10px 0px 15px 0px; font-weight:400; }
174 | #main h2 { font-size: 20pt; margin:20px 0px 0px 0px; font-weight:400; }
175 | #main h3 { font-size: 14pt; margin:15px 0px 0px 0px; font-weight:600; }
176 | #main p { font-size: 11pt; margin:5px 0px 15px 0px; }
177 | #main ul { font-size: 11pt; margin-top:10px; }
178 | #main li { font-size: 11pt; margin: 5px 0px 5px 0px; }
179 | #main strong { font-weight:700; }
180 |
181 | /*--------------------------------------------------------------------------
182 | Formatting for API reference
183 | /*--------------------------------------------------------------------------*/
184 |
185 | .type-list .type-name, .module-list .module-name {
186 | width:25%;
187 | font-weight:bold;
188 | }
189 | .member-list .member-name {
190 | width:35%;
191 | }
192 | #main .xmldoc h2 {
193 | font-size:14pt;
194 | margin:10px 0px 0px 0px;
195 | }
196 | #main .xmldoc h3 {
197 | font-size:12pt;
198 | margin:10px 0px 0px 0px;
199 | }
200 | .github-link {
201 | float:right;
202 | text-decoration:none;
203 | }
204 | .github-link img {
205 | border-style:none;
206 | margin-left:10px;
207 | }
208 | .github-link .hover { display:none; }
209 | .github-link:hover .hover { display:block; }
210 | .github-link .normal { display: block; }
211 | .github-link:hover .normal { display: none; }
212 |
213 | /*--------------------------------------------------------------------------
214 | Links
215 | /*--------------------------------------------------------------------------*/
216 |
217 | h1 a, h1 a:hover, h1 a:focus,
218 | h2 a, h2 a:hover, h2 a:focus,
219 | h3 a, h3 a:hover, h3 a:focus,
220 | h4 a, h4 a:hover, h4 a:focus,
221 | h5 a, h5 a:hover, h5 a:focus,
222 | h6 a, h6 a:hover, h6 a:focus { color : inherit; text-decoration : inherit; outline:none }
223 |
224 | /*--------------------------------------------------------------------------
225 | Additional formatting for the homepage
226 | /*--------------------------------------------------------------------------*/
227 |
228 | #nuget {
229 | margin-top:20px;
230 | font-size: 11pt;
231 | padding:20px;
232 | }
233 |
234 | #nuget pre {
235 | font-size:11pt;
236 | -moz-border-radius: 0px;
237 | -webkit-border-radius: 0px;
238 | border-radius: 0px;
239 | background: #404040;
240 | border-style:none;
241 | color: #e0e0e0;
242 | margin-top:15px;
243 | }
244 |
--------------------------------------------------------------------------------
/test/Tests/LensesCompatibility.fs:
--------------------------------------------------------------------------------
1 | module Tests.LensesCompatibility
2 | open System
3 | open System.Collections.Generic
4 | open System.Linq
5 | open Fuchu
6 | open Fleece
7 | open FSharpPlus
8 | open FSharpPlus.Lens
9 |
10 | #if FSHARPDATA
11 | open FSharp.Data
12 | open Fleece.FSharpData
13 | open Fleece.FSharpData.Operators
14 | open Fleece.FSharpData.Lens
15 |
16 | type FdEncoding = Fleece.FSharpData.Encoding
17 | let JString = (JString >> FdEncoding.Unwrap)
18 |
19 | #endif
20 |
21 |
22 | #if NEWTONSOFT
23 | open Fleece.Newtonsoft
24 | open Fleece.Newtonsoft.Operators
25 | open Fleece.Newtonsoft.Lens
26 | open Newtonsoft.Json
27 | open Newtonsoft.Json.Linq
28 |
29 | type NsjEncoding = Fleece.Newtonsoft.Encoding
30 | let JString = (JString >> NsjEncoding.Unwrap)
31 |
32 | #endif
33 |
34 | #if SYSTEMJSON
35 | open System.Json
36 | open Fleece.SystemJson
37 | open Fleece.SystemJson.Operators
38 | open Fleece.SystemJson.Lens
39 |
40 | type SjEncoding = Fleece.SystemJson.Encoding
41 | let JString = (JString >> SjEncoding.Unwrap)
42 |
43 | #endif
44 |
45 | #if SYSTEMTEXTJSON
46 | open Fleece.SystemTextJson
47 | open Fleece.SystemTextJson.Operators
48 | open System.Text.Json
49 | open Fleece.SystemTextJson.Lens
50 |
51 | type StjEncoding = Fleece.SystemTextJson.Encoding
52 | let JString = (JString >> StjEncoding.Unwrap)
53 |
54 | #endif
55 |
56 | let strCleanUp x = System.Text.RegularExpressions.Regex.Replace(x, @"\s|\r\n?|\n", "")
57 |
58 | let tests = [
59 | testList "key" [
60 | test "example 1: read first key" {
61 | let actual = JsonValue.Parse( "{\"a\": true, \"b\": 200}" ) ^? (_jkey "a" << _JBool)
62 | let expected = true
63 | Assert.Equal("item", Some expected, actual)
64 | }
65 | test "example 2: read second key" {
66 | let actual = JsonValue.Parse( "{\"a\": true, \"b\": 200}" ) ^? (_jkey "b" << _JNumber)
67 | let expected = 200m
68 | Assert.Equal("item", Some expected, actual)
69 | }
70 | test "example 3: read with missing key" {
71 | let actual = JsonValue.Parse( "{\"a\": true, \"b\": 200}" ) ^? (_jkey "c" << _JNumber)
72 | Assert.Equal("item", None, actual)
73 | }
74 | test "example 4.1: write with missing key" {
75 | let actual = JsonValue.Parse( "{\"a\": true, \"b\": 200}" )|> (_jkey "c" ) .-> JString "a"
76 | let expected = JsonValue.Parse ("{\"a\": true, \"b\": 200, \"c\":\"a\"}")
77 | Assert.Equal("item", strCleanUp (string expected), strCleanUp (string actual))
78 | }
79 | test "example 4.2: write with missing key" { //TODO: Fix
80 | let actual = JsonValue.Parse( "{\"a\": true, \"b\": 200}" )|> (_jkey "c" << _JString) .-> "a"
81 | let expected = JsonValue.Parse ("{\"a\": true, \"b\": 200, \"c\":\"a\"}")
82 | //Assert.Equal("item", string expected, string actual)
83 | printfn "todo: %A ~ %A" expected actual
84 | }
85 | test "example 5: write existing key" {
86 | let actual = JsonValue.Parse( "{\"a\": true, \"b\": 200}" )|> (_jkey "a" << _JBool) .-> false
87 | let expected = JsonValue.Parse ("{\"a\": false, \"b\": 200}")
88 | Assert.Equal("item", strCleanUp (string expected), strCleanUp (string actual))
89 | }
90 | test "example 6: read key from a different type" {
91 | let actual = JsonValue.Parse( "[1,2,3]" ) ^? _jkey "a"
92 | Assert.Equal("item", true, actual.IsNone)
93 | }
94 |
95 | ]
96 | testList "_String" [
97 | test "example 1" {
98 | let actual = JsonValue.Parse ("{\"a\": \"xyz\", \"b\": true}") ^? (_jkey "a" << _JString)
99 | let expected = "xyz"
100 | Assert.Equal("item", Some expected, actual)
101 | }
102 | test "example 2" {
103 | let actual = JsonValue.Parse ("{\"a\": \"xyz\", \"b\": true}") ^? (_jkey "b" << _JString)
104 | Assert.Equal("item", None, actual)
105 | }
106 | test "example 3" {
107 | let actual = JString "a" |> _JString .-> "b"
108 | let expected = JString "b"
109 | Assert.Equal("item", string expected, string actual)
110 | }
111 | ]
112 | testList "_Number" [
113 | test "example 1" {
114 | let actual = JsonValue.Parse ("{\"a\": 100, \"b\": true}") ^? (_jkey "a" << _JNumber)
115 | let expected = 100m
116 | Assert.Equal("item", Some expected, actual)
117 | }
118 | test "example 2: write" {
119 | let actual = JsonValue.Parse ("{\"a\": 100, \"b\": true}") |> (_jkey "a" << _JNumber) .-> 200m
120 | let expected =
121 | #if NEWTONSOFT
122 | "{\"a\": 200.0, \"b\": true}"
123 | #else
124 | "{\"a\": 200, \"b\": true}"
125 | #endif
126 | Assert.Equal("item", strCleanUp (string (JsonValue.Parse expected)), strCleanUp (string actual))
127 | }
128 | ]
129 | testList "array" [
130 | test "example 1" {
131 | let actual = JsonValue.Parse ("[\"a\"]") ^? (_jnth 0 << _JString)
132 | let expected = "a"
133 | Assert.Equal("item", Some expected, actual)
134 | }
135 | test "example 2" {
136 | let actual = JsonValue.Parse ("[123]") ^? (_jnth 0 << _JNumber)
137 | let expected = 123m
138 | Assert.Equal("item", Some expected, actual)
139 | }
140 | test "example 3: read for missing index" {
141 | let actual = JsonValue.Parse ("[1,2,3]") ^? (_jnth 4 << _JNumber)
142 | Assert.Equal("item", None, actual)
143 | }
144 | test "example 4: write" {
145 | let actual = JsonValue.Parse ("[1,2,3]") |> (_jnth 1 << _JNumber) .-> 2.5m
146 | let expected = JsonValue.Parse ("[1,2.5,3]")
147 | Assert.Equal("item", string expected, string actual)
148 | }
149 | test "example 5: write for missing index" {
150 | let actual = JsonValue.Parse ("[1]") |> (_jnth 1 << _JString) .-> "a"
151 | let expected = JsonValue.Parse ("[1]")
152 | Assert.Equal("item", string expected, string actual)
153 | }
154 | ]
155 | ]
--------------------------------------------------------------------------------
/docsrc/content/codec.fsx:
--------------------------------------------------------------------------------
1 | (*** hide ***)
2 | // This block of code is omitted in the generated HTML documentation. Use
3 | // it to define helpers that you do not want to show in the documentation.
4 | #r "nuget: System.Json, 4.7.1"
5 | #r "nuget: FSharpPlus, 1.2.2"
6 | #r @"../../src/Fleece.SystemJson/bin/Release/netstandard2.0/Fleece.dll"
7 | #r @"../../src/Fleece.SystemJson/bin/Release/netstandard2.0/Fleece.SystemJson.dll"
8 |
9 | (**
10 |
11 | ## CODEC
12 |
13 | ```f#
14 | #r "nuget: Fleece.SystemJson"
15 | ```
16 | *)
17 |
18 | open Fleece
19 | open Fleece.SystemJson
20 | open Fleece.SystemJson.Operators
21 |
22 | (**
23 | For types that deserialize to Json Objets, typically (but not limited to) records, you can alternatively use codecs and have a single method which maps between fields and values.
24 | *)
25 |
26 | type Person = {
27 | name : string * string
28 | age : int option
29 | children: Person list }
30 | with
31 | static member JsonObjCodec =
32 | fun f l a c -> { name = (f, l); age = a; children = c }
33 | jreq "firstName" (fun x -> Some (fst x.name))
34 | <*> jreq "lastName" (fun x -> Some (snd x.name))
35 | <*> jopt "age" (fun x -> x.age) // Optional fields: use 'jopt'
36 | <*> jreq "children" (fun x -> Some x.children)
37 |
38 |
39 | let p = {name = ("John", "Doe"); age = None; children = [{name = ("Johnny", "Doe"); age = Some 21; children = []}]}
40 | //printfn "%s" (string (toJson p))
41 |
42 | let john = ofJsonText """{
43 | "children": [{
44 | "children": [],
45 | "age": 21,
46 | "lastName": "Doe",
47 | "firstName": "Johnny"
48 | }],
49 | "lastName": "Doe",
50 | "firstName": "John"
51 | }"""
52 |
53 | (**
54 | If you prefer you can write the same with a codec computation expression:
55 | *)
56 |
57 | type PersonF = {
58 | name : string * string
59 | age : int option
60 | children: PersonF list }
61 | with
62 | static member JsonObjCodec = codec {
63 | let! f = jreq "firstName" (fun x -> Some (fst x.name))
64 | and! l = jreq "lastName" (fun x -> Some (snd x.name))
65 | and! a = jopt "age" (fun x -> x.age)
66 | and! c = jreq "children" (fun x -> Some x.children)
67 | return { name = (f, l); age = a; children = c }
68 | }
69 |
70 | (**
71 | Both approaches build a codec from the same pieces:
72 |
73 | - A constructor function that builds a new record from deserialized pieces
74 | - A sequence of field specifications with `jfield/jfieldOpt` or `jreq/jot`.
75 | These specs take a field name and a function for getting that fields value from a record instance.
76 |
77 | Discriminated unions can be modeled with alternatives:
78 | *)
79 |
80 | type Shape =
81 | | Rectangle of width : float * length : float
82 | | Circle of radius : float
83 | | Prism of width : float * float * height : float
84 | with
85 | static member JsonObjCodec =
86 | Rectangle jreq "rectangle" (function Rectangle (x, y) -> Some (x, y) | _ -> None)
87 | <|> ( Circle jreq "radius" (function Circle x -> Some x | _ -> None) )
88 | <|> ( Prism jreq "prism" (function Prism (x, y, z) -> Some (x, y, z) | _ -> None) )
89 | (**
90 | or using the codec computation expression:
91 | *)
92 |
93 | type ShapeC =
94 | | Rectangle of width : float * length : float
95 | | Circle of radius : float
96 | | Prism of width : float * float * height : float
97 | with
98 | static member JsonObjCodec = codec {
99 | Rectangle jreq "rectangle" (function Rectangle (x, y) -> Some (x, y) | _ -> None)
100 | Circle jreq "radius" (function Circle x -> Some x | _ -> None)
101 | Prism jreq "prism" (function Prism (x, y, z) -> Some (x, y, z) | _ -> None)
102 | }
103 |
104 | (**
105 | What's happening here is that we're getting a Codec to/from a Json Object (not neccesarily a JsonValue) which Fleece is able to take it and fill the gap by composing it with a codec from JsonObject to/from JsonValue.
106 |
107 | For DUs that carry no data, a function is still necessary:
108 | *)
109 |
110 | type CompassDirection =
111 | | North
112 | | East
113 | | South
114 | | West
115 | with
116 | static member JsonObjCodec = codec {
117 | (fun () -> North) jreq "north" (function North -> Some () | _ -> None)
118 | (fun () -> South) jreq "south" (function South -> Some () | _ -> None)
119 | (fun () -> East) jreq "east" (function East -> Some () | _ -> None)
120 | (fun () -> West) jreq "west" (function West -> Some () | _ -> None)
121 | }
122 |
123 |
124 | (**
125 | A common way to represent algebraic data types in JSON is to use a type tag.
126 | For example:
127 | **)
128 |
129 | let someShapes = """
130 | [
131 | {
132 | "type": "rectangle",
133 | "width": 8.8,
134 | "length": 12.0
135 | },
136 | {
137 | "type": "circle",
138 | "radius": 37.8
139 | },
140 | {
141 | "type": "prism",
142 | "width": [10.0, 23.0],
143 | "height": 9.10
144 | }
145 | ]
146 | """
147 |
148 | open FSharpPlus
149 | open FSharpPlus.Operators
150 |
151 |
152 | type ShapeD =
153 | | Rectangle of width : float * length : float
154 | | Circle of radius : float
155 | | Prism of width : float * float * height : float
156 | with
157 | static member JsonObjCodec =
158 | /// Derives a field codec for a required field and value
159 | let inline jreqValue prop value codec =
160 | let matchPropValue o =
161 | match IReadOnlyDictionary.tryGetValue prop o with
162 | | Some a when ofJson a = Ok value -> Ok o
163 | | Some a -> Decode.Fail.invalidValue a value
164 | | None -> Decode.Fail.propertyNotFound prop o
165 | codec
166 | |> Codec.compose (
167 | matchPropValue <->
168 | fun (encoded: PropertyList) ->
169 | if encoded.Count = 0 then encoded // we have not encoded anything so no need to add property and value
170 | else PropertyList [|prop, toJson value|] ++ encoded
171 | )
172 |
173 | jchoice
174 | [
175 | fun w l -> Rectangle (w, l)
176 | jreq "width" (function Rectangle(w, _) -> Some w | _ -> None)
177 | <*> jreq "length" (function Rectangle(_, l) -> Some l | _ -> None)
178 | |> jreqValue "type" "rectangle"
179 |
180 | Circle
181 | jreq "radius" (function Circle r -> Some r | _ -> None)
182 | |> jreqValue "type" "circle"
183 |
184 | fun (w, w2) h -> Prism (w, w2, h)
185 | jreq "width" (function Prism (x, y, _) -> Some (x, y) | _ -> None)
186 | <*> jreq "height" (function Prism (_, _, h) -> Some h | _ -> None)
187 | |> jreqValue "type" "prism"
188 | ]
189 |
190 | let parsedShapedD = ofJsonText someShapes
191 |
192 | (**
193 | We can manipulate codecs by using functions in the Codec module. Here's an example:
194 | *)
195 | open System.Text
196 | let pf : PersonF= {name = ("John", "Doe"); age = None; children = [{name = ("Johnny", "Doe"); age = Some 21; children = []}]}
197 |
198 | let personBytesCodec =
199 | let getString (bytes:byte array) = Encoding.UTF8.GetString bytes
200 | PersonF.JsonObjCodec
201 | |> Codec.compose jsonObjToValueCodec // this is the codec that fills the gap to/from JsonValue
202 | |> Codec.compose jsonValueToTextCodec // this is a codec between JsonValue and JsonText
203 | |> Codec.invmap getString Encoding.UTF8.GetBytes // This is a pair of of isomorphic functions
204 |
205 | let bytePerson = Codec.encode personBytesCodec pf
206 | // val bytePerson : byte [] = [|123uy; 13uy; 10uy; 32uy; 32uy; ... |]
207 | let p' = Codec.decode personBytesCodec bytePerson
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Fleece
2 | ======
3 |
4 | Fleece is a JSON mapper for F#. It simplifies mapping from a Json library's JsonValue onto your types, and mapping from your types onto JsonValue.
5 |
6 | The Json library could be [System.Json](http://bit.ly/1axIBoA), [System.Text.Json](https://docs.microsoft.com/en-us/dotnet/api/system.text.json), [FSharp.Data](http://fsharp.github.io/FSharp.Data/)'s or [NewtonSoft's Json.NET](https://www.newtonsoft.com/json).
7 |
8 | Its design is strongly influenced by Haskell's [Aeson](http://hackage.haskell.org/package/aeson-0.7.0.0/docs/Data-Aeson.html). Like Aeson, Fleece is designed around two typeclasses (in [FSharpPlus](https://github.com/fsprojects/FSharpPlus) style) ToJson and OfJson.
9 |
10 | ### Download binaries
11 | * [Fleece core](https://www.nuget.org/packages/Fleece)
12 | * [For System.Json](https://www.nuget.org/packages/Fleece.SystemJson/)
13 | * [For System.Text.Json](https://www.nuget.org/packages/Fleece.SystemTextJson/)
14 | * [For FSharp.Data](https://www.nuget.org/packages/Fleece.FSharpData/)
15 | * [For NewtonSoft](https://www.nuget.org/packages/Fleece.NewtonSoftJson/)
16 |
17 | ### Example
18 |
19 | For example, given this data type:
20 |
21 | ```fsharp
22 | type Person = {
23 | Name: string
24 | Age: int
25 | Children: Person list
26 | }
27 | ```
28 |
29 | You can map it to JSON like this:
30 |
31 | ```fsharp
32 | open Fleece
33 | open Fleece.Operators
34 |
35 | type Person with
36 | static member ToJson (x: Person) =
37 | jobj [
38 | "name" .= x.Name
39 | "age" .= x.Age
40 | "children" .= x.Children
41 | ]
42 |
43 | let p =
44 | { Person.Name = "John"
45 | Age = 44
46 | Children =
47 | [
48 | { Person.Name = "Katy"
49 | Age = 5
50 | Children = [] }
51 | { Person.Name = "Johnny"
52 | Age = 7
53 | Children = [] }
54 | ] }
55 |
56 | // Test with System.Text.Json
57 |
58 | open Fleece.SystemTextJson
59 | printfn "%s" (toJsonText p)
60 | ```
61 |
62 | And you can map it from JSON like this:
63 |
64 | ```fsharp
65 | type Person with
66 | static member OfJson json =
67 | match json with
68 | | JObject o ->
69 | let name = o .@ "name"
70 | let age = o .@ "age"
71 | let children = o .@ "children"
72 | match name, age, children with
73 | | Decode.Success name, Decode.Success age, Decode.Success children ->
74 | Decode.Success {
75 | Person.Name = name
76 | Age = age
77 | Children = children
78 | }
79 | | x -> Error <| Uncategorized (sprintf "Error parsing person: %A" x)
80 | | x -> Decode.Fail.objExpected x
81 |
82 | let john : Person ParseResult = ofJsonText """{"name": "John", "age": 44, "children": [{"name": "Katy", "age": 5, "children": []}, {"name": "Johnny", "age": 7, "children": []}]}"""
83 | ```
84 |
85 | Though it's much easier to do this in a monadic or applicative way. For example, using [FSharpPlus](https://github.com/fsprojects/FSharpPlus) (which is already a dependency of Fleece):
86 |
87 | ```fsharp
88 | open FSharpPlus
89 |
90 | type Person with
91 | static member Create name age children = { Person.Name = name; Age = age; Children = children }
92 |
93 | static member OfJson json =
94 | match json with
95 | | JObject o -> Person.Create (o .@ "name") <*> (o .@ "age") <*> (o .@ "children")
96 | | x -> Decode.Fail.objExpected x
97 |
98 | ```
99 |
100 | or with applicatives:
101 |
102 |
103 | ```fsharp
104 |
105 | open FSharpPlus
106 |
107 | type Person with
108 | static member OfJson json =
109 | match json with
110 | | JObject o ->
111 | monad {
112 | let! name = o .@ "name"
113 | and! age = o .@ "age"
114 | and! children = o .@ "children"
115 | return {
116 | Person.Name = name
117 | Age = age
118 | Children = children
119 | }
120 | }
121 | | x -> Decode.Fail.objExpected x
122 | ```
123 |
124 | Or you can use the Choice monad/applicative in [FSharpx.Extras](https://github.com/fsprojects/FSharpx.Extras) instead, if you prefer.
125 |
126 | You can see more examples in the [EdmundsNet](https://github.com/mausch/EdmundsNet) project.
127 |
128 |
129 | ### CODEC
130 |
131 | For types that deserialize to Json Objets, typically (but not limited to) records, you can alternatively use codecs and have a single method which maps between fields and values.
132 |
133 |
134 | ```fsharp
135 |
136 | type Person = {
137 | name : string * string
138 | age : int option
139 | children: Person list }
140 | with
141 | static member get_Codec () =
142 | fun f l a c -> { name = (f, l); age = a; children = c }
143 | jreq "firstName" (Some << fun x -> fst x.name)
144 | <*> jreq "lastName" (Some << fun x -> snd x.name)
145 | <*> jopt "age" (fun x -> x.age) // Optional fields can use 'jopt'
146 | <*> jreq "children" (fun x -> Some x.children)
147 | |> ofObjCodec
148 |
149 | let john: Person ParseResult = ofJsonText """{"name": "John", "age": 44, "children": [{"name": "Katy", "age": 5, "children": []}, {"name": "Johnny", "age": 7, "children": []}]}"""
150 | ```
151 |
152 |
153 | Discriminated unions can be modeled with alternatives:
154 | ```fsharp
155 | type Shape =
156 | | Rectangle of width : float * length : float
157 | | Circle of radius : float
158 | | Prism of width : float * float * height : float
159 | with
160 | static member get_Codec () =
161 | (Rectangle jreq "rectangle" (function Rectangle (x, y) -> Some (x, y) | _ -> None))
162 | <|> (Circle jreq "radius" (function Circle x -> Some x | _ -> None))
163 | <|> (Prism jreq "prism" (function Prism (x, y, z) -> Some (x, y, z) | _ -> None))
164 | |> ofObjCodec
165 | ```
166 |
167 |
168 | or using the jchoice combinator:
169 | ```fsharp
170 | type Shape with
171 | static member JsonObjCodec =
172 | jchoice
173 | [
174 | Rectangle jreq "rectangle" (function Rectangle (x, y) -> Some (x, y) | _ -> None)
175 | Circle jreq "radius" (function Circle x -> Some x | _ -> None)
176 | Prism jreq "prism" (function Prism (x, y, z) -> Some (x, y, z) | _ -> None)
177 | ]
178 | |> ofObjCodec
179 |
180 | ```
181 |
182 | But codecs for both types can easily be written with the codec computation expressions
183 |
184 | ```fsharp
185 |
186 | type Person = {
187 | name : string * string
188 | age : int option
189 | children: Person list }
190 | with
191 | static member get_Codec () =
192 | codec {
193 | let! f = jreq "firstName" (Some << fun x -> fst x.name)
194 | and! l = jreq "lastName" (Some << fun x -> snd x.name)
195 | and! a = jopt "age" (fun x -> x.age) // Optional fields can use 'jopt'
196 | and! c = jreq "children" (fun x -> Some x.children)
197 | return { name = (f, l); age = a; children = c } }
198 | |> ofObjCodec
199 |
200 |
201 | type Shape =
202 | | Rectangle of width : float * length : float
203 | | Circle of radius : float
204 | | Prism of width : float * float * height : float
205 | with
206 | static member get_Codec () =
207 | codec {
208 | Rectangle jreq "rectangle" (function Rectangle (x, y) -> Some (x, y) | _ -> None)
209 | Circle jreq "radius" (function Circle x -> Some x | _ -> None)
210 | Prism jreq "prism" (function Prism (x, y, z) -> Some (x, y, z) | _ -> None)
211 | }
212 | |> ofObjCodec
213 |
214 | ```
215 |
216 |
217 | What's happening here is that we're getting a Codec to/from a Json Object (not neccesarily a JsonValue) which Fleece is able to take it and fill the gap by composing it with a codec from JsonObject to/from JsonValue.
218 |
219 | We can also do that by hand, we can manipulate codecs by using functions in the Codec module. Here's an example:
220 |
221 | ```fsharp
222 | open System.Text
223 | open Fleece.SystemTextJson.Operators
224 |
225 | type Person = {
226 | name : string * string
227 | age : int option
228 | children: Person list }
229 | with
230 | static member JsonObjCodec: Codec, Person> = codec {
231 | let! f = jreq "firstName" (Some << fun x -> fst x.name)
232 | and! l = jreq "lastName" (Some << fun x -> snd x.name)
233 | and! a = jopt "age" (fun x -> x.age) // Optional fields can use 'jopt'
234 | and! c = jreq "children" (fun x -> Some x.children)
235 | return { name = (f, l); age = a; children = c } }
236 |
237 | let personBytesCodec =
238 | Person.JsonObjCodec
239 | |> Codec.compose jsonObjToValueCodec // this is the codec that fills the gap to/from JsonValue
240 | |> Codec.compose jsonValueToTextCodec // this is a codec between JsonValue and JsonText
241 | |> Codec.invmap (Encoding.UTF8.GetString: byte [] -> string) Encoding.UTF8.GetBytes // This is a pair of of isomorphic functions
242 |
243 | let p = { name = "John", "Smith"; age = Some 42; children = [] }
244 |
245 |
246 | let bytePerson = Codec.encode personBytesCodec p
247 | // val bytePerson : byte [] = [|123uy; 34uy; 102uy; 105uy; 114uy; 115uy; ... |]
248 | let p' = Codec.decode personBytesCodec bytePerson
249 | ```
250 |
251 | ### Combinators
252 |
253 | So far we've seen how Fleece is capable of encoding/decoding by deriving automatically a codec from static members in the type.
254 |
255 | But for those cases where we don't have control over the types (extension members won't be taken into account) we can explicitly specify combinators.
256 |
257 | To do so, a set of the available functions exists, ending with the `With` suffix, which accepts a combinator as first parameter:
258 |
259 | ```fsharp
260 | type Color = Red | Blue | White
261 |
262 | type Car = {
263 | Id : string
264 | Color : Color
265 | Kms : int }
266 |
267 | let colorDecoder = function
268 | | JString "red" -> Decode.Success Red
269 | | JString "blue" -> Decode.Success Blue
270 | | JString "white" -> Decode.Success White
271 | | JString x as v -> Decode.Fail.invalidValue v ("Wrong color: " + x)
272 | | x -> Decode.Fail.strExpected x
273 |
274 | let colorEncoder = function
275 | | Red -> JString "red"
276 | | Blue -> JString "blue"
277 | | White -> JString "white"
278 |
279 | let colorCodec () = colorDecoder <-> colorEncoder
280 |
281 | let carCodec () =
282 | codec {
283 | let! i = jreqWith Codecs.string "id" (fun x -> Some x.Id)
284 | and! c = jreqWith colorCodec "color" (fun x -> Some x.Color)
285 | and! k = jreqWith Codecs.int "kms" (fun x -> Some x.Kms)
286 | return { Id = i; Color = c; Kms = k }
287 | }
288 | |> Codec.compose (Codecs.propList Codecs.id)
289 |
290 | let car = { Id = "xyz"; Color = Red; Kms = 0 }
291 |
292 | let jsonCar : Fleece.SystemTextJson.Encoding = Codec.encode (carCodec ()) car
293 | // val jsonCar: SystemTextJson.Encoding = {"id":"xyz","color":"red","kms":0}
294 | ```
295 |
296 | ### Json Lenses
297 |
298 | Json lenses allow to focus on a specific part of the json structure to perform operations like view, write and update.
299 |
300 | For a quick reference have a look at [this test file](https://github.com/fsprojects/Fleece/blob/master/test/Tests/Lenses.fs)
301 |
302 |
303 | ## Maintainer(s)
304 |
305 | - [@mausch](https://github.com/mausch)
306 | - [@gusty](https://github.com/gusty)
307 | - [@wallymathieu](https://github.com/wallymathieu)
308 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/Fleece.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.31727.386
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Fleece.FSharpData", "src\Fleece.FSharpData\Fleece.FSharpData.fsproj", "{972BE606-278E-4789-B789-044EE68A3EED}"
7 | EndProject
8 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Tests.SystemJson", "test\Tests.SystemJson\Tests.SystemJson.fsproj", "{20FDBC9C-1D0C-441A-9A8A-64D16158DBAD}"
9 | EndProject
10 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Tests.FSharpData", "test\Tests.FSharpData\Tests.FSharpData.fsproj", "{FE7182BB-793C-491B-9F41-73919FC990DA}"
11 | EndProject
12 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Fleece.NewtonsoftJson", "src\Fleece.NewtonsoftJson\Fleece.NewtonsoftJson.fsproj", "{739A5738-C3EF-43AB-BF4B-32BA27D748F8}"
13 | EndProject
14 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Tests.NewtonsoftJson", "test\Tests.NewtonsoftJson\Tests.NewtonsoftJson.fsproj", "{2EC2C684-1C70-4D7C-9CA4-9CF0531FC100}"
15 | EndProject
16 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "IntegrationCompilationTests", "test\IntegrationCompilationTests\IntegrationCompilationTests.fsproj", "{39FA79C4-DD47-47FE-8D50-9C4E9B66DEB8}"
17 | EndProject
18 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Fleece.SystemJson", "src\Fleece.SystemJson\Fleece.SystemJson.fsproj", "{632614D7-05B6-4023-9D24-C1C084DB0233}"
19 | EndProject
20 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docsrc", "docsrc", "{4FCC9202-EFE7-4969-92CD-9FD9DA8C5A54}"
21 | EndProject
22 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "docsTool", "docsrc\tool\docsTool.fsproj", "{5516C542-3865-44F1-AB9F-3703ADA61E63}"
23 | EndProject
24 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "docs", "docsrc\docs\docs.fsproj", "{FFEB1FB1-B9CE-41D7-BE00-B39A1DB809D6}"
25 | EndProject
26 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Benchmarks", "Benchmarks\Benchmarks.fsproj", "{4C177C9C-48C4-4592-BD52-5A14980763EE}"
27 | EndProject
28 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Fleece.SystemTextJson", "src\Fleece.SystemTextJson\Fleece.SystemTextJson.fsproj", "{30D506A7-21D9-4E4D-A4E9-2D77D0F16047}"
29 | EndProject
30 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Tests.SystemTextJson", "test\Tests.SystemTextJson\Tests.SystemTextJson.fsproj", "{0998A221-9056-49FF-82CF-B18F576E6E5C}"
31 | EndProject
32 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fleece", "src\Fleece\Fleece.fsproj", "{41090B14-2BE9-481A-BEA6-062BE33F4822}"
33 | EndProject
34 | Global
35 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
36 | Debug|Any CPU = Debug|Any CPU
37 | Debug|x64 = Debug|x64
38 | Debug|x86 = Debug|x86
39 | Release|Any CPU = Release|Any CPU
40 | Release|x64 = Release|x64
41 | Release|x86 = Release|x86
42 | EndGlobalSection
43 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
44 | {972BE606-278E-4789-B789-044EE68A3EED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
45 | {972BE606-278E-4789-B789-044EE68A3EED}.Debug|Any CPU.Build.0 = Debug|Any CPU
46 | {972BE606-278E-4789-B789-044EE68A3EED}.Debug|x64.ActiveCfg = Debug|Any CPU
47 | {972BE606-278E-4789-B789-044EE68A3EED}.Debug|x64.Build.0 = Debug|Any CPU
48 | {972BE606-278E-4789-B789-044EE68A3EED}.Debug|x86.ActiveCfg = Debug|Any CPU
49 | {972BE606-278E-4789-B789-044EE68A3EED}.Debug|x86.Build.0 = Debug|Any CPU
50 | {972BE606-278E-4789-B789-044EE68A3EED}.Release|Any CPU.ActiveCfg = Release|Any CPU
51 | {972BE606-278E-4789-B789-044EE68A3EED}.Release|Any CPU.Build.0 = Release|Any CPU
52 | {972BE606-278E-4789-B789-044EE68A3EED}.Release|x64.ActiveCfg = Release|Any CPU
53 | {972BE606-278E-4789-B789-044EE68A3EED}.Release|x64.Build.0 = Release|Any CPU
54 | {972BE606-278E-4789-B789-044EE68A3EED}.Release|x86.ActiveCfg = Release|Any CPU
55 | {972BE606-278E-4789-B789-044EE68A3EED}.Release|x86.Build.0 = Release|Any CPU
56 | {20FDBC9C-1D0C-441A-9A8A-64D16158DBAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
57 | {20FDBC9C-1D0C-441A-9A8A-64D16158DBAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
58 | {20FDBC9C-1D0C-441A-9A8A-64D16158DBAD}.Debug|x64.ActiveCfg = Debug|Any CPU
59 | {20FDBC9C-1D0C-441A-9A8A-64D16158DBAD}.Debug|x64.Build.0 = Debug|Any CPU
60 | {20FDBC9C-1D0C-441A-9A8A-64D16158DBAD}.Debug|x86.ActiveCfg = Debug|Any CPU
61 | {20FDBC9C-1D0C-441A-9A8A-64D16158DBAD}.Debug|x86.Build.0 = Debug|Any CPU
62 | {20FDBC9C-1D0C-441A-9A8A-64D16158DBAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
63 | {20FDBC9C-1D0C-441A-9A8A-64D16158DBAD}.Release|Any CPU.Build.0 = Release|Any CPU
64 | {20FDBC9C-1D0C-441A-9A8A-64D16158DBAD}.Release|x64.ActiveCfg = Release|Any CPU
65 | {20FDBC9C-1D0C-441A-9A8A-64D16158DBAD}.Release|x64.Build.0 = Release|Any CPU
66 | {20FDBC9C-1D0C-441A-9A8A-64D16158DBAD}.Release|x86.ActiveCfg = Release|Any CPU
67 | {20FDBC9C-1D0C-441A-9A8A-64D16158DBAD}.Release|x86.Build.0 = Release|Any CPU
68 | {FE7182BB-793C-491B-9F41-73919FC990DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
69 | {FE7182BB-793C-491B-9F41-73919FC990DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
70 | {FE7182BB-793C-491B-9F41-73919FC990DA}.Debug|x64.ActiveCfg = Debug|Any CPU
71 | {FE7182BB-793C-491B-9F41-73919FC990DA}.Debug|x64.Build.0 = Debug|Any CPU
72 | {FE7182BB-793C-491B-9F41-73919FC990DA}.Debug|x86.ActiveCfg = Debug|Any CPU
73 | {FE7182BB-793C-491B-9F41-73919FC990DA}.Debug|x86.Build.0 = Debug|Any CPU
74 | {FE7182BB-793C-491B-9F41-73919FC990DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
75 | {FE7182BB-793C-491B-9F41-73919FC990DA}.Release|Any CPU.Build.0 = Release|Any CPU
76 | {FE7182BB-793C-491B-9F41-73919FC990DA}.Release|x64.ActiveCfg = Release|Any CPU
77 | {FE7182BB-793C-491B-9F41-73919FC990DA}.Release|x64.Build.0 = Release|Any CPU
78 | {FE7182BB-793C-491B-9F41-73919FC990DA}.Release|x86.ActiveCfg = Release|Any CPU
79 | {FE7182BB-793C-491B-9F41-73919FC990DA}.Release|x86.Build.0 = Release|Any CPU
80 | {739A5738-C3EF-43AB-BF4B-32BA27D748F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
81 | {739A5738-C3EF-43AB-BF4B-32BA27D748F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
82 | {739A5738-C3EF-43AB-BF4B-32BA27D748F8}.Debug|x64.ActiveCfg = Debug|Any CPU
83 | {739A5738-C3EF-43AB-BF4B-32BA27D748F8}.Debug|x64.Build.0 = Debug|Any CPU
84 | {739A5738-C3EF-43AB-BF4B-32BA27D748F8}.Debug|x86.ActiveCfg = Debug|Any CPU
85 | {739A5738-C3EF-43AB-BF4B-32BA27D748F8}.Debug|x86.Build.0 = Debug|Any CPU
86 | {739A5738-C3EF-43AB-BF4B-32BA27D748F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
87 | {739A5738-C3EF-43AB-BF4B-32BA27D748F8}.Release|Any CPU.Build.0 = Release|Any CPU
88 | {739A5738-C3EF-43AB-BF4B-32BA27D748F8}.Release|x64.ActiveCfg = Release|Any CPU
89 | {739A5738-C3EF-43AB-BF4B-32BA27D748F8}.Release|x64.Build.0 = Release|Any CPU
90 | {739A5738-C3EF-43AB-BF4B-32BA27D748F8}.Release|x86.ActiveCfg = Release|Any CPU
91 | {739A5738-C3EF-43AB-BF4B-32BA27D748F8}.Release|x86.Build.0 = Release|Any CPU
92 | {2EC2C684-1C70-4D7C-9CA4-9CF0531FC100}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
93 | {2EC2C684-1C70-4D7C-9CA4-9CF0531FC100}.Debug|Any CPU.Build.0 = Debug|Any CPU
94 | {2EC2C684-1C70-4D7C-9CA4-9CF0531FC100}.Debug|x64.ActiveCfg = Debug|Any CPU
95 | {2EC2C684-1C70-4D7C-9CA4-9CF0531FC100}.Debug|x64.Build.0 = Debug|Any CPU
96 | {2EC2C684-1C70-4D7C-9CA4-9CF0531FC100}.Debug|x86.ActiveCfg = Debug|Any CPU
97 | {2EC2C684-1C70-4D7C-9CA4-9CF0531FC100}.Debug|x86.Build.0 = Debug|Any CPU
98 | {2EC2C684-1C70-4D7C-9CA4-9CF0531FC100}.Release|Any CPU.ActiveCfg = Release|Any CPU
99 | {2EC2C684-1C70-4D7C-9CA4-9CF0531FC100}.Release|Any CPU.Build.0 = Release|Any CPU
100 | {2EC2C684-1C70-4D7C-9CA4-9CF0531FC100}.Release|x64.ActiveCfg = Release|Any CPU
101 | {2EC2C684-1C70-4D7C-9CA4-9CF0531FC100}.Release|x64.Build.0 = Release|Any CPU
102 | {2EC2C684-1C70-4D7C-9CA4-9CF0531FC100}.Release|x86.ActiveCfg = Release|Any CPU
103 | {2EC2C684-1C70-4D7C-9CA4-9CF0531FC100}.Release|x86.Build.0 = Release|Any CPU
104 | {39FA79C4-DD47-47FE-8D50-9C4E9B66DEB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
105 | {39FA79C4-DD47-47FE-8D50-9C4E9B66DEB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
106 | {39FA79C4-DD47-47FE-8D50-9C4E9B66DEB8}.Debug|x64.ActiveCfg = Debug|Any CPU
107 | {39FA79C4-DD47-47FE-8D50-9C4E9B66DEB8}.Debug|x64.Build.0 = Debug|Any CPU
108 | {39FA79C4-DD47-47FE-8D50-9C4E9B66DEB8}.Debug|x86.ActiveCfg = Debug|Any CPU
109 | {39FA79C4-DD47-47FE-8D50-9C4E9B66DEB8}.Debug|x86.Build.0 = Debug|Any CPU
110 | {39FA79C4-DD47-47FE-8D50-9C4E9B66DEB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
111 | {39FA79C4-DD47-47FE-8D50-9C4E9B66DEB8}.Release|Any CPU.Build.0 = Release|Any CPU
112 | {39FA79C4-DD47-47FE-8D50-9C4E9B66DEB8}.Release|x64.ActiveCfg = Release|Any CPU
113 | {39FA79C4-DD47-47FE-8D50-9C4E9B66DEB8}.Release|x64.Build.0 = Release|Any CPU
114 | {39FA79C4-DD47-47FE-8D50-9C4E9B66DEB8}.Release|x86.ActiveCfg = Release|Any CPU
115 | {39FA79C4-DD47-47FE-8D50-9C4E9B66DEB8}.Release|x86.Build.0 = Release|Any CPU
116 | {632614D7-05B6-4023-9D24-C1C084DB0233}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
117 | {632614D7-05B6-4023-9D24-C1C084DB0233}.Debug|Any CPU.Build.0 = Debug|Any CPU
118 | {632614D7-05B6-4023-9D24-C1C084DB0233}.Debug|x64.ActiveCfg = Debug|Any CPU
119 | {632614D7-05B6-4023-9D24-C1C084DB0233}.Debug|x64.Build.0 = Debug|Any CPU
120 | {632614D7-05B6-4023-9D24-C1C084DB0233}.Debug|x86.ActiveCfg = Debug|Any CPU
121 | {632614D7-05B6-4023-9D24-C1C084DB0233}.Debug|x86.Build.0 = Debug|Any CPU
122 | {632614D7-05B6-4023-9D24-C1C084DB0233}.Release|Any CPU.ActiveCfg = Release|Any CPU
123 | {632614D7-05B6-4023-9D24-C1C084DB0233}.Release|Any CPU.Build.0 = Release|Any CPU
124 | {632614D7-05B6-4023-9D24-C1C084DB0233}.Release|x64.ActiveCfg = Release|Any CPU
125 | {632614D7-05B6-4023-9D24-C1C084DB0233}.Release|x64.Build.0 = Release|Any CPU
126 | {632614D7-05B6-4023-9D24-C1C084DB0233}.Release|x86.ActiveCfg = Release|Any CPU
127 | {632614D7-05B6-4023-9D24-C1C084DB0233}.Release|x86.Build.0 = Release|Any CPU
128 | {5516C542-3865-44F1-AB9F-3703ADA61E63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
129 | {5516C542-3865-44F1-AB9F-3703ADA61E63}.Debug|Any CPU.Build.0 = Debug|Any CPU
130 | {5516C542-3865-44F1-AB9F-3703ADA61E63}.Debug|x64.ActiveCfg = Debug|Any CPU
131 | {5516C542-3865-44F1-AB9F-3703ADA61E63}.Debug|x64.Build.0 = Debug|Any CPU
132 | {5516C542-3865-44F1-AB9F-3703ADA61E63}.Debug|x86.ActiveCfg = Debug|Any CPU
133 | {5516C542-3865-44F1-AB9F-3703ADA61E63}.Debug|x86.Build.0 = Debug|Any CPU
134 | {5516C542-3865-44F1-AB9F-3703ADA61E63}.Release|Any CPU.ActiveCfg = Release|Any CPU
135 | {5516C542-3865-44F1-AB9F-3703ADA61E63}.Release|x64.ActiveCfg = Release|Any CPU
136 | {5516C542-3865-44F1-AB9F-3703ADA61E63}.Release|x86.ActiveCfg = Release|Any CPU
137 | {FFEB1FB1-B9CE-41D7-BE00-B39A1DB809D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
138 | {FFEB1FB1-B9CE-41D7-BE00-B39A1DB809D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
139 | {FFEB1FB1-B9CE-41D7-BE00-B39A1DB809D6}.Debug|x64.ActiveCfg = Debug|Any CPU
140 | {FFEB1FB1-B9CE-41D7-BE00-B39A1DB809D6}.Debug|x64.Build.0 = Debug|Any CPU
141 | {FFEB1FB1-B9CE-41D7-BE00-B39A1DB809D6}.Debug|x86.ActiveCfg = Debug|Any CPU
142 | {FFEB1FB1-B9CE-41D7-BE00-B39A1DB809D6}.Debug|x86.Build.0 = Debug|Any CPU
143 | {FFEB1FB1-B9CE-41D7-BE00-B39A1DB809D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
144 | {FFEB1FB1-B9CE-41D7-BE00-B39A1DB809D6}.Release|x64.ActiveCfg = Release|Any CPU
145 | {FFEB1FB1-B9CE-41D7-BE00-B39A1DB809D6}.Release|x86.ActiveCfg = Release|Any CPU
146 | {4C177C9C-48C4-4592-BD52-5A14980763EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
147 | {4C177C9C-48C4-4592-BD52-5A14980763EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
148 | {4C177C9C-48C4-4592-BD52-5A14980763EE}.Debug|x64.ActiveCfg = Debug|Any CPU
149 | {4C177C9C-48C4-4592-BD52-5A14980763EE}.Debug|x64.Build.0 = Debug|Any CPU
150 | {4C177C9C-48C4-4592-BD52-5A14980763EE}.Debug|x86.ActiveCfg = Debug|Any CPU
151 | {4C177C9C-48C4-4592-BD52-5A14980763EE}.Debug|x86.Build.0 = Debug|Any CPU
152 | {4C177C9C-48C4-4592-BD52-5A14980763EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
153 | {4C177C9C-48C4-4592-BD52-5A14980763EE}.Release|Any CPU.Build.0 = Release|Any CPU
154 | {4C177C9C-48C4-4592-BD52-5A14980763EE}.Release|x64.ActiveCfg = Release|Any CPU
155 | {4C177C9C-48C4-4592-BD52-5A14980763EE}.Release|x64.Build.0 = Release|Any CPU
156 | {4C177C9C-48C4-4592-BD52-5A14980763EE}.Release|x86.ActiveCfg = Release|Any CPU
157 | {4C177C9C-48C4-4592-BD52-5A14980763EE}.Release|x86.Build.0 = Release|Any CPU
158 | {30D506A7-21D9-4E4D-A4E9-2D77D0F16047}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
159 | {30D506A7-21D9-4E4D-A4E9-2D77D0F16047}.Debug|Any CPU.Build.0 = Debug|Any CPU
160 | {30D506A7-21D9-4E4D-A4E9-2D77D0F16047}.Debug|x64.ActiveCfg = Debug|Any CPU
161 | {30D506A7-21D9-4E4D-A4E9-2D77D0F16047}.Debug|x64.Build.0 = Debug|Any CPU
162 | {30D506A7-21D9-4E4D-A4E9-2D77D0F16047}.Debug|x86.ActiveCfg = Debug|Any CPU
163 | {30D506A7-21D9-4E4D-A4E9-2D77D0F16047}.Debug|x86.Build.0 = Debug|Any CPU
164 | {30D506A7-21D9-4E4D-A4E9-2D77D0F16047}.Release|Any CPU.ActiveCfg = Release|Any CPU
165 | {30D506A7-21D9-4E4D-A4E9-2D77D0F16047}.Release|Any CPU.Build.0 = Release|Any CPU
166 | {30D506A7-21D9-4E4D-A4E9-2D77D0F16047}.Release|x64.ActiveCfg = Release|Any CPU
167 | {30D506A7-21D9-4E4D-A4E9-2D77D0F16047}.Release|x64.Build.0 = Release|Any CPU
168 | {30D506A7-21D9-4E4D-A4E9-2D77D0F16047}.Release|x86.ActiveCfg = Release|Any CPU
169 | {30D506A7-21D9-4E4D-A4E9-2D77D0F16047}.Release|x86.Build.0 = Release|Any CPU
170 | {0998A221-9056-49FF-82CF-B18F576E6E5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
171 | {0998A221-9056-49FF-82CF-B18F576E6E5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
172 | {0998A221-9056-49FF-82CF-B18F576E6E5C}.Debug|x64.ActiveCfg = Debug|Any CPU
173 | {0998A221-9056-49FF-82CF-B18F576E6E5C}.Debug|x64.Build.0 = Debug|Any CPU
174 | {0998A221-9056-49FF-82CF-B18F576E6E5C}.Debug|x86.ActiveCfg = Debug|Any CPU
175 | {0998A221-9056-49FF-82CF-B18F576E6E5C}.Debug|x86.Build.0 = Debug|Any CPU
176 | {0998A221-9056-49FF-82CF-B18F576E6E5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
177 | {0998A221-9056-49FF-82CF-B18F576E6E5C}.Release|Any CPU.Build.0 = Release|Any CPU
178 | {0998A221-9056-49FF-82CF-B18F576E6E5C}.Release|x64.ActiveCfg = Release|Any CPU
179 | {0998A221-9056-49FF-82CF-B18F576E6E5C}.Release|x64.Build.0 = Release|Any CPU
180 | {0998A221-9056-49FF-82CF-B18F576E6E5C}.Release|x86.ActiveCfg = Release|Any CPU
181 | {0998A221-9056-49FF-82CF-B18F576E6E5C}.Release|x86.Build.0 = Release|Any CPU
182 | {41090B14-2BE9-481A-BEA6-062BE33F4822}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
183 | {41090B14-2BE9-481A-BEA6-062BE33F4822}.Debug|Any CPU.Build.0 = Debug|Any CPU
184 | {41090B14-2BE9-481A-BEA6-062BE33F4822}.Debug|x64.ActiveCfg = Debug|Any CPU
185 | {41090B14-2BE9-481A-BEA6-062BE33F4822}.Debug|x64.Build.0 = Debug|Any CPU
186 | {41090B14-2BE9-481A-BEA6-062BE33F4822}.Debug|x86.ActiveCfg = Debug|Any CPU
187 | {41090B14-2BE9-481A-BEA6-062BE33F4822}.Debug|x86.Build.0 = Debug|Any CPU
188 | {41090B14-2BE9-481A-BEA6-062BE33F4822}.Release|Any CPU.ActiveCfg = Release|Any CPU
189 | {41090B14-2BE9-481A-BEA6-062BE33F4822}.Release|Any CPU.Build.0 = Release|Any CPU
190 | {41090B14-2BE9-481A-BEA6-062BE33F4822}.Release|x64.ActiveCfg = Release|Any CPU
191 | {41090B14-2BE9-481A-BEA6-062BE33F4822}.Release|x64.Build.0 = Release|Any CPU
192 | {41090B14-2BE9-481A-BEA6-062BE33F4822}.Release|x86.ActiveCfg = Release|Any CPU
193 | {41090B14-2BE9-481A-BEA6-062BE33F4822}.Release|x86.Build.0 = Release|Any CPU
194 | EndGlobalSection
195 | GlobalSection(SolutionProperties) = preSolution
196 | HideSolutionNode = FALSE
197 | EndGlobalSection
198 | GlobalSection(NestedProjects) = preSolution
199 | {5516C542-3865-44F1-AB9F-3703ADA61E63} = {4FCC9202-EFE7-4969-92CD-9FD9DA8C5A54}
200 | {FFEB1FB1-B9CE-41D7-BE00-B39A1DB809D6} = {4FCC9202-EFE7-4969-92CD-9FD9DA8C5A54}
201 | EndGlobalSection
202 | GlobalSection(ExtensibilityGlobals) = postSolution
203 | SolutionGuid = {7BFF4CAC-2021-49A7-A987-597287D8F9B1}
204 | EndGlobalSection
205 | EndGlobal
206 |
--------------------------------------------------------------------------------
/src/Fleece/Compatibility.fs:
--------------------------------------------------------------------------------
1 | #if FSHARPDATA
2 | namespace Fleece.FSharpData
3 | type JsonValue = FSharp.Data.JsonValue
4 | #endif
5 |
6 | #if NEWTONSOFT
7 | namespace Fleece.Newtonsoft
8 | #endif
9 |
10 | #if SYSTEMJSON
11 | namespace Fleece.SystemJson
12 | type JsonValue = System.Json.JsonValue
13 | #endif
14 |
15 | #if SYSTEMTEXTJSON
16 | namespace Fleece.SystemTextJson
17 | []
18 | type Extensions =
19 | static member Encoding x = Encoding.Wrap x
20 |
21 | []
22 | module JsonValue =
23 | let Parse (x: string) = let doc = System.Text.Json.JsonDocument.Parse x in doc.RootElement
24 |
25 | type JsonValue = System.Text.Json.JsonElement
26 | #endif
27 |
28 | open Fleece
29 | open Internals
30 | open FSharpPlus
31 | open FSharpPlus.Data
32 |
33 |
34 | #if SYSTEMTEXTJSON
35 | open Fleece.SystemTextJson.InternalHelpers
36 | #endif
37 |
38 |
39 | ///////////////////////
40 | // Main entry points //
41 | ///////////////////////
42 |
43 | []
44 | type Operators =
45 |
46 | /// Gets the json encoding representation of the value, using its default codec.
47 | static member inline toJson (x: 'T) : Encoding = toEncoding x
48 |
49 | /// Attempts to decode the value from its json encoding representation, using its default codec.
50 | static member inline ofJson (x: Encoding) : Result<'T, DecodeError> = ofEncoding x
51 |
52 | /// Gets the native json type representation of the value, using its default codec.
53 | static member inline toJsonValue (x: 'T) : JsonValue = toEncoding x |> Encoding.Unwrap
54 |
55 | /// Attempts to decode the value from its native json type representation, using its default codec.
56 | static member inline ofJsonValue (x: JsonValue) : Result<'T, DecodeError> = ofEncoding (Encoding x)
57 |
58 | /// Gets the json text representation of the value, using its default codec.
59 | static member inline toJsonText (x: 'T) = x |> Operators.toJson |> string
60 |
61 | /// Attempts to decode the value from its json text representation, using its default codec.
62 | static member inline ofJsonText (x: string) : Result<'T, DecodeError> = try (Encoding.Parse x |> ofEncoding) with e -> Decode.Fail.parseError e x
63 |
64 |
65 | ///////////////////
66 | // Compatibility //
67 | ///////////////////
68 |
69 | []
70 | module Operators =
71 |
72 | type JsonObject = Map
73 | type ParseResult<'t> = Result<'t, DecodeError>
74 |
75 | type Codec<'S1, 'S2, 't1, 't2> = Fleece.Codec<'S1, 'S2, 't1, 't2>
76 | type Codec<'S, 't> = Fleece.Codec<'S, 't>
77 |
78 | type DecodeError = Fleece.DecodeError
79 |
80 | let (|JsonTypeMismatch|_|) = function
81 | | Fleece.DecodeError.EncodingCaseMismatch (destinationType, encodedValue, expectedCase, actualCase) -> Some (destinationType, encodedValue, expectedCase, actualCase)
82 | | _ -> None
83 |
84 | let (|NullString|_|) = function
85 | | Fleece.DecodeError.NullString destinationType -> Some destinationType
86 | | _ -> None
87 |
88 | let (|IndexOutOfRange|_|) = function
89 | | Fleece.DecodeError.IndexOutOfRange (int, iEncoding) -> Some (int, iEncoding)
90 | | _ -> None
91 |
92 | let (|InvalidValue|_|) = function
93 | | Fleece.DecodeError.InvalidValue (destinationType, iEncoding, additionalInformation) -> Some (destinationType, iEncoding, additionalInformation)
94 | | _ -> None
95 |
96 | let (|PropertyNotFound|_|) = function
97 | | Fleece.DecodeError.PropertyNotFound (string, obj) -> Some (string, obj)
98 | | _ -> None
99 |
100 | let (|ParseError|_|) = function
101 | | Fleece.DecodeError.ParseError (destinationType, exn, string) -> Some (destinationType, exn, string)
102 | | _ -> None
103 |
104 | let (|Uncategorized|_|) = function
105 | | Fleece.DecodeError.Uncategorized string -> Some string
106 | | _ -> None
107 |
108 | let (|Multiple|_|) = function
109 | | Fleece.DecodeError.Multiple list -> Some list
110 | | _ -> None
111 |
112 |
113 | module Decode =
114 | let inline Success x = Ok x : ParseResult<_>
115 | let (|Success|Failure|) (x: ParseResult<_>) = x |> function
116 | | Ok x -> Success x
117 | | Error x -> Failure x
118 |
119 | module Fail =
120 | let inline objExpected (v: 'Encoding) : Result<'t, _> = let a = (v :> IEncoding).getCase in Error (EncodingCaseMismatch (typeof<'t>, v, "Object", a))
121 | let inline arrExpected (v: 'Encoding) : Result<'t, _> = let a = (v :> IEncoding).getCase in Error (EncodingCaseMismatch (typeof<'t>, v, "Array" , a))
122 | let inline numExpected (v: 'Encoding) : Result<'t, _> = let a = (v :> IEncoding).getCase in Error (EncodingCaseMismatch (typeof<'t>, v, "Number", a))
123 | let inline strExpected (v: 'Encoding) : Result<'t, _> = let a = (v :> IEncoding).getCase in Error (EncodingCaseMismatch (typeof<'t>, v, "String", a))
124 | let inline boolExpected (v: 'Encoding) : Result<'t, _> = let a = (v :> IEncoding).getCase in Error (EncodingCaseMismatch (typeof<'t>, v, "Bool" , a))
125 | let []nullString<'t> : Result<'t, _> = Error (NullString typeof<'t>)
126 | let inline count e (x: 'Encoding) =
127 | let a =
128 | match (Codecs.array (Ok <-> id) |> Codec.decode) x with
129 | | Ok a -> a
130 | | Error x -> failwithf "Error on error handling: Expected an 'Encoding [] but received %A." x
131 | Error (IndexOutOfRange (e, map (fun x -> x :> obj) a))
132 | let invalidValue (v: 'Encoding) o : Result<'t, _> = Error (InvalidValue (typeof<'t>, v, o))
133 | let propertyNotFound p (o: PropertyList<'Encoding>) = Error (PropertyNotFound (p, map (fun x -> x :> obj) o))
134 | let parseError s v : Result<'t, _> = Error (ParseError (typeof<'t>, s, v))
135 |
136 |
137 | /// Functions operating on Codecs
138 | module Codec =
139 |
140 | let decode { Decoder = ReaderT d } = d
141 | let encode { Encoder = e } = e >> Const.run
142 |
143 | /// Turns a Codec into another Codec, by mapping it over an isomorphism.
144 | let inline invmap (f: 'T -> 'U) (g: 'U -> 'T) c =
145 | let (Codec (r, w)) = c
146 | contramap f r <-> map g w
147 |
148 |
149 | /// Creates a new codec which is the result of applying codec2 then codec1 for encoding
150 | /// and codec1 then codec2 for decoding
151 | let inline compose codec1 codec2 =
152 | let (Codec (dec1, enc1)) = codec1
153 | let (Codec (dec2, enc2)) = codec2
154 | (dec1 >> (=<<) dec2) <-> (enc1 << enc2)
155 |
156 | /// Maps a function over the decoder.
157 | let map (f: 't1 -> 'u1) (field: Codec, PropertyList<'S>, 't1, 't2>) =
158 | (fun x ->
159 | match Codec.decode field x with
160 | | Error e -> Error e
161 | | Ok a -> Ok (f a))
162 | <-> Codec.encode field
163 |
164 |
165 | let ofConcrete x = id x
166 | let toConcrete x = id x
167 |
168 | []
169 | module JsonCodec =
170 | let result (x: Codec) = Codecs.result x
171 | let choice (x: Codec) = Codecs.choice x
172 | let choice3 (x: Codec) = Codecs.choice3 x
173 | let option (x: Codec) = Codecs.option x
174 | let nullable (x: Codec) = Codecs.nullable x
175 | let array (x: Codec) = Codecs.array x
176 | let arraySegment (x: Codec) = Codecs.arraySegment x
177 | let list (x: Codec) = Codecs.list x
178 | let set (x: Codec) = Codecs.set x
179 | let resizeArray (x: Codec) = Codecs.resizeArray x
180 | let map (x: Codec) = Codecs.propMap x
181 | let dictionary (x: Codec) = Codecs.propDictionary x
182 | let unit () : Codec = Codecs.unit
183 | let tuple2 (x: Codec) = Codecs.tuple2 x
184 | let tuple3 (x: Codec) = Codecs.tuple3 x
185 | let tuple4 (x: Codec) = Codecs.tuple4 x
186 | let tuple5 (x: Codec) = Codecs.tuple5 x
187 | let tuple6 (x: Codec) = Codecs.tuple6 x
188 | let tuple7 (x: Codec) = Codecs.tuple7 x
189 | let boolean : Codec = Codecs.boolean
190 | let string : Codec = Codecs.string
191 | let dateTime : Codec = Codecs.dateTime
192 | let dateTimeOffset : Codec = Codecs.dateTimeOffset
193 | let decimal : Codec = Codecs.decimal
194 | let float : Codec = Codecs.float
195 | let float32 : Codec = Codecs.float32
196 | let int : Codec = Codecs.int
197 | let uint32 : Codec = Codecs.uint32
198 | let int64 : Codec = Codecs.int64
199 | let uint64 : Codec = Codecs.uint64
200 | let int16 : Codec = Codecs.int16
201 | let uint16 : Codec = Codecs.uint16
202 | let byte : Codec = Codecs.byte
203 | let sbyte : Codec = Codecs.sbyte
204 | let char : Codec = Codecs.char
205 | let guid : Codec = Codecs.guid
206 |
207 |
208 | let inline jsonValueCodec< ^t when (GetCodec or ^t) : (static member GetCodec : ^t * GetCodec * GetCodec * OpCodec -> Codec)> = GetCodec.Invoke Unchecked.defaultof<'t>
209 |
210 | let jobj (x: list) : Encoding = x |> List.toArray |> PropertyList |> Codec.encode (Codecs.propList Codecs.id)
211 |
212 | let JNull : Encoding = (Codecs.option Codecs.unit |> Codec.encode) None
213 | let JBool x : Encoding = (Codecs.boolean |> Codec.encode) x
214 | let JNumber x : Encoding = (Codecs.decimal |> Codec.encode) x
215 | let JString x : Encoding = (Codecs.string |> Codec.encode) x
216 | let JArray (x: System.Collections.Generic.IReadOnlyList) : Encoding = (Codecs.array (Ok <-> id) |> Codec.encode) (toArray x)
217 | let JObject (x: PropertyList) : Encoding = (Codecs.propList (Ok <-> id) |> Codec.encode) x
218 |
219 | let (|JNull|_|) (x: Encoding) = match (Codecs.option (Ok <-> id) |> Codec.decode) x with | Ok None -> Some () | _ -> None
220 | let (|JBool|_|) (x: Encoding) = (Codecs.boolean |> Codec.decode) x |> Option.ofResult
221 | let (|JNumber|_|) (x: Encoding) = (Codecs.decimal |> Codec.decode) x |> Option.ofResult
222 | let (|JString|_|) (x: Encoding) = (Codecs.string |> Codec.decode) x |> Option.ofResult
223 | let (|JArray|_|) (x: Encoding) = (Codecs.array (Ok <-> id) |> Codec.decode) x |> Option.ofResult |> Option.map IReadOnlyList.ofArray
224 | let (|JObject|_|) (x: Encoding) = (Codecs.propList (Ok <-> id) |> Codec.decode) x |> Option.ofResult
225 |
226 | /// A codec to encode a collection of property/values into a Json encoding and the other way around.
227 | let jsonObjToValueCodec : Codec> = ((function JObject (o: PropertyList<_>) -> Ok o | a -> Decode.Fail.objExpected a) <-> JObject)
228 |
229 | /// A codec to encode a Json value to a Json text and the other way around.
230 | let jsonValueToTextCodec = (fun x -> try Ok (Encoding.Parse x) with e -> Decode.Fail.parseError e x) <-> (fun (x: Encoding) -> string x)
231 |
232 | let inline parseJson<'T when (Internals.GetDec or ^T) : (static member GetCodec: ^T * Internals.GetDec * Internals.GetDec * Internals.OpDecode -> Codec)> (x: string) : ParseResult<'T> =
233 | Codec.decode jsonValueToTextCodec x >>= Operators.ofJson
234 |
235 | let inline jreq name getter = jreq name getter : Codec,_,_,_>
236 | let inline jopt name getter = jopt name getter : Codec,_,_,_>
237 |
238 | let inline jreqWith codec name getter = jreqWith codec name getter : Codec,_,_,_>
239 | let inline joptWith codec name getter = joptWith codec name getter : Codec,_,_,_>
240 |
241 | let inline jchoice (codecs: seq, PropertyList, 't1, 't2>>) =
242 | let head, tail = Seq.head codecs, Seq.tail codecs
243 | foldBack (<|>) tail head
244 |
245 |
246 |
247 | /// Gets a value from a Json object
248 | let jgetWith ofJson (o: PropertyList) key =
249 | match o.[key] with
250 | | value::_ -> ofJson value
251 | | _ -> Decode.Fail.propertyNotFound key (o |> map (fun x -> x :> IEncoding))
252 |
253 | /// Tries to get a value from a Json object.
254 | /// Returns None if key is not present in the object.
255 | let jgetOptWith ofJson (o: PropertyList) key =
256 | match o.[key] with
257 | | JNull _::_ -> Ok None
258 | | value ::_ -> ofJson value |> Result.map Some
259 | | _ -> Ok None
260 |
261 | /// Gets a value from a Json object
262 | let inline jget (o: PropertyList) key = jgetWith ofEncoding o key
263 |
264 | /// Tries to get a value from a Json object.
265 | /// Returns None if key is not present in the object.
266 | let inline jgetOpt (o: PropertyList) key = jgetOptWith ofEncoding o key
267 |
268 | /// Gets a value from a Json object
269 | let inline (.@) o key = jget o key
270 |
271 | /// Tries to get a value from a Json object.
272 | /// Returns None if key is not present in the object.
273 | let inline (.@?) o key = jgetOpt o key
274 |
275 | /// Creates a new Json key-value pair for a Json object
276 | let inline jpairWith toJson (key: string) value = key, toJson value
277 |
278 | /// Creates a new Json key-value pair for a Json object
279 | let inline jpair (key: string) value = jpairWith toEncoding key value
280 |
281 | /// Creates a new Json key-value pair for a Json object if the value option is present
282 | let jpairOptWith toJson (key: string) value = match value with Some value -> (key, toJson value) | _ -> (null, JNull)
283 |
284 | /// Creates a new Json key-value pair for a Json object if the value option is present
285 | let inline jpairOpt (key: string) value = jpairOptWith toJson key value
286 |
287 |
288 | /// Creates a new Json key-value pair for a Json object
289 | let inline (.=) key value = jpair key value
290 |
291 | /// Creates a new Json key-value pair for a Json object if the value is present in the option
292 | let inline (.=?) (key: string) value = jpairOpt key value
293 |
294 | let jsonObjectGetValues x = id x
295 |
296 |
297 | // Verbose syntax
298 |
299 | /// Initialize the field mappings.
300 | /// An object constructor as a curried function.
301 | /// The resulting object codec.
302 | let inline withFields f : Codec<'s,'s,_,_> = result f //(fun _ -> Ok f) <-> (fun _ -> multiMap [])
303 |
304 | /// Appends a field mapping to the codec.
305 | /// A string that will be used as key to the field.
306 | /// The field getter function.
307 | /// The other mappings.
308 | /// The resulting object codec.
309 | let inline jfield fieldName getter rest = rest <*> jreq fieldName (getter >> Some)
310 |
311 | /// Appends an optional field mapping to the codec.
312 | /// A string that will be used as key to the field.
313 | /// The field getter function.
314 | /// The other mappings.
315 | /// The resulting object codec.
316 | let inline jfieldOpt fieldName getter rest = rest <*> jopt fieldName getter
317 |
318 | /// Appends a field mapping to the codec.
319 | /// The codec to be used.
320 | /// A string that will be used as key to the field.
321 | /// The field getter function.
322 | /// The other mappings.
323 | /// The resulting object codec.
324 | let inline jfieldWith codec fieldName getter rest = rest <*> jreqWith codec fieldName (getter >> Some)
325 |
326 | /// Appends a field mapping to the codec.
327 | /// The codec thunk to be used.
328 | /// A string that will be used as key to the field.
329 | /// The field getter function.
330 | /// The other mappings.
331 | /// The resulting object codec.
332 | let inline jfieldWithLazy codec fieldName getter rest = rest <*> jreqWithLazy codec fieldName (getter >> Some)
333 |
334 | /// Appends an optional field mapping to the codec.
335 | /// The codec to be used.
336 | /// A string that will be used as key to the field.
337 | /// The field getter function.
338 | /// The other mappings.
339 | /// The resulting object codec.
340 | let inline jfieldOptWith codec fieldName getter rest = rest <*> joptWith codec fieldName getter
341 |
342 |
343 | module Lens =
344 | open FSharpPlus.Lens
345 | let inline _JString x = (prism' JString <| function JString s -> Some s | _ -> None) x
346 | let inline _JObject x = (prism' JObject <| function JObject s -> Some s | _ -> None) x
347 | let inline _JArray x = (prism' JArray <| function JArray s -> Some s | _ -> None) x
348 | let inline _JBool x = (prism' JBool <| function JBool s -> Some s | _ -> None) x
349 | let inline _JNumber x = (prism' JNumber <| fun v -> match Operators.ofJsonValue v : decimal ParseResult with Ok s -> Some s | _ -> None) x
350 | let inline _JNull x = prism' (konst JNull) (function JNull -> Some () | _ -> None) x
351 |
352 | /// Like '_jnth', but for 'Object' with Text indices.
353 | let inline _jkey i =
354 | let inline dkey i f t = map (fun x -> IReadOnlyDictionary.add i x t) (f (IReadOnlyDictionary.tryGetValue i t |> Option.defaultValue JNull))
355 | _JObject << dkey i
356 |
357 | let inline _jnth i =
358 | let inline dnth i f t = map (fun x -> t |> IReadOnlyList.trySetItem i x |> Option.defaultValue t) (f (IReadOnlyList.tryItem i t |> Option.defaultValue JNull))
359 | _JArray << dnth i
360 |
361 | // Reimport some basic Lens operations from F#+
362 |
363 | let setl optic value (source: 's) : 't = setl optic value source
364 | let over optic updater (source: 's) : 't = over optic updater source
365 | let preview (optic: ('a -> Const<_,'b>) -> _ -> Const<_,'t>) (source: 's) : 'a option = preview optic source
--------------------------------------------------------------------------------
/src/Fleece.SystemJson/Fleece.SystemJson.fs:
--------------------------------------------------------------------------------
1 | namespace Fleece.SystemJson
2 |
3 | open System
4 | open System.Collections.Generic
5 | open System.Json
6 |
7 | []
8 | module Internals =
9 |
10 | open FSharpPlus
11 |
12 | type JsonObject with
13 | member x.AsReadOnlyDictionary () = (x :> IDictionary) |> Dict.toIReadOnlyDictionary
14 | static member GetValues (x: JsonObject) = x.AsReadOnlyDictionary ()
15 |
16 | let jsonObjectGetValues (x: JsonObject) = JsonObject.GetValues x
17 |
18 | type JsonHelpers () =
19 | static member create (x: decimal) = JsonPrimitive x :> JsonValue
20 | static member create (x: Double ) = JsonPrimitive x :> JsonValue
21 | static member create (x: Single ) = JsonPrimitive x :> JsonValue
22 | static member create (x: int ) = JsonPrimitive x :> JsonValue
23 | static member create (x: uint32 ) = JsonPrimitive x :> JsonValue
24 | static member create (x: int64 ) = JsonPrimitive x :> JsonValue
25 | static member create (x: uint64 ) = JsonPrimitive x :> JsonValue
26 | static member create (x: int16 ) = JsonPrimitive x :> JsonValue
27 | static member create (x: uint16 ) = JsonPrimitive x :> JsonValue
28 | static member create (x: byte ) = JsonPrimitive x :> JsonValue
29 | static member create (x: sbyte ) = JsonPrimitive x :> JsonValue
30 | static member create (x: char ) = JsonPrimitive (string x) :> JsonValue
31 | static member create (x: bigint ) = JsonPrimitive (string x) :> JsonValue
32 | static member create (x: Guid ) = JsonPrimitive (string x) :> JsonValue
33 |
34 |
35 | // pseudo-AST, wrapping JsonValue subtypes:
36 |
37 | let (|JArray|JObject|JNumber|JBool|JString|JNull|) (o: JsonValue) =
38 | match o with
39 | | null -> JNull
40 | | :? JsonArray as x -> JArray ((x :> JsonValue IList) |> IList.toIReadOnlyList)
41 | | :? JsonObject as x -> JObject (x.AsReadOnlyDictionary ())
42 | | :? JsonPrimitive as x ->
43 | match x.JsonType with
44 | | JsonType.Number -> JNumber x
45 | | JsonType.Boolean -> JBool (implicit x: bool)
46 | | JsonType.String -> JString (implicit x: string)
47 | | _ -> failwithf "Invalid JsonType %A for primitive %A" x.JsonType x
48 | | _ -> failwithf "Invalid JsonValue %A" o
49 |
50 | let inline JArray (x: JsonValue IReadOnlyList) = JsonArray x :> JsonValue
51 | let inline JObject (x: IReadOnlyDictionary) = JsonObject x :> JsonValue
52 | let inline JBool (x: bool) = JsonPrimitive x :> JsonValue
53 | let JNull : JsonValue = null
54 | let inline JString (x: string) = if isNull x then JNull else JsonPrimitive x :> JsonValue
55 | let inline JNumber (x: decimal) = JsonPrimitive x :> JsonValue
56 |
57 | /// Creates a new Json object for serialization
58 | let jobj x = JObject (x |> Seq.filter (fun (k,_) -> not (isNull k)) |> readOnlyDict)
59 |
60 |
61 | open System.Globalization
62 | open FSharpPlus
63 | open FSharpPlus.Data
64 | open Fleece
65 | open Fleece.Helpers
66 | open Internals
67 |
68 |
69 | type [] Encoding = Encoding of JsonValue with
70 |
71 | override this.ToString () = let (Encoding x) = this in x.ToString ()
72 |
73 | static member Parse (x: string) = Encoding (JsonValue.Parse x)
74 |
75 | static member inline tryRead x =
76 | match x with
77 | | JNumber j ->
78 | try
79 | Ok (implicit j)
80 | with e -> Decode.Fail.invalidValue (Encoding j) (string e)
81 | | js -> Decode.Fail.numExpected (Encoding js)
82 |
83 | /// Unwraps the JsonValue inside an IEncoding
84 | static member Unwrap (x: IEncoding) = x :?> Encoding |> fun (Encoding s) -> s
85 |
86 | /// Wraps a JsonValue inside an IEncoding
87 | static member Wrap x = Encoding x :> IEncoding
88 |
89 | static member toIEncoding (c: Codec) : Codec = c |> Codec.compose ((Encoding.Unwrap >> Ok) <-> Encoding.Wrap)
90 | static member ofIEncoding (c: Codec) : Codec = c |> Codec.compose ((Encoding.Wrap >> Ok) <-> Encoding.Unwrap)
91 |
92 | static member inline jsonObjectOfJson =
93 | fun (o: JsonValue) ->
94 | match box o with
95 | | :? JsonObject as x -> Ok x
96 | | _ -> Decode.Fail.objExpected (Encoding o)
97 |
98 | static member jsonOfJsonObject (o: JsonObject) = o :> JsonValue
99 |
100 |
101 | //////////////
102 | // Decoders //
103 | //////////////
104 |
105 | static member resultD (decoder1: JsonValue -> ParseResult<'a>) (decoder2: JsonValue -> ParseResult<'b>) : JsonValue -> ParseResult> = function
106 | | JObject o as jobj ->
107 | match Seq.toList o with
108 | | [KeyValue ("Ok", a)] -> a |> decoder1 |> Result.map Ok
109 | | [KeyValue ("Error", a)] -> a |> decoder2 |> Result.map Error
110 | | _ -> Decode.Fail.invalidValue (Encoding jobj) ""
111 | | a -> Decode.Fail.objExpected (Encoding a)
112 |
113 | static member choiceD (decoder1: JsonValue -> ParseResult<'a>) (decoder2: JsonValue -> ParseResult<'b>) : JsonValue -> ParseResult> = function
114 | | JObject o as jobj ->
115 | match Seq.toList o with
116 | | [KeyValue ("Choice1Of2", a)] -> a |> decoder1 |> Result.map Choice1Of2
117 | | [KeyValue ("Choice2Of2", a)] -> a |> decoder2 |> Result.map Choice2Of2
118 | | _ -> Decode.Fail.invalidValue (Encoding jobj) ""
119 | | a -> Decode.Fail.objExpected (Encoding a)
120 |
121 | static member choice3D (decoder1: JsonValue -> ParseResult<'a>) (decoder2: JsonValue -> ParseResult<'b>) (decoder3: JsonValue -> ParseResult<'c>) : JsonValue -> ParseResult> = function
122 | | JObject o as jobj ->
123 | match Seq.toList o with
124 | | [KeyValue ("Choice1Of3", a)] -> a |> decoder1 |> Result.map Choice1Of3
125 | | [KeyValue ("Choice2Of3", a)] -> a |> decoder2 |> Result.map Choice2Of3
126 | | [KeyValue ("Choice3Of3", a)] -> a |> decoder3 |> Result.map Choice3Of3
127 | | _ -> Decode.Fail.invalidValue (Encoding jobj) ""
128 | | a -> Decode.Fail.objExpected (Encoding a)
129 |
130 | static member optionD (decoder: JsonValue -> ParseResult<'a>) : JsonValue -> ParseResult<'a option> = function
131 | | JNull _ -> Ok None
132 | | x -> Result.map Some (decoder x)
133 |
134 | static member nullableD (decoder: JsonValue -> ParseResult<'a>) : JsonValue -> ParseResult> = function
135 | | JNull _ -> Ok (Nullable ())
136 | | x -> Result.map Nullable (decoder x)
137 |
138 | static member arrayD (decoder: JsonValue -> ParseResult<'a>) : JsonValue -> ParseResult<'a array> = function
139 | | JArray a -> traversei (fun i -> decoder >> Result.bindError (Decode.Fail.inner ($"#{i}"))) a |> Result.map Seq.toArray
140 | | a -> Decode.Fail.arrExpected (Encoding a)
141 |
142 | static member propListD (decoder: JsonValue -> ParseResult<'a>) : JsonValue -> ParseResult> = function
143 | | JObject o -> traversei (fun i -> decoder >> Result.bindError (Decode.Fail.inner i)) (o |> Seq.map (|KeyValue|) |> toArray |> PropertyList)
144 | | a -> Decode.Fail.objExpected (Encoding a)
145 |
146 | static member decimalD x = Encoding.tryRead x
147 | static member int16D x = Encoding.tryRead x
148 | static member intD x = Encoding.tryRead x
149 | static member int64D x = Encoding.tryRead x
150 | static member uint16D x = Encoding.tryRead x
151 | static member uint32D x = Encoding.tryRead x
152 | static member uint64D x = Encoding.tryRead x
153 | static member byteD x = Encoding.tryRead x
154 | static member sbyteD x = Encoding.tryRead x
155 | static member floatD x = Encoding.tryRead x
156 | static member float32D x = Encoding.tryRead x
157 |
158 | static member enumD x : Result< 't, _> when 't: enum<_> =
159 | match x with
160 | | JString null -> Decode.Fail.nullString
161 | | JString s -> match Enum.TryParse s with (true, value) -> Ok value | _ -> Decode.Fail.invalidValue (Encoding x) s
162 | | a -> Decode.Fail.strExpected (Encoding a)
163 |
164 | static member booleanD x =
165 | match x with
166 | | JBool b -> Ok b
167 | | a -> Decode.Fail.boolExpected (Encoding a)
168 |
169 | static member stringD x =
170 | match x with
171 | | JString b -> Ok b
172 | | JNull -> Ok null
173 | | a -> Decode.Fail.strExpected (Encoding a)
174 |
175 | static member charD x =
176 | match x with
177 | | JString null -> Decode.Fail.nullString
178 | | JString s -> Ok s.[0]
179 | | a -> Decode.Fail.strExpected (Encoding a)
180 |
181 | static member bigintD x =
182 | match x with
183 | | JString null -> Decode.Fail.nullString
184 | | JString s -> match tryParse s with (Some (value: bigint)) -> Ok value | _ -> Decode.Fail.invalidValue (Encoding x) s
185 | | a -> Decode.Fail.strExpected (Encoding a)
186 |
187 | static member guidD x =
188 | match x with
189 | | JString null -> Decode.Fail.nullString
190 | | JString s -> match Guid.TryParse s with (true, value) -> Ok value | _ -> Decode.Fail.invalidValue (Encoding x) s
191 | | a -> Decode.Fail.strExpected (Encoding a)
192 |
193 | static member dateD x =
194 | match x with
195 | | JString null -> Decode.Fail.nullString
196 | | JString s ->
197 | match DateTime.TryParseExact (s, [|"yyyy-MM-dd" |], null, DateTimeStyles.RoundtripKind) with
198 | | true, t -> Ok t
199 | | _ -> Decode.Fail.invalidValue (Encoding x) ""
200 | | a -> Decode.Fail.strExpected (Encoding a)
201 |
202 | static member timeD x =
203 | match x with
204 | | JString null -> Decode.Fail.nullString
205 | | JString s ->
206 | match DateTime.TryParseExact (s, [| "HH:mm:ss.fff"; "HH:mm:ss" |], null, DateTimeStyles.RoundtripKind) with
207 | | true, t -> Ok t
208 | | _ -> Decode.Fail.invalidValue (Encoding x) ""
209 | | a -> Decode.Fail.strExpected (Encoding a)
210 |
211 | static member dateTimeD x =
212 | match x with
213 | | JString null -> Decode.Fail.nullString
214 | | JString s ->
215 | match DateTime.TryParseExact (s, [| "yyyy-MM-ddTHH:mm:ss.fffZ"; "yyyy-MM-ddTHH:mm:ssZ" |], null, DateTimeStyles.RoundtripKind) with
216 | | true, t -> Ok t
217 | | _ -> Decode.Fail.invalidValue (Encoding x) ""
218 | | a -> Decode.Fail.strExpected (Encoding a)
219 |
220 | static member dateTimeOffsetD x =
221 | match x with
222 | | JString null -> Decode.Fail.nullString
223 | | JString s ->
224 | match DateTimeOffset.TryParseExact (s, [| "yyyy-MM-ddTHH:mm:ss.fffK"; "yyyy-MM-ddTHH:mm:ssK" |], null, DateTimeStyles.RoundtripKind) with
225 | | true, t -> Ok t
226 | | _ -> Decode.Fail.invalidValue (Encoding x) ""
227 | | a -> Decode.Fail.strExpected (Encoding a)
228 |
229 | static member timeSpanD x =
230 | match x with
231 | | JString null -> Decode.Fail.nullString
232 | | JNumber _ as j -> Encoding.int64D j |> Result.map TimeSpan
233 | | a -> Decode.Fail.numExpected (Encoding a)
234 |
235 |
236 | //////////////
237 | // Encoders //
238 | //////////////
239 |
240 | static member resultE (encoder1: _ -> JsonValue) (encoder2: _ -> JsonValue) = function
241 | | Ok a -> jobj [ "Ok" , encoder1 a ]
242 | | Error a -> jobj [ "Error", encoder2 a ]
243 |
244 | static member choiceE (encoder1: _ -> JsonValue) (encoder2: _ -> JsonValue) = function
245 | | Choice1Of2 a -> jobj [ "Choice1Of2", encoder1 a ]
246 | | Choice2Of2 a -> jobj [ "Choice2Of2", encoder2 a ]
247 |
248 | static member choice3E (encoder1: _ -> JsonValue) (encoder2: _ -> JsonValue) (encoder3: _ -> JsonValue) = function
249 | | Choice1Of3 a -> jobj [ "Choice1Of3", encoder1 a ]
250 | | Choice2Of3 a -> jobj [ "Choice2Of3", encoder2 a ]
251 | | Choice3Of3 a -> jobj [ "Choice3Of3", encoder3 a ]
252 |
253 | static member optionE (encoder: _ -> JsonValue) = function
254 | | None -> JNull
255 | | Some a -> encoder a
256 |
257 | static member nullableE (encoder: _ -> JsonValue) (x: Nullable<'a>) = if x.HasValue then encoder x.Value else JNull
258 | static member arrayE (encoder: _ -> JsonValue) (x: 'a []) = JArray ((Array.map encoder x) |> Array.toList)
259 | static member propListE (encoder: _ -> JsonValue) (x: PropertyList<'a>) = x |> filter (fun (k, _) -> not (isNull k)) |> map encoder |> JObject
260 |
261 | static member tuple1E (encoder1: 'a -> JsonValue) (a: Tuple<_>) = JArray ([|encoder1 a.Item1|] |> Seq.toList)
262 | static member tuple2E (encoder1: 'a -> JsonValue) (encoder2: 'b -> JsonValue) (a, b) = JArray ([|encoder1 a; encoder2 b|] |> Seq.toList)
263 | static member tuple3E (encoder1: 'a -> JsonValue) (encoder2: 'b -> JsonValue) (encoder3: 'c -> JsonValue) (a, b, c) = JArray ([|encoder1 a; encoder2 b; encoder3 c|] |> Seq.toList)
264 | static member tuple4E (encoder1: 'a -> JsonValue) (encoder2: 'b -> JsonValue) (encoder3: 'c -> JsonValue) (encoder4: 'd -> JsonValue) (a, b, c, d) = JArray ([|encoder1 a; encoder2 b; encoder3 c; encoder4 d|] |> Seq.toList)
265 | static member tuple5E (encoder1: 'a -> JsonValue) (encoder2: 'b -> JsonValue) (encoder3: 'c -> JsonValue) (encoder4: 'd -> JsonValue) (encoder5: 'e -> JsonValue) (a, b, c, d, e) = JArray ([|encoder1 a; encoder2 b; encoder3 c; encoder4 d; encoder5 e|] |> Seq.toList)
266 | static member tuple6E (encoder1: 'a -> JsonValue) (encoder2: 'b -> JsonValue) (encoder3: 'c -> JsonValue) (encoder4: 'd -> JsonValue) (encoder5: 'e -> JsonValue) (encoder6: 'f -> JsonValue) (a, b, c, d, e, f) = JArray ([|encoder1 a; encoder2 b; encoder3 c; encoder4 d; encoder5 e; encoder6 f|] |> Seq.toList)
267 | static member tuple7E (encoder1: 'a -> JsonValue) (encoder2: 'b -> JsonValue) (encoder3: 'c -> JsonValue) (encoder4: 'd -> JsonValue) (encoder5: 'e -> JsonValue) (encoder6: 'f -> JsonValue) (encoder7: 'g -> JsonValue) (a, b, c, d, e, f, g) = JArray ([|encoder1 a; encoder2 b; encoder3 c; encoder4 d; encoder5 e; encoder6 f; encoder7 g|] |> Seq.toList)
268 |
269 | static member enumE (x: 't when 't: enum<_>) = JString (string x)
270 | static member unitE () = JArray []
271 |
272 | static member booleanE (x: bool ) = JBool x
273 | static member stringE (x: string ) = JString x
274 | static member dateE (x: DateTime ) = JString (x.ToString "yyyy-MM-dd")
275 | static member timeE (x: DateTime ) = JString (x.ToString "HH:mm:ss.fff")
276 | static member dateTimeE (x: DateTime ) = JString (x.ToString ("yyyy-MM-ddTHH:mm:ss.fffZ"))
277 | static member dateTimeOffsetE (x: DateTimeOffset) = JString (x.ToString ("yyyy-MM-ddTHH:mm:ss.fffK"))
278 | static member timeSpanE (x: TimeSpan) = JsonHelpers.create x.Ticks
279 |
280 | static member decimalE (x: decimal ) = JsonHelpers.create x
281 | static member floatE (x: Double ) = JsonHelpers.create x
282 | static member float32E (x: Single ) = JsonHelpers.create x
283 | static member intE (x: int ) = JsonHelpers.create x
284 | static member uint32E (x: uint32 ) = JsonHelpers.create x
285 | static member int64E (x: int64 ) = JsonHelpers.create x
286 | static member uint64E (x: uint64 ) = JsonHelpers.create x
287 | static member int16E (x: int16 ) = JsonHelpers.create x
288 | static member uint16E (x: uint16 ) = JsonHelpers.create x
289 | static member byteE (x: byte ) = JsonHelpers.create x
290 | static member sbyteE (x: sbyte ) = JsonHelpers.create x
291 | static member charE (x: char ) = JsonHelpers.create x
292 | static member bigintE (x: bigint ) = JsonHelpers.create x
293 | static member guidE (x: Guid ) = JsonHelpers.create x
294 |
295 |
296 | ////////////
297 | // Codecs //
298 | ////////////
299 |
300 | static member result (codec1: Codec<_,_>) (codec2: Codec<_,_>) = Encoding.resultD (Codec.decode codec1) (Codec.decode codec2) <-> Encoding.resultE (Codec.encode codec1) (Codec.encode codec2)
301 |
302 | static member choice (codec1: Codec<_,_>) (codec2: Codec<_,_>) = Encoding.choiceD (Codec.decode codec1) (Codec.decode codec2) <-> Encoding.choiceE (Codec.encode codec1) (Codec.encode codec2)
303 | static member choice3 (codec1: Codec<_,_>) (codec2: Codec<_,_>) (codec3: Codec<_,_>) = Encoding.choice3D (Codec.decode codec1) (Codec.decode codec2) (Codec.decode codec3) <-> Encoding.choice3E (Codec.encode codec1) (Codec.encode codec2) (Codec.encode codec3)
304 | static member option (codec: Codec<_,_>) = Encoding.optionD (Codec.decode codec) <-> Encoding.optionE (Codec.encode codec)
305 | static member nullable (codec: Codec) = Encoding.nullableD (Codec.decode codec) <-> Encoding.nullableE (Codec.encode codec) : Codec>
306 | static member array (codec: Codec<_,_>) = Encoding.arrayD (Codec.decode codec) <-> Encoding.arrayE (Codec.encode codec)
307 | static member multiMap (codec: Codec<_,_>) = Encoding.propListD (Codec.decode codec) <-> Encoding.propListE (Codec.encode codec)
308 |
309 | static member boolean = Encoding.booleanD <-> Encoding.booleanE
310 | static member string = Encoding.stringD <-> Encoding.stringE
311 | static member date = Encoding.dateD <-> Encoding.dateE
312 | static member time = Encoding.timeD <-> Encoding.timeE
313 | static member dateTime = Encoding.dateTimeD <-> Encoding.dateTimeE
314 | static member dateTimeOffset = Encoding.dateTimeOffsetD <-> Encoding.dateTimeOffsetE
315 | static member timeSpan = Encoding.timeSpanD <-> Encoding.timeSpanE
316 | static member decimal = Encoding.decimalD <-> Encoding.decimalE
317 | static member float = Encoding.floatD <-> Encoding.floatE
318 | static member float32 = Encoding.float32D <-> Encoding.float32E
319 | static member int = Encoding.intD <-> Encoding.intE
320 | static member uint32 = Encoding.uint32D <-> Encoding.uint32E
321 | static member int64 = Encoding.int64D <-> Encoding.int64E
322 | static member uint64 = Encoding.uint64D <-> Encoding.uint64E
323 | static member int16 = Encoding.int16D <-> Encoding.int16E
324 | static member uint16 = Encoding.uint16D <-> Encoding.uint16E
325 | static member byte = Encoding.byteD <-> Encoding.byteE
326 | static member sbyte = Encoding.sbyteD <-> Encoding.sbyteE
327 | static member char = Encoding.charD <-> Encoding.charE
328 | static member bigint = Encoding.bigintD <-> Encoding.bigintE
329 | static member guid = Encoding.guidD <-> Encoding.guidE
330 |
331 |
332 | interface IEncoding with
333 | member _.boolean = Encoding.toIEncoding Encoding.boolean
334 | member _.string = Encoding.toIEncoding Encoding.string
335 | member _.dateTime t =
336 | match t with
337 | | Some DateTimeContents.Date -> Encoding.toIEncoding Encoding.date
338 | | Some DateTimeContents.Time -> Encoding.toIEncoding Encoding.time
339 | | _ -> Encoding.toIEncoding Encoding.dateTime
340 | member _.dateTimeOffset = Encoding.toIEncoding Encoding.dateTimeOffset
341 | member _.timeSpan = Encoding.toIEncoding Encoding.timeSpan
342 | member _.decimal = Encoding.toIEncoding Encoding.decimal
343 | member _.float = Encoding.toIEncoding Encoding.float
344 | member _.float32 = Encoding.toIEncoding Encoding.float32
345 | member _.int = Encoding.toIEncoding Encoding.int
346 | member _.uint32 = Encoding.toIEncoding Encoding.uint32
347 | member _.int64 = Encoding.toIEncoding Encoding.int64
348 | member _.uint64 = Encoding.toIEncoding Encoding.uint64
349 | member _.int16 = Encoding.toIEncoding Encoding.int16
350 | member _.uint16 = Encoding.toIEncoding Encoding.uint16
351 | member _.byte = Encoding.toIEncoding Encoding.byte
352 | member _.sbyte = Encoding.toIEncoding Encoding.sbyte
353 | member _.char = Encoding.toIEncoding Encoding.char
354 | member _.bigint = Encoding.toIEncoding Encoding.bigint
355 | member _.guid = Encoding.toIEncoding Encoding.guid
356 |
357 | member _.result c1 c2 = Encoding.toIEncoding (Encoding.result (Encoding.ofIEncoding c1) (Encoding.ofIEncoding c2))
358 | member _.choice c1 c2 = Encoding.toIEncoding (Encoding.choice (Encoding.ofIEncoding c1) (Encoding.ofIEncoding c2))
359 | member _.choice3 c1 c2 c3 = Encoding.toIEncoding (Encoding.choice3 (Encoding.ofIEncoding c1) (Encoding.ofIEncoding c2) (Encoding.ofIEncoding c3))
360 | member _.option c = Encoding.toIEncoding (Encoding.option (Encoding.ofIEncoding c))
361 | member _.array c = Encoding.toIEncoding (Encoding.array (Encoding.ofIEncoding c))
362 | member _.propertyList c = Encoding.toIEncoding (Encoding.multiMap (Encoding.ofIEncoding c))
363 |
364 | member _.enum<'t, 'u when 't : enum<'u> and 't : (new : unit -> 't) and 't : struct and 't :> ValueType> () : Codec = Encoding.toIEncoding (Encoding.enumD <-> Encoding.enumE)
365 |
366 | member x.getCase =
367 | match x with
368 | | Encoding (JNull ) -> "JNull"
369 | | Encoding (JBool _) -> "JBool"
370 | | Encoding (JNumber _) -> "JNumber"
371 | | Encoding (JString _) -> "JString"
372 | | Encoding (JArray _) -> "JArray"
373 | | Encoding (JObject _) -> "JObject"
--------------------------------------------------------------------------------
/src/Fleece.FSharpData/Fleece.FSharpData.fs:
--------------------------------------------------------------------------------
1 | namespace Fleece.FSharpData
2 |
3 | open System
4 | open System.Collections.Generic
5 | open FSharp.Data
6 |
7 | []
8 | module Internals =
9 |
10 | open FSharpPlus
11 |
12 | type JsonHelpers () =
13 | static member create (x: decimal) : JsonValue = JsonValue.Number x
14 | static member create (x: Double ) : JsonValue = JsonValue.Float x
15 | static member create (x: Single ) : JsonValue = JsonValue.Float (float x)
16 | static member create (x: int ) : JsonValue = JsonValue.Number (decimal x)
17 | static member create (x: bool ) : JsonValue = JsonValue.Boolean x
18 | static member create (x: uint32 ) : JsonValue = JsonValue.Number (decimal x)
19 | static member create (x: int64 ) : JsonValue = JsonValue.Number (decimal x)
20 | static member create (x: uint64 ) : JsonValue = JsonValue.Number (decimal x)
21 | static member create (x: int16 ) : JsonValue = JsonValue.Number (decimal x)
22 | static member create (x: uint16 ) : JsonValue = JsonValue.Number (decimal x)
23 | static member create (x: byte ) : JsonValue = JsonValue.Number (decimal x)
24 | static member create (x: sbyte ) : JsonValue = JsonValue.Number (decimal x)
25 | static member create (x: char ) : JsonValue = JsonValue.String (string x)
26 | static member create (x: bigint ) : JsonValue = JsonValue.String (string x)
27 | static member create (x: Guid ) : JsonValue = JsonValue.String (string x)
28 |
29 |
30 | type JsonObject = Fleece.PropertyList
31 |
32 | let jsonObjectGetValues (x: JsonObject) = x :> IReadOnlyDictionary
33 |
34 |
35 | // FSharp.Data.JsonValue AST adapter
36 | let (|JArray|JObject|JNumber|JBool|JString|JNull|) (o: JsonValue) =
37 | match o with
38 | | JsonValue.Null -> JNull
39 | | JsonValue.Array els -> JArray (IList.toIReadOnlyList els)
40 | | JsonValue.Record props -> JObject (jsonObjectGetValues (JsonObject props))
41 | | JsonValue.Number _ as x -> JNumber x
42 | | JsonValue.Float _ as x -> JNumber x
43 | | JsonValue.Boolean x -> JBool x
44 | | JsonValue.String x -> JString x
45 |
46 | let dictAsJsonObject (x: IReadOnlyDictionary) =
47 | match x with
48 | | :? JsonObject as x' -> x'
49 | | _ -> x |> Seq.map (|KeyValue|) |> Array.ofSeq |> JsonObject
50 |
51 | let dictAsProps (x: IReadOnlyDictionary) =
52 | match x with
53 | | :? JsonObject as x' -> x'.Properties
54 | | _ -> x |> Seq.map (|KeyValue|) |> Array.ofSeq
55 |
56 | let inline JArray (x: JsonValue IReadOnlyList) = JsonValue.Array (x |> Array.ofSeq)
57 | let inline JObject (x: IReadOnlyDictionary) = JsonValue.Record (dictAsProps x)
58 | let inline JBool (x: bool) = JsonValue.Boolean x
59 | let inline JNumber (x: decimal) = JsonValue.Number x
60 | let JNull : JsonValue = JsonValue.Null
61 | let inline JString (x: string) = if isNull x then JsonValue.Null else JsonValue.String x
62 |
63 | /// Creates a new Json object for serialization
64 | let jobj x = JObject (x |> Seq.filter (fun (k,_) -> not (isNull k)) |> readOnlyDict)
65 |
66 |
67 | open System.Globalization
68 | open FSharpPlus
69 | open FSharpPlus.Data
70 | open Fleece
71 | open Internals
72 |
73 |
74 | type [] Encoding = Encoding of JsonValue with
75 | override this.ToString () = let (Encoding x) = this in x.ToString ()
76 | static member Parse (x: string) = Encoding (JsonValue.Parse x)
77 |
78 | static member inline tryRead x =
79 | match x with
80 | | JsonValue.Number n -> Ok (explicit n)
81 | | JsonValue.Float n -> Ok (explicit n)
82 | | js -> Decode.Fail.numExpected (Encoding js)
83 |
84 | /// Unwraps the JsonValue inside an IEncoding
85 | static member Unwrap (x: IEncoding) = x :?> Encoding |> fun (Encoding s) -> s
86 |
87 | /// Wraps a JsonValue inside an IEncoding
88 | static member Wrap x = Encoding x :> IEncoding
89 |
90 | static member toIEncoding (c: Codec) : Codec = c |> Codec.compose ((Encoding.Unwrap >> Ok) <-> Encoding.Wrap)
91 | static member ofIEncoding (c: Codec) : Codec = c |> Codec.compose ((Encoding.Wrap >> Ok) <-> Encoding.Unwrap)
92 |
93 | static member jsonObjectOfJson = function
94 | | JObject x -> Ok (dictAsJsonObject x)
95 | | a -> Decode.Fail.objExpected (Encoding a)
96 |
97 | static member jsonOfJsonObject o = JObject o
98 |
99 |
100 | //////////////
101 | // Decoders //
102 | //////////////
103 |
104 | static member resultD (decoder1: JsonValue -> ParseResult<'a>) (decoder2: JsonValue -> ParseResult<'b>) : JsonValue -> ParseResult> = function
105 | | JObject o as jobj ->
106 | match Seq.toList o with
107 | | [KeyValue ("Ok", a)] -> a |> decoder1 |> Result.map Ok
108 | | [KeyValue ("Error", a)] -> a |> decoder2 |> Result.map Error
109 | | _ -> Decode.Fail.invalidValue (Encoding jobj) ""
110 | | a -> Decode.Fail.objExpected (Encoding a)
111 |
112 | static member choiceD (decoder1: JsonValue -> ParseResult<'a>) (decoder2: JsonValue -> ParseResult<'b>) : JsonValue -> ParseResult> = function
113 | | JObject o as jobj ->
114 | match Seq.toList o with
115 | | [KeyValue ("Choice1Of2", a)] -> a |> decoder1 |> Result.map Choice1Of2
116 | | [KeyValue ("Choice2Of2", a)] -> a |> decoder2 |> Result.map Choice2Of2
117 | | _ -> Decode.Fail.invalidValue (Encoding jobj) ""
118 | | a -> Decode.Fail.objExpected (Encoding a)
119 |
120 | static member choice3D (decoder1: JsonValue -> ParseResult<'a>) (decoder2: JsonValue -> ParseResult<'b>) (decoder3: JsonValue -> ParseResult<'c>) : JsonValue -> ParseResult> = function
121 | | JObject o as jobj ->
122 | match Seq.toList o with
123 | | [KeyValue ("Choice1Of3", a)] -> a |> decoder1 |> Result.map Choice1Of3
124 | | [KeyValue ("Choice2Of3", a)] -> a |> decoder2 |> Result.map Choice2Of3
125 | | [KeyValue ("Choice3Of3", a)] -> a |> decoder3 |> Result.map Choice3Of3
126 | | _ -> Decode.Fail.invalidValue (Encoding jobj) ""
127 | | a -> Decode.Fail.objExpected (Encoding a)
128 |
129 | static member optionD (decoder: JsonValue -> ParseResult<'a>) : JsonValue -> ParseResult<'a option> = function
130 | | JNull _ -> Ok None
131 | | x -> Result.map Some (decoder x)
132 |
133 | static member nullableD (decoder: JsonValue -> ParseResult<'a>) : JsonValue -> ParseResult> = function
134 | | JNull _ -> Ok (Nullable ())
135 | | x -> Result.map Nullable (decoder x)
136 |
137 | static member arrayD (decoder: JsonValue -> ParseResult<'a>) : JsonValue -> ParseResult<'a array> = function
138 | | JArray a -> traversei (fun i -> decoder >> Result.bindError (Decode.Fail.inner ($"#{i}"))) a |> Result.map Seq.toArray
139 | | a -> Decode.Fail.arrExpected (Encoding a)
140 |
141 | static member propListD (decoder: JsonValue -> ParseResult<'a>) : JsonValue -> ParseResult> = function
142 | | JObject o -> traversei (fun i -> decoder >> Result.bindError (Decode.Fail.inner i)) (o |> Seq.map (|KeyValue|) |> toArray |> PropertyList)
143 | | a -> Decode.Fail.objExpected (Encoding a)
144 |
145 | static member decimalD x = Encoding.tryRead x
146 | static member int16D x = Encoding.tryRead x
147 | static member intD x = Encoding.tryRead x
148 | static member int64D x = Encoding.tryRead x
149 | static member uint16D x = Encoding.tryRead x
150 | static member uint32D x = Encoding.tryRead x
151 | static member uint64D x = Encoding.tryRead x
152 | static member byteD x = Encoding.tryRead x
153 | static member sbyteD x = Encoding.tryRead x
154 | static member floatD x = Encoding.tryRead x
155 | static member float32D x = Encoding.tryRead x
156 |
157 | static member enumD x : Result< 't, _> when 't: enum<_> =
158 | match x with
159 | | JString null -> Decode.Fail.nullString
160 | | JString s -> match Enum.TryParse s with (true, value) -> Ok value | _ -> Decode.Fail.invalidValue (Encoding x) s
161 | | a -> Decode.Fail.strExpected (Encoding a)
162 |
163 | static member booleanD x =
164 | match x with
165 | | JBool b -> Ok b
166 | | a -> Decode.Fail.boolExpected (Encoding a)
167 |
168 | static member stringD x =
169 | match x with
170 | | JString b -> Ok b
171 | | JNull -> Ok null
172 | | a -> Decode.Fail.strExpected (Encoding a)
173 |
174 | static member charD x =
175 | match x with
176 | | JString null -> Decode.Fail.nullString
177 | | JString s -> Ok s.[0]
178 | | a -> Decode.Fail.strExpected (Encoding a)
179 |
180 | static member bigintD x =
181 | match x with
182 | | JString null -> Decode.Fail.nullString
183 | | JString s -> match tryParse s with (Some (value: bigint)) -> Ok value | _ -> Decode.Fail.invalidValue (Encoding x) s
184 | | a -> Decode.Fail.strExpected (Encoding a)
185 |
186 | static member guidD x =
187 | match x with
188 | | JString null -> Decode.Fail.nullString
189 | | JString s -> match Guid.TryParse s with (true, value) -> Ok value | _ -> Decode.Fail.invalidValue (Encoding x) s
190 | | a -> Decode.Fail.strExpected (Encoding a)
191 |
192 | static member dateD x =
193 | match x with
194 | | JString null -> Decode.Fail.nullString
195 | | JString s ->
196 | match DateTime.TryParseExact (s, [|"yyyy-MM-dd" |], null, DateTimeStyles.RoundtripKind) with
197 | | true, t -> Ok t
198 | | _ -> Decode.Fail.invalidValue (Encoding x) ""
199 | | a -> Decode.Fail.strExpected (Encoding a)
200 |
201 | static member timeD x =
202 | match x with
203 | | JString null -> Decode.Fail.nullString
204 | | JString s ->
205 | match DateTime.TryParseExact (s, [| "HH:mm:ss.fff"; "HH:mm:ss" |], null, DateTimeStyles.RoundtripKind) with
206 | | true, t -> Ok t
207 | | _ -> Decode.Fail.invalidValue (Encoding x) ""
208 | | a -> Decode.Fail.strExpected (Encoding a)
209 |
210 | static member dateTimeD x =
211 | match x with
212 | | JString null -> Decode.Fail.nullString
213 | | JString s ->
214 | match DateTime.TryParseExact (s, [| "yyyy-MM-ddTHH:mm:ss.fffZ"; "yyyy-MM-ddTHH:mm:ssZ" |], null, DateTimeStyles.RoundtripKind) with
215 | | true, t -> Ok t
216 | | _ -> Decode.Fail.invalidValue (Encoding x) ""
217 | | a -> Decode.Fail.strExpected (Encoding a)
218 |
219 | static member dateTimeOffsetD x =
220 | match x with
221 | | JString null -> Decode.Fail.nullString
222 | | JString s ->
223 | match DateTimeOffset.TryParseExact (s, [| "yyyy-MM-ddTHH:mm:ss.fffK"; "yyyy-MM-ddTHH:mm:ssK" |], null, DateTimeStyles.RoundtripKind) with
224 | | true, t -> Ok t
225 | | _ -> Decode.Fail.invalidValue (Encoding x) ""
226 | | a -> Decode.Fail.strExpected (Encoding a)
227 |
228 | static member timeSpanD x =
229 | match x with
230 | | JString null -> Decode.Fail.nullString
231 | | JNumber _ as j -> Encoding.int64D j |> Result.map TimeSpan
232 | | a -> Decode.Fail.numExpected (Encoding a)
233 |
234 |
235 | //////////////
236 | // Encoders //
237 | //////////////
238 |
239 | static member resultE (encoder1: _ -> JsonValue) (encoder2: _ -> JsonValue) = function
240 | | Ok a -> jobj [ "Ok" , encoder1 a ]
241 | | Error a -> jobj [ "Error", encoder2 a ]
242 |
243 | static member choiceE (encoder1: _ -> JsonValue) (encoder2: _ -> JsonValue) = function
244 | | Choice1Of2 a -> jobj [ "Choice1Of2", encoder1 a ]
245 | | Choice2Of2 a -> jobj [ "Choice2Of2", encoder2 a ]
246 |
247 | static member choice3E (encoder1: _ -> JsonValue) (encoder2: _ -> JsonValue) (encoder3: _ -> JsonValue) = function
248 | | Choice1Of3 a -> jobj [ "Choice1Of3", encoder1 a ]
249 | | Choice2Of3 a -> jobj [ "Choice2Of3", encoder2 a ]
250 | | Choice3Of3 a -> jobj [ "Choice3Of3", encoder3 a ]
251 |
252 | static member optionE (encoder: _ -> JsonValue) = function
253 | | None -> JNull
254 | | Some a -> encoder a
255 |
256 | static member nullableE (encoder: _ -> JsonValue) (x: Nullable<'a>) = if x.HasValue then encoder x.Value else JNull
257 | static member arrayE (encoder: _ -> JsonValue) (x: 'a []) = JArray (Array.map encoder x |> Array.toList)
258 | static member propListE (encoder: _ -> JsonValue) (x: PropertyList<'a>) = x |> filter (fun (k, _) -> not (isNull k)) |> map encoder |> JObject
259 |
260 | static member tuple1E (encoder1: 'a -> JsonValue) (a: Tuple<_>) = JArray ([|encoder1 a.Item1|] |> Seq.toList)
261 | static member tuple2E (encoder1: 'a -> JsonValue) (encoder2: 'b -> JsonValue) (a, b) = JArray ([|encoder1 a; encoder2 b|] |> Seq.toList)
262 | static member tuple3E (encoder1: 'a -> JsonValue) (encoder2: 'b -> JsonValue) (encoder3: 'c -> JsonValue) (a, b, c) = JArray ([|encoder1 a; encoder2 b; encoder3 c|] |> Seq.toList)
263 | static member tuple4E (encoder1: 'a -> JsonValue) (encoder2: 'b -> JsonValue) (encoder3: 'c -> JsonValue) (encoder4: 'd -> JsonValue) (a, b, c, d) = JArray ([|encoder1 a; encoder2 b; encoder3 c; encoder4 d|] |> Seq.toList)
264 | static member tuple5E (encoder1: 'a -> JsonValue) (encoder2: 'b -> JsonValue) (encoder3: 'c -> JsonValue) (encoder4: 'd -> JsonValue) (encoder5: 'e -> JsonValue) (a, b, c, d, e) = JArray ([|encoder1 a; encoder2 b; encoder3 c; encoder4 d; encoder5 e|] |> Seq.toList)
265 | static member tuple6E (encoder1: 'a -> JsonValue) (encoder2: 'b -> JsonValue) (encoder3: 'c -> JsonValue) (encoder4: 'd -> JsonValue) (encoder5: 'e -> JsonValue) (encoder6: 'f -> JsonValue) (a, b, c, d, e, f) = JArray ([|encoder1 a; encoder2 b; encoder3 c; encoder4 d; encoder5 e; encoder6 f|] |> Seq.toList)
266 | static member tuple7E (encoder1: 'a -> JsonValue) (encoder2: 'b -> JsonValue) (encoder3: 'c -> JsonValue) (encoder4: 'd -> JsonValue) (encoder5: 'e -> JsonValue) (encoder6: 'f -> JsonValue) (encoder7: 'g -> JsonValue) (a, b, c, d, e, f, g) = JArray ([|encoder1 a; encoder2 b; encoder3 c; encoder4 d; encoder5 e; encoder6 f; encoder7 g|] |> Seq.toList)
267 |
268 | static member enumE (x: 't when 't: enum<_>) = JString (string x)
269 | static member unitE () = JArray []
270 |
271 | static member booleanE (x: bool ) = JBool x
272 | static member stringE (x: string ) = JString x
273 | static member dateE (x: DateTime ) = JString (x.ToString "yyyy-MM-dd")
274 | static member timeE (x: DateTime ) = JString (x.ToString "HH:mm:ss.fff")
275 | static member dateTimeE (x: DateTime ) = JString (x.ToString ("yyyy-MM-ddTHH:mm:ss.fffZ"))
276 | static member dateTimeOffsetE (x: DateTimeOffset) = JString (x.ToString ("yyyy-MM-ddTHH:mm:ss.fffK"))
277 | static member timeSpanE (x: TimeSpan ) = JsonHelpers.create x.Ticks
278 |
279 | static member decimalE (x: decimal ) = JsonHelpers.create x
280 | static member floatE (x: Double ) = JsonHelpers.create x
281 | static member float32E (x: Single ) = JsonHelpers.create x
282 | static member intE (x: int ) = JsonHelpers.create x
283 | static member uint32E (x: uint32 ) = JsonHelpers.create x
284 | static member int64E (x: int64 ) = JsonHelpers.create x
285 | static member uint64E (x: uint64 ) = JsonHelpers.create x
286 | static member int16E (x: int16 ) = JsonHelpers.create x
287 | static member uint16E (x: uint16 ) = JsonHelpers.create x
288 | static member byteE (x: byte ) = JsonHelpers.create x
289 | static member sbyteE (x: sbyte ) = JsonHelpers.create x
290 | static member charE (x: char ) = JsonHelpers.create x
291 | static member bigintE (x: bigint ) = JsonHelpers.create x
292 | static member guidE (x: Guid ) = JsonHelpers.create x
293 |
294 |
295 | ////////////
296 | // Codecs //
297 | ////////////
298 |
299 | static member result (codec1: Codec<_,_>) (codec2: Codec<_,_>) = Encoding.resultD (Codec.decode codec1) (Codec.decode codec2) <-> Encoding.resultE (Codec.encode codec1) (Codec.encode codec2)
300 |
301 | static member choice (codec1: Codec<_,_>) (codec2: Codec<_,_>) = Encoding.choiceD (Codec.decode codec1) (Codec.decode codec2) <-> Encoding.choiceE (Codec.encode codec1) (Codec.encode codec2)
302 | static member choice3 (codec1: Codec<_,_>) (codec2: Codec<_,_>) (codec3: Codec<_,_>) = Encoding.choice3D (Codec.decode codec1) (Codec.decode codec2) (Codec.decode codec3) <-> Encoding.choice3E (Codec.encode codec1) (Codec.encode codec2) (Codec.encode codec3)
303 | static member option (codec: Codec<_,_>) = Encoding.optionD (Codec.decode codec) <-> Encoding.optionE (Codec.encode codec)
304 | static member nullable (codec: Codec) = Encoding.nullableD (Codec.decode codec) <-> Encoding.nullableE (Codec.encode codec) : Codec>
305 | static member array (codec: Codec<_,_>) = Encoding.arrayD (Codec.decode codec) <-> Encoding.arrayE (Codec.encode codec)
306 | static member multiMap (codec: Codec<_,_>) = Encoding.propListD (Codec.decode codec) <-> Encoding.propListE (Codec.encode codec)
307 |
308 | static member boolean = Encoding.booleanD <-> Encoding.booleanE
309 | static member string = Encoding.stringD <-> Encoding.stringE
310 | static member date = Encoding.dateD <-> Encoding.dateE
311 | static member time = Encoding.timeD <-> Encoding.timeE
312 | static member dateTime = Encoding.dateTimeD <-> Encoding.dateTimeE
313 | static member dateTimeOffset = Encoding.dateTimeOffsetD <-> Encoding.dateTimeOffsetE
314 | static member timeSpan = Encoding.timeSpanD <-> Encoding.timeSpanE
315 | static member decimal = Encoding.decimalD <-> Encoding.decimalE
316 | static member float = Encoding.floatD <-> Encoding.floatE
317 | static member float32 = Encoding.float32D <-> Encoding.float32E
318 | static member int = Encoding.intD <-> Encoding.intE
319 | static member uint32 = Encoding.uint32D <-> Encoding.uint32E
320 | static member int64 = Encoding.int64D <-> Encoding.int64E
321 | static member uint64 = Encoding.uint64D <-> Encoding.uint64E
322 | static member int16 = Encoding.int16D <-> Encoding.int16E
323 | static member uint16 = Encoding.uint16D <-> Encoding.uint16E
324 | static member byte = Encoding.byteD <-> Encoding.byteE
325 | static member sbyte = Encoding.sbyteD <-> Encoding.sbyteE
326 | static member char = Encoding.charD <-> Encoding.charE
327 | static member bigint = Encoding.bigintD <-> Encoding.bigintE
328 | static member guid = Encoding.guidD <-> Encoding.guidE
329 |
330 |
331 | interface IEncoding with
332 | member _.boolean = Encoding.toIEncoding Encoding.boolean
333 | member _.string = Encoding.toIEncoding Encoding.string
334 | member _.dateTime t =
335 | match t with
336 | | Some DateTimeContents.Date -> Encoding.toIEncoding Encoding.date
337 | | Some DateTimeContents.Time -> Encoding.toIEncoding Encoding.time
338 | | _ -> Encoding.toIEncoding Encoding.dateTime
339 | member _.dateTimeOffset = Encoding.toIEncoding Encoding.dateTimeOffset
340 | member _.timeSpan = Encoding.toIEncoding Encoding.timeSpan
341 | member _.decimal = Encoding.toIEncoding Encoding.decimal
342 | member _.float = Encoding.toIEncoding Encoding.float
343 | member _.float32 = Encoding.toIEncoding Encoding.float32
344 | member _.int = Encoding.toIEncoding Encoding.int
345 | member _.uint32 = Encoding.toIEncoding Encoding.uint32
346 | member _.int64 = Encoding.toIEncoding Encoding.int64
347 | member _.uint64 = Encoding.toIEncoding Encoding.uint64
348 | member _.int16 = Encoding.toIEncoding Encoding.int16
349 | member _.uint16 = Encoding.toIEncoding Encoding.uint16
350 | member _.byte = Encoding.toIEncoding Encoding.byte
351 | member _.sbyte = Encoding.toIEncoding Encoding.sbyte
352 | member _.char = Encoding.toIEncoding Encoding.char
353 | member _.bigint = Encoding.toIEncoding Encoding.bigint
354 | member _.guid = Encoding.toIEncoding Encoding.guid
355 |
356 | member _.result c1 c2 = Encoding.toIEncoding (Encoding.result (Encoding.ofIEncoding c1) (Encoding.ofIEncoding c2))
357 | member _.choice c1 c2 = Encoding.toIEncoding (Encoding.choice (Encoding.ofIEncoding c1) (Encoding.ofIEncoding c2))
358 | member _.choice3 c1 c2 c3 = Encoding.toIEncoding (Encoding.choice3 (Encoding.ofIEncoding c1) (Encoding.ofIEncoding c2) (Encoding.ofIEncoding c3))
359 | member _.option c = Encoding.toIEncoding (Encoding.option (Encoding.ofIEncoding c))
360 | member _.array c = Encoding.toIEncoding (Encoding.array (Encoding.ofIEncoding c))
361 | member _.propertyList c = Encoding.toIEncoding (Encoding.multiMap (Encoding.ofIEncoding c))
362 |
363 | member _.enum<'t, 'u when 't : enum<'u> and 't : (new : unit -> 't) and 't : struct and 't :> ValueType> () : Codec = Encoding.toIEncoding (Encoding.enumD <-> Encoding.enumE)
364 |
365 | member x.getCase =
366 | match x with
367 | | Encoding (JNull ) -> "JNull"
368 | | Encoding (JBool _) -> "JBool"
369 | | Encoding (JNumber _) -> "JNumber"
370 | | Encoding (JString _) -> "JString"
371 | | Encoding (JArray _) -> "JArray"
372 | | Encoding (JObject _) -> "JObject"
--------------------------------------------------------------------------------