├── docsrc ├── files │ ├── favicon.ico │ ├── tips.js │ ├── img │ │ └── logo-color.svg │ └── style.css ├── content │ ├── content │ │ ├── favicon.ico │ │ ├── tips.js │ │ ├── img │ │ │ └── logo.svg │ │ └── style.css │ ├── index.md │ ├── suave.fsx │ ├── combinators.fsx │ ├── further-techniques.fsx │ ├── giraffe.fsx │ ├── to-json-and-of-json.fsx │ ├── comparison-with-json-net.fsx │ └── codec.fsx ├── docs │ ├── Program.fs │ └── docs.fsproj └── tool │ ├── docsTool.fsproj │ ├── manually_build_docs.sh │ ├── tools.fs │ ├── templates │ ├── reference │ │ ├── part-nested.cshtml │ │ ├── namespaces.cshtml │ │ ├── part-members.cshtml │ │ ├── module.cshtml │ │ └── type.cshtml │ └── template.cshtml │ └── Program.fs ├── global.json ├── Directory.Build.targets ├── Benchmarks ├── error.json ├── Program.fs ├── fparsec.json ├── user.json ├── Benchmarks.fsproj ├── Prelude.fs ├── prettyuser.json └── Parsing.fs ├── nuget.config ├── .config └── dotnet-tools.json ├── .gitignore ├── src ├── Fleece │ ├── Fleece.fsproj │ └── Compatibility.fs ├── Fleece.FSharpData │ ├── Fleece.FSharpData.fsproj │ └── Fleece.FSharpData.fs ├── Fleece.NewtonsoftJson │ └── Fleece.NewtonsoftJson.fsproj ├── Fleece.SystemJson │ ├── Fleece.SystemJson.fsproj │ └── Fleece.SystemJson.fs └── Fleece.SystemTextJson │ └── Fleece.SystemTextJson.fsproj ├── test ├── Tests.SystemJson │ └── Tests.SystemJson.fsproj ├── Tests.FSharpData │ └── Tests.FSharpData.fsproj ├── Tests.SystemTextJson │ └── Tests.SystemTextJson.fsproj ├── IntegrationCompilationTests │ └── IntegrationCompilationTests.fsproj ├── Tests.NewtonsoftJson │ └── Tests.NewtonsoftJson.fsproj └── Tests │ ├── Lenses.fs │ └── LensesCompatibility.fs ├── Directory.Build.props ├── appveyor.yml ├── .github └── workflows │ └── dotnetcore.yml ├── RELEASE_NOTES.md ├── README.md ├── LICENSE └── Fleece.sln /docsrc/files/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fsprojects/Fleece/HEAD/docsrc/files/favicon.ico -------------------------------------------------------------------------------- /docsrc/content/content/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fsprojects/Fleece/HEAD/docsrc/content/content/favicon.ico -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "6.0.0", 4 | "rollForward": "latestMinor" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Benchmarks/error.json: -------------------------------------------------------------------------------- 1 | {"errorId":"30c7acf1-b448-48e3-a613-b50c4e266b11","message":"Ut duis tempor voluptate nisi in nostrud proident eiusmod excepteur qui nisi sit."} -------------------------------------------------------------------------------- /docsrc/docs/Program.fs: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org 2 | 3 | open System 4 | 5 | [] 6 | let main argv = 7 | printfn "Hello World from F#!" 8 | 0 // return an integer exit code 9 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "FSharp.Formatting.CommandTool": { 6 | "version": "11.5.1", 7 | "commands": [ 8 | "fsdocs" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.ide 2 | *.suo 3 | docs/* 4 | **/bin/* 5 | **/obj/* 6 | packages/* 7 | .nuget/*.exe 8 | *.nupkg 9 | .vs/ 10 | .idea/ 11 | 12 | .ionide 13 | 14 | docsrc/content/license.md 15 | docsrc/content/release-notes.md 16 | *.user 17 | 18 | # FSFormatting 19 | .fsdocs/ 20 | tmp/watch 21 | -------------------------------------------------------------------------------- /Benchmarks/Program.fs: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org 2 | module FleeceB.Benchmarks.Program 3 | 4 | open BenchmarkDotNet.Configs 5 | open BenchmarkDotNet.Analysers 6 | open BenchmarkDotNet.Diagnosers 7 | //open BenchmarkDotNet.Diagnostics.Windows 8 | open BenchmarkDotNet.Validators 9 | open BenchmarkDotNet.Running 10 | 11 | [] 12 | let main argv = 13 | let switcher = BenchmarkSwitcher thisAssembly 14 | let _ = switcher.Run argv 15 | 0 16 | -------------------------------------------------------------------------------- /src/Fleece/Fleece.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net6.0 5 | Serialization library 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Benchmarks/fparsec.json: -------------------------------------------------------------------------------- 1 | { 2 | "glossary": { 3 | "title": "example glossary", 4 | "GlossDiv": { 5 | "title": "S", 6 | "GlossList": { 7 | "GlossEntry": { 8 | "ID": "SGML", 9 | "SortAs": "SGML", 10 | "GlossTerm": "Standard Generalized Markup Language", 11 | "Acronym": "SGML", 12 | "Abbrev": "ISO 8879:1986", 13 | "GlossDef": { 14 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 15 | "GlossSeeAlso": ["GML", "XML"] 16 | }, 17 | "GlossSee": "markup" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docsrc/tool/docsTool.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Benchmarks/user.json: -------------------------------------------------------------------------------- 1 | {"_id":"57f1b86402a03d4fa66d49e0","index":0,"guid":"1d1ed680-e9be-483a-95c4-0a359f7579de","isActive":false,"balance":"$1,694.68","picture":"http://placehold.it/32x32","age":37,"eyeColor":"blue","name":"Malinda Young","gender":"female","company":"OVOLO","email":"malindayoung@ovolo.com","phone":"+1 (951) 411-3939","address":"108 Vernon Avenue, Garfield, Kansas, 3414","about":"Commodo officia culpa magna pariatur ea nostrud.","registered":"2015-09-13T11:24:08 +04:00","latitude":79.255199,"longitude":-145.819751,"tags":["cupidatat","ipsum","magna","mollit","irure","do","commodo"],"friends":[{"id":0,"name":"Jami Stewart"},{"id":1,"name":"Tillman Montoya"},{"id":2,"name":"Horton Vang"}],"greeting":"Hello, Malinda Young! You have 4 unread messages.","favoriteFruit":"strawberry","favoriteEscapes":["\r","\t","\u0000"]} -------------------------------------------------------------------------------- /src/Fleece.FSharpData/Fleece.FSharpData.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net6.0 5 | true 6 | JSON mapper for FSharp.Data 7 | FSHARPDATA;$(DefineConstants) 8 | --warnon:1182 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Fleece.NewtonsoftJson/Fleece.NewtonsoftJson.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net6.0 5 | true 6 | JSON mapper for Newtonsoft Json 7 | NEWTONSOFT;$(DefineConstants) 8 | --warnon:1182 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Fleece.SystemJson/Fleece.SystemJson.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net6.0 5 | true 6 | JSON mapper for System.Json 7 | SYSTEMJSON;$(DefineConstants) 8 | --warnon:1182 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Fleece.SystemTextJson/Fleece.SystemTextJson.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net6 5 | true 6 | JSON mapper for System.Text.Json 7 | SYSTEMTEXTJSON;$(DefineConstants) 8 | --warnon:1182 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /docsrc/content/index.md: -------------------------------------------------------------------------------- 1 | Fleece 2 | ====== 3 | 4 | Fleece is a library intended to help with parsing and crafting specific JSON without having to write data transfer objects in order to get the expected 5 | representation. 6 | 7 | ### Introduction 8 | 9 | You can get an overview of the important part of the library by reading the following pages: 10 | 11 | - [ToJson and OfJson](./to-json-and-of-json.html) are the basic building blocks 12 | - [Codec](./codec.html) let's you combine both ToJson and OfJson in one declaration 13 | - [Combinators](./combinators.html) lets you have more control 14 | - [Further Techniques](./further-techniques.html) describes ways of solving various JSON-wrangling problems 15 | 16 | ### Integration with Web frameworks 17 | 18 | In order to use Fleece mappings in your Web API you might follow one of the below guides: 19 | 20 | - [Giraffe](./giraffe.html) 21 | - [Suave](./suave.html) 22 | -------------------------------------------------------------------------------- /docsrc/docs/docs.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6 6 | 7 | 8 | 9 | 10 | Docs\%(FileName)%(Extension) 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/Tests.SystemJson/Tests.SystemJson.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6;net461 6 | SYSTEMJSON;$(DefineConstants) 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /docsrc/tool/manually_build_docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | pushd $(dirname "${0}") > /dev/null 3 | cd ../../ 4 | # Restore 5 | dotnet tool restore 6 | 7 | # Build 8 | dotnet restore 9 | #msbuild /t:Build /p:Configuration=Debug 10 | dotnet build -c Release 11 | 12 | # Gen docs 13 | dotnet run --project ./docsrc/tool 14 | # In order to release, append "ReleaseDocs" when running the command 15 | 16 | # dotnet fsdocs "build" "--input" "docsrc/content/" "--output" "/Users/mathieu/src/fs/Fleece/docsrc/tool/../../docs" "--sourcerepo" "https://github.com/fsprojects/Fleece/tree/master" "--parameters" "root" "/Fleece/" "project-name" "Fleece" "project-author" "Mauricio Scheffer,Lev Gorodinski,Oskar Gewalli, Gustavo P. Leon" "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." "project-github" "https://github.com/fsprojects/Fleece" "project-nuget" "http://nuget.org/packages/Fleece" -------------------------------------------------------------------------------- /test/Tests.FSharpData/Tests.FSharpData.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp31;net6 6 | FSHARPDATA;$(DefineConstants) 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/Tests.SystemTextJson/Tests.SystemTextJson.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp31;net6 6 | SYSTEMTEXTJSON;$(DefineConstants) 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/IntegrationCompilationTests/IntegrationCompilationTests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Exe 6 | net6 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/Tests.NewtonsoftJson/Tests.NewtonsoftJson.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp31;net6 6 | NEWTONSOFT;$(DefineConstants) 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Benchmarks/Benchmarks.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Benchmarks/Prelude.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module internal Prelude 3 | 4 | open System.Reflection 5 | //open BenchmarkDotNet 6 | open BenchmarkDotNet.Configs 7 | open BenchmarkDotNet.Analysers 8 | open BenchmarkDotNet.Diagnosers 9 | open BenchmarkDotNet.Jobs 10 | //open BenchmarkDotNet.Diagnostics.Windows 11 | open BenchmarkDotNet.Validators 12 | 13 | type Dummy = Dummy 14 | 15 | type CoreConfig() = 16 | inherit ManualConfig() 17 | do 18 | base.Add(Job.MediumRun) 19 | base.Add(EnvironmentAnalyser.Default) 20 | base.Add(MemoryDiagnoser.Default) 21 | base.Add(BaselineValidator.FailOnError) 22 | base.Add(JitOptimizationsValidator.FailOnError) 23 | 24 | let thisAssembly = typeof.GetTypeInfo().Assembly 25 | 26 | let loadJsonResource name = 27 | let path = "Benchmarks." + name + ".json" 28 | let res = thisAssembly.GetManifestResourceStream path 29 | if isNull res then failwithf "Failed to load '%s'." path 30 | res 31 | 32 | let loadJsonResourceAsString name = 33 | use stream = loadJsonResource name 34 | use reader = new System.IO.StreamReader(stream) 35 | reader.ReadToEnd() -------------------------------------------------------------------------------- /Benchmarks/prettyuser.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id": "57f1b86402a03d4fa66d49e0", 3 | "index": 0, 4 | "guid": "1d1ed680-e9be-483a-95c4-0a359f7579de", 5 | "isActive": false, 6 | "balance": "$1,694.68", 7 | "picture": "http://placehold.it/32x32", 8 | "age": 37, 9 | "eyeColor": "blue", 10 | "name": "Malinda Young", 11 | "gender": "female", 12 | "company": "OVOLO", 13 | "email": "malindayoung@ovolo.com", 14 | "phone": "+1 (951) 411-3939", 15 | "address": "108 Vernon Avenue, Garfield, Kansas, 3414", 16 | "about": "Commodo officia culpa magna pariatur ea nostrud.", 17 | "registered": "2015-09-13T11:24:08 +04:00", 18 | "latitude": 79.255199, 19 | "longitude": -145.819751, 20 | "tags": [ 21 | "cupidatat", 22 | "ipsum", 23 | "magna", 24 | "mollit", 25 | "irure", 26 | "do", 27 | "commodo" 28 | ], 29 | "friends": [ 30 | { 31 | "id": 0, 32 | "name": "Jami Stewart" 33 | }, 34 | { 35 | "id": 1, 36 | "name": "Tillman Montoya" 37 | }, 38 | { 39 | "id": 2, 40 | "name": "Horton Vang" 41 | } 42 | ], 43 | "greeting": "Hello, Malinda Young! You have 4 unread messages.", 44 | "favoriteFruit": "strawberry", 45 | "favoriteEscapes": [ 46 | "\r", 47 | "\t", 48 | "\u0000" 49 | ] 50 | } -------------------------------------------------------------------------------- /docsrc/files/tips.js: -------------------------------------------------------------------------------- 1 | var currentTip = null; 2 | var currentTipElement = null; 3 | 4 | function hideTip(evt, name, unique) { 5 | var el = document.getElementById(name); 6 | el.style.display = "none"; 7 | currentTip = null; 8 | } 9 | 10 | function findPos(obj) { 11 | // no idea why, but it behaves differently in webbrowser component 12 | if (window.location.search == "?inapp") 13 | return [obj.offsetLeft + 10, obj.offsetTop + 30]; 14 | 15 | var curleft = 0; 16 | var curtop = obj.offsetHeight; 17 | while (obj) { 18 | curleft += obj.offsetLeft; 19 | curtop += obj.offsetTop; 20 | obj = obj.offsetParent; 21 | }; 22 | return [curleft, curtop]; 23 | } 24 | 25 | function hideUsingEsc(e) { 26 | if (!e) { e = event; } 27 | hideTip(e, currentTipElement, currentTip); 28 | } 29 | 30 | function showTip(evt, name, unique, owner) { 31 | document.onkeydown = hideUsingEsc; 32 | if (currentTip == unique) return; 33 | currentTip = unique; 34 | currentTipElement = name; 35 | 36 | var pos = findPos(owner ? owner : (evt.srcElement ? evt.srcElement : evt.target)); 37 | var posx = pos[0]; 38 | var posy = pos[1]; 39 | 40 | var el = document.getElementById(name); 41 | var parent = (document.documentElement == null) ? document.body : document.documentElement; 42 | el.style.position = "absolute"; 43 | el.style.left = posx + "px"; 44 | el.style.top = posy + "px"; 45 | el.style.display = "block"; 46 | } -------------------------------------------------------------------------------- /docsrc/content/content/tips.js: -------------------------------------------------------------------------------- 1 | var currentTip = null; 2 | var currentTipElement = null; 3 | 4 | function hideTip(evt, name, unique) { 5 | var el = document.getElementById(name); 6 | el.style.display = "none"; 7 | currentTip = null; 8 | } 9 | 10 | function findPos(obj) { 11 | // no idea why, but it behaves differently in webbrowser component 12 | if (window.location.search == "?inapp") 13 | return [obj.offsetLeft + 10, obj.offsetTop + 30]; 14 | 15 | var curleft = 0; 16 | var curtop = obj.offsetHeight; 17 | while (obj) { 18 | curleft += obj.offsetLeft; 19 | curtop += obj.offsetTop; 20 | obj = obj.offsetParent; 21 | }; 22 | return [curleft, curtop]; 23 | } 24 | 25 | function hideUsingEsc(e) { 26 | if (!e) { e = event; } 27 | hideTip(e, currentTipElement, currentTip); 28 | } 29 | 30 | function showTip(evt, name, unique, owner) { 31 | document.onkeydown = hideUsingEsc; 32 | if (currentTip == unique) return; 33 | currentTip = unique; 34 | currentTipElement = name; 35 | 36 | var pos = findPos(owner ? owner : (evt.srcElement ? evt.srcElement : evt.target)); 37 | var posx = pos[0]; 38 | var posy = pos[1]; 39 | 40 | var el = document.getElementById(name); 41 | var parent = (document.documentElement == null) ? document.body : document.documentElement; 42 | el.style.position = "absolute"; 43 | el.style.left = posx + "px"; 44 | el.style.top = posy + "px"; 45 | el.style.display = "block"; 46 | } -------------------------------------------------------------------------------- /docsrc/tool/tools.fs: -------------------------------------------------------------------------------- 1 | module Tools 2 | open System 3 | let () x y = IO.Path.Combine(x,y) 4 | 5 | 6 | module Path = 7 | // Paths with template/source/output locations 8 | let content = __SOURCE_DIRECTORY__ "../content" 9 | let output = __SOURCE_DIRECTORY__ "../../docs" 10 | let files = __SOURCE_DIRECTORY__ "../files" 11 | let templates = __SOURCE_DIRECTORY__ "./templates" 12 | 13 | let dir p = IO.Path.GetDirectoryName(p: string) 14 | let filename p = IO.Path.GetFileName(p: string) 15 | let changeExt ext p = IO.Path.ChangeExtension(p, ext) 16 | let docTemplate = "docpage.cshtml" 17 | 18 | module Directory = 19 | let ensure dir = 20 | if not (IO.Directory.Exists dir) then 21 | IO.Directory.CreateDirectory dir |> ignore 22 | 23 | let copyRecursive (path: string) dest = 24 | let path = 25 | if not (path.EndsWith(string IO.Path.DirectorySeparatorChar)) then 26 | path + string IO.Path.DirectorySeparatorChar 27 | else 28 | path 29 | let trim (p: string) = 30 | if p.StartsWith(path) then 31 | p.Substring(path.Length) 32 | else 33 | failwithf "Cannot find path root" 34 | IO.Directory.EnumerateFiles(path, "*", IO.SearchOption.AllDirectories) 35 | |> Seq.iter (fun p -> 36 | let target = dest trim p 37 | ensure(Path.dir target) 38 | IO.File.Copy(p, target, true)) 39 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | https://github.com/fsprojects/Fleece 5 | https://github.com/fsprojects/Fleece 6 | Mauricio Scheffer, Lev Gorodinski, Oskar Gewalli, Gustavo M. Wild 7 | Apache-2.0 8 | README.md 9 | json fsharp 10 | 3 11 | --nowarn:3186 $(OtherFlags) 12 | 0.11.0-RC1 13 | 14 | $(VersionPrefix)-$(VersionSuffix) 15 | $(VersionPrefix) 16 | $(VersionPrefix).0 17 | $(VersionPrefix).0 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | runtime; build; native; contentfiles; analyzers 26 | all 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /docsrc/tool/templates/reference/part-nested.cshtml: -------------------------------------------------------------------------------- 1 | @if (Enumerable.Count(Model.Types) > 0) { 2 | 3 | 4 | 5 | 6 | 7 | @foreach (var it in Model.Types) 8 | { 9 | 10 | 13 | 23 | 24 | } 25 | 26 |
TypeDescription
11 | @it.Name 12 | 14 | @if (it.IsObsolete) 15 | { 16 |
17 | WARNING: This API is obsolete 18 |

@Html.Encode(@it.ObsoleteMessage)

19 |
20 | } 21 | @it.Comment.Blurb 22 |
27 | } 28 | @if (Enumerable.Count(Model.Modules) > 0) { 29 | 30 | 31 | 32 | 33 | 34 | @foreach (var it in Model.Modules) 35 | { 36 | 37 | 40 | 50 | 51 | } 52 | 53 |
ModuleDescription
38 | @it.Name 39 | 41 | @if (it.IsObsolete) 42 | { 43 |
44 | WARNING: This API is obsolete 45 |

@Html.Encode(@it.ObsoleteMessage)

46 |
47 | } 48 | @it.Comment.Blurb 49 |
54 | } -------------------------------------------------------------------------------- /docsrc/tool/templates/reference/namespaces.cshtml: -------------------------------------------------------------------------------- 1 | @using FSharp.MetadataFormat 2 | @{ 3 | Layout = "template"; 4 | Title = "Namespaces - " + Properties["project-name"]; 5 | } 6 | 7 |

@Model.Name

8 | 9 | @{ var nsIndex = 0; } 10 | @foreach (var ns in Model.Namespaces) 11 | { 12 | nsIndex++; 13 | var typedNs = (Namespace)ns; 14 | var allCategories = 15 | typedNs.Types.Select(t => t.Category) 16 | .Concat(typedNs.Modules.Select(m => m.Category)) 17 | .Distinct() 18 | .OrderBy(s => String.IsNullOrEmpty(s) ? "ZZZ" : s); 19 | var allByCategory = 20 | allCategories 21 | .Select((c, i) => new { 22 | Name = String.IsNullOrEmpty(c) ? "Other namespace members" : c, 23 | Index = String.Format("{0}_{1}", nsIndex, i), 24 | Types = typedNs.Types.Where(t => t.Category == c) 25 | .Where(t=>!t.Name.StartsWith("Default") 26 | &&!t.Name.StartsWith("Id1") 27 | &&!t.Name.StartsWith("Id2")).ToArray(), 28 | Modules = typedNs.Modules.Where(m => m.Category == c).ToArray() }) 29 | .Where(c => c.Types.Length + c.Modules.Length > 0).ToArray(); 30 | 31 |

@ns.Name Namespace

32 | if (allByCategory.Length > 1) 33 | { 34 | 35 |
    36 | @foreach (var g in allByCategory) 37 | { 38 |
  • @g.Name
  • 39 | } 40 |
41 | } 42 | foreach(var g in allByCategory) 43 | { 44 | if (allByCategory.Length > 1) 45 | { 46 |

@g.Name

47 | } 48 |
49 | @RenderPart("part-nested", new 50 | { 51 | Types = g.Types, 52 | Modules = g.Modules 53 | }) 54 |
55 | } 56 | } 57 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2022 2 | 3 | before_build: 4 | - ps: >- 5 | $buildId = $env:APPVEYOR_BUILD_NUMBER.PadLeft(5, '0'); 6 | $versionSuffixPR = "-PR$($env:APPVEYOR_PULL_REQUEST_NUMBER)-$buildId"; 7 | $branchName = "$env:APPVEYOR_REPO_BRANCH".Replace("_",""); 8 | $versionSuffixBRANCH = "-$branchName-$buildId"; 9 | $env:VersionSuffix = if ("$env:APPVEYOR_REPO_TAG" -eq "true") { "" } else { if ("$env:APPVEYOR_PULL_REQUEST_NUMBER") { $versionSuffixPR } else { $versionSuffixBRANCH } }; 10 | 11 | init: 12 | - git config --global core.autocrlf input 13 | build_script: 14 | - cmd: echo vs %VersionSuffix%" 15 | - cmd: build.bat 16 | test_script: 17 | - cmd: dotnet run --project .\test\Tests.SystemJson\Tests.SystemJson.fsproj -c Release -f net461 18 | - cmd: dotnet run --project .\test\Tests.SystemJson\Tests.SystemJson.fsproj -c Release -f net6 19 | - cmd: dotnet run --project .\test\Tests.FSharpData\Tests.FSharpData.fsproj -c Release -f net6 20 | - cmd: dotnet run --project .\test\Tests.FSharpData\Tests.FSharpData.fsproj -c Release -f netcoreapp31 21 | - cmd: dotnet run --project .\test\Tests.NewtonsoftJson\Tests.NewtonsoftJson.fsproj -c Release -f net6 22 | - cmd: dotnet run --project .\test\Tests.NewtonsoftJson\Tests.NewtonsoftJson.fsproj -c Release -f netcoreapp31 23 | - cmd: dotnet run --project .\test\Tests.SystemTextJson\Tests.SystemTextJson.fsproj -c Release -f net6 24 | - cmd: dotnet run --project .\test\Tests.SystemTextJson\Tests.SystemTextJson.fsproj -c Release -f netcoreapp31 25 | - cmd: dotnet run --project .\test\IntegrationCompilationTests\IntegrationCompilationTests.fsproj -c Release -f net6 26 | 27 | artifacts: 28 | # pushing all *.nupkg files in build directory recursively 29 | - path: '**\*.nupkg' 30 | name: nupkgs 31 | type: NuGetPackage 32 | nuget: 33 | account_feed: true 34 | project_feed: true 35 | # disable_publish_on_pr: false 36 | -------------------------------------------------------------------------------- /docsrc/content/suave.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: Suave, 2.5.6" 7 | #r @"../../src/Fleece.SystemJson/bin/Release/netstandard2.0/Fleece.dll" 8 | #r @"../../src/Fleece.SystemJson/bin/Release/netstandard2.0/Fleece.SystemJson.dll" 9 | 10 | (** 11 | ## Suave 12 | 13 | In this page we will get an overview of how you can use Fleece together with Suave. 14 | 15 | A minimal integration can be done by 16 | 17 | *) 18 | 19 | open Suave 20 | open Suave.Http 21 | open Suave.Operators 22 | open System.IO 23 | open System.Text 24 | // Fleece and Json related: 25 | open System.Json 26 | open Fleece.SystemJson 27 | open Fleece.SystemJson.Operators 28 | 29 | module BusinessApp= 30 | [] 31 | module Json = 32 | let inline OK (dataObj) : WebPart= 33 | let str = toJson dataObj |> string 34 | Successful.OK str 35 | >=> Writers.setMimeType "application/json; charset=utf-8" 36 | 37 | let inline parseRequestForm (ctx : HttpContext) = 38 | let body = ctx.request.rawForm |> Encoding.UTF8.GetString 39 | parseJson body 40 | 41 | (** 42 | In the web API part of your business app you would then do something like the code below: 43 | *) 44 | 45 | open BusinessApp 46 | 47 | type Person = { Name : string } 48 | with 49 | static member JsonObjCodec = 50 | fun name -> { Name = name } 51 | jreq "name" (Some << fun x -> x.Name) 52 | 53 | let personHandler : WebPart = 54 | warbler (fun ctx -> 55 | match Json.parseRequestForm ctx with // instead of using mapJson 56 | | Ok (person:Person)-> 57 | Json.OK person 58 | // and ideally we would deal with case when the parsing fails as well 59 | ) 60 | -------------------------------------------------------------------------------- /.github/workflows/dotnetcore.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Setup .NET Core 6 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 6.0.300 20 | - name: Set Timezone 21 | uses: szenius/set-timezone@v1.2 22 | with: 23 | timezoneWindows: "Nepal Standard Time" 24 | - name: Install dependencies 25 | run: dotnet restore 26 | - name: Build 27 | run: dotnet build -c Release 28 | - name: Test SystemJson 29 | run: dotnet run --no-build --project ./test/Tests.SystemJson/Tests.SystemJson.fsproj -c Release -f net6 30 | - name: Test FSharpData 31 | run: dotnet run --no-build --project ./test/Tests.FSharpData/Tests.FSharpData.fsproj -c Release -f net6 32 | - name: Test NewtonsoftJson 33 | run: dotnet run --no-build --project ./test/Tests.NewtonsoftJson/Tests.NewtonsoftJson.fsproj -c Release -f net6 34 | - name: Test SystemTextJson 35 | run: dotnet run --no-build --project ./test/Tests.SystemTextJson/Tests.SystemTextJson.fsproj -c Release -f net6 36 | - name: Test Integration 37 | run: dotnet run --no-build --project ./test/IntegrationCompilationTests/IntegrationCompilationTests.fsproj -c Release -f net6 38 | 39 | docs: 40 | 41 | runs-on: ubuntu-latest 42 | 43 | steps: 44 | - uses: actions/checkout@v2 45 | - name: Setup .NET Core 6 46 | uses: actions/setup-dotnet@v1 47 | with: 48 | dotnet-version: 6.0.300 49 | - name: Restore dotnet tools 50 | run: dotnet tool restore 51 | - name: Install dependencies 52 | run: dotnet restore 53 | - name: Build 54 | run: dotnet build -c Release 55 | - name: Build Doc src 56 | run: dotnet build -c Release ./docsrc/docs 57 | - name: Render Docs 58 | run: dotnet run --project ./docsrc/tool 59 | 60 | -------------------------------------------------------------------------------- /docsrc/files/img/logo-color.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /docsrc/content/content/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /docsrc/content/combinators.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 | ```f# 12 | #r "nuget: Fleece.SystemJson" 13 | ``` 14 | 15 | *) 16 | 17 | open Fleece 18 | open Fleece.SystemJson 19 | open Fleece.SystemJson.Operators 20 | 21 | (** 22 | 23 | ## Combinators 24 | 25 | So far we've seen how Fleece is capable of encoding/decoding by deriving automatically a codec from static members in the type. 26 | 27 | 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. 28 | 29 | To do so, a set of the available functions exists, ending with the `With` suffix, which accepts a combinator as first parameter: 30 | 31 | *) 32 | 33 | 34 | type Color = Red | Blue | White 35 | 36 | type Car = { 37 | Id : string 38 | Color : Color 39 | Kms : int } 40 | 41 | let colorDecoder = function 42 | | JString "red" -> Decode.Success Red 43 | | JString "blue" -> Decode.Success Blue 44 | | JString "white" -> Decode.Success White 45 | | JString x as v -> Decode.Fail.invalidValue v ("Wrong color: " + x) 46 | | x -> Decode.Fail.strExpected x 47 | 48 | let colorEncoder = function 49 | | Red -> JString "red" 50 | | Blue -> JString "blue" 51 | | White -> JString "white" 52 | 53 | let colorCodec = colorDecoder <-> colorEncoder 54 | 55 | let []carCodec<'t> = 56 | codec { 57 | let! i = jreqWith Codecs.string "id" (fun x -> Some x.Id) 58 | and! c = jreqWith colorCodec "color" (fun x -> Some x.Color) 59 | and! k = jreqWith Codecs.int "kms" (fun x -> Some x.Kms) 60 | return { Id = i; Color = c; Kms = k } 61 | } 62 | |> Codec.compose (Codecs.propList Codecs.id) 63 | 64 | let car = { Id = "xyz"; Color = Red; Kms = 0 } 65 | 66 | let jsonCar = Codec.encode carCodec car 67 | // val jsonCar : JsonValue = {"id": "xyz", "color": "red", "kms": 0} 68 | -------------------------------------------------------------------------------- /docsrc/tool/templates/reference/part-members.cshtml: -------------------------------------------------------------------------------- 1 | @if (Enumerable.Count(Model.Members) > 0) { 2 |

@Model.Header

3 | 4 | 5 | 6 | 7 | 8 | @foreach (var it in Model.Members) 9 | { 10 | 11 | 36 | 57 | 58 | } 59 | 60 |
@Model.TableHeaderDescription
12 | @{ var id = Html.UniqueID().ToString(); } 13 | 14 | @Html.Encode(it.Details.FormatUsage(40)) 15 | 16 |
17 | Signature: @Html.Encode(it.Details.Signature)
18 | @if (!it.Details.Modifiers.IsEmpty) { 19 | Modifiers: @it.Details.FormatModifiers
20 | } 21 | @if (!it.Details.TypeArguments.IsEmpty) { 22 | Type parameters: @it.Details.FormatTypeArguments 23 | } 24 | @if (Enumerable.Any(it.Attributes)) 25 | { 26 | 27 | Attributes:
28 | @foreach (var attr in it.Attributes) 29 | { 30 | @Html.Encode(@attr.Format())
31 | } 32 |
33 | } 34 |
35 |
37 | @if (it.IsObsolete) 38 | { 39 |
40 | WARNING: This API is obsolete 41 |

@Html.Encode(@it.ObsoleteMessage)

42 |
43 | } 44 | @if (!String.IsNullOrEmpty(it.Details.FormatSourceLocation)) 45 | { 46 | 47 | 48 | 49 | 50 | } 51 | @it.Comment.FullText 52 | @if (!String.IsNullOrEmpty(it.Details.FormatCompiledName)) 53 | { 54 | @:

CompiledName: @it.Details.FormatCompiledName

55 | } 56 |
61 | } 62 | -------------------------------------------------------------------------------- /docsrc/tool/templates/template.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | @Title 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 |
24 |
25 | 29 |

@Properties["project-name"]

30 |
31 |
32 |
33 |
34 | @RenderBody() 35 |
36 |
37 | F# Project 38 | 52 |
53 |
54 |
55 | Fork me on GitHub 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 |

@Model.Module.Name

35 |

36 | @if (Model.Module.IsObsolete) 37 | { 38 |

39 | WARNING: This API is obsolete 40 |

@Html.Encode(@Model.Module.ObsoleteMessage)

41 |
42 | } 43 | Namespace: @Model.Namespace.Name
44 | @if(Model.ParentModule.Exists()) 45 | { 46 | Parent Module: @Model.ParentModule.Value.Name 47 | } 48 | @if (Model.Module.Attributes.Any()) 49 | { 50 | 51 | Attributes:
52 | @foreach (var attr in Model.Module.Attributes) 53 | { 54 | @Html.Encode(@attr.Format())
55 | } 56 | 57 |
58 | } 59 |

60 |
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 |

@sec.Key

69 | } 70 | @sec.Value 71 | } 72 | } 73 |
74 | @if (byCategory.Count() > 1) 75 | { 76 | 77 |

Table of contents

78 |
    79 | @foreach (var g in byCategory) 80 | { 81 |
  • @g.Name
  • 82 | } 83 |
84 | } 85 | 86 | 87 | @if (nestTypes.Count() + nestModules.Count() > 0) 88 | { 89 |

Nested types and modules

90 |
91 | @RenderPart("part-nested", new { 92 | Types = nestTypes, 93 | Modules = nestModules 94 | }) 95 |
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 |

@Model.Type.Name

33 |

34 | 35 | @if (Model.Type.IsObsolete) 36 | { 37 |

38 | WARNING: This API is obsolete 39 |

@Html.Encode(@Model.Type.ObsoleteMessage)

40 |
41 | } 42 | Namespace: @Model.Namespace.Name
43 | @if (Model.HasParentModule) 44 | { 45 | Parent Module: @Model.ParentModule.Value.Name
46 | } 47 | @if (type.Attributes.Any()) 48 | { 49 | 50 | Attributes:
51 | @foreach (var attr in type.Attributes) 52 | { 53 | @Html.Encode(@attr.Format())
54 | } 55 | 56 |
57 | } 58 |

59 |
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 |

@sec.Key

70 | } 71 | @sec.Value 72 | } 73 | } 74 |
75 | @if (byCategory.Count() > 1) 76 | { 77 | 78 |

Table of contents

79 |
    80 | @foreach (var g in byCategory) 81 | { 82 |
  • @g.Name
  • 83 | } 84 |
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" --------------------------------------------------------------------------------