xyncro / chiron Goto Github PK
View Code? Open in Web Editor NEWJSON for F#
Home Page: https://xyncro.tech/chiron
License: MIT License
JSON for F#
Home Page: https://xyncro.tech/chiron
License: MIT License
See https://msdn.microsoft.com/en-us/library/bb384267%28v=vs.110%29.aspx
Unless a particular DateTime value represents UTC, that date and time value is often ambiguous or limited in its portability. For example, if a DateTime value represents the local time, it is portable within that local time zone (that is, if the value is deserialized on another system in the same time zone, that value still unambiguously identifies a single point in time). Outside the local time zone, that DateTime value can have multiple interpretations. If the value's Kind property is DateTimeKind.Unspecified, it is even less portable: it is now ambiguous within the same time zone and possibly even on the same system on which it was first serialized. Only if a DateTime value represents UTC does that value unambiguously identify a single point in time regardless of the system or time zone in which the value is used.
You more often want to represent something as a point-in-time, than something 'local to where you computer/server is'.
Shouldn't have to format Json as a string to serialize types which actually contain JSON...
If we serialise decimal, we lose the 128 bit accuracy; better to serialise it to a string by default and parse that string.
Json.tryParse "null3"
val it : Choice<Json,string> = Choice1Of2 (Null ())
compare to
Json.tryParse "ddd"
val it : Choice<Json,string> =
Choice2Of2
"Error in Ln: 1 Col: 1
ddd
^
Expecting: '"', '-', '0', '[', 'false', 'null', 'true' or '{'
"
I often have a tuple in F# 3-5 items long, but want to serialise it as an object;
then I need to do lots of shenanigans.
With these I don't:
module Json =
let writeNested key (toJson : _ -> Json<unit>) value : Json<unit> =
fun json ->
let inner = snd (toJson value json)
Json.write key inner json
module Patterns =
let inline (|InProperty|_|) key (fromJson : 'a -> Json<'a>) =
Aether.Lens.getPartial (Json.ObjectPLens >??> Aether.mapPLens key)
>> Option.bind (fromJson (Unchecked.defaultof<_>)
>> fst
>> function | Value x -> Some x
| Error _ -> None)
Means I can have this usage:
type Interact =
| Create of owner:Owner * createdByPrincipal:Id * bbf:Money
| TopUp of amount:Money
| Charge of amount:Money
static member ToJson (i : Interact) : Json<unit> =
let writeInner (owner, createdBy, bbf) : Json<unit> =
Json.write "owner" owner
*> Json.write "createdByPrincipal" createdBy
*> Json.write "bbf" bbf
match i with
| Create (o, cb, bbf) -> Json.writeNested "create" writeInner (o, cb, bbf)
| TopUp amount -> Json.write "topUp" amount
| Charge amount -> Json.write "charge" amount
static member FromJson (_ : Interact) : Json<Interact> =
let inline readInner _ : Json<_ * _ * _> =
(fun o cb bbf -> o, cb, bbf)
<!> Json.read "owner"
<*> Json.read "createdByPrincipal"
<*> Json.read "bbf"
function
| InProperty "create" readInner (o, cb, bbf) as json ->
Json.init (Create (o, cb, bbf)) json
| Property "topUp" amount as json ->
Json.init (TopUp amount) json
| Property "charge" amount as json ->
Json.init (Charge amount) json
| json ->
Json.error (sprintf "couldn't convert %A to Interact" json) json
What about adding them to the project?
Error in Ln: 1 Col: 54
{"updated":{"description":"a","key":"a","settings":{" ":"a"}}}
^
Expecting: '"' or '\\'
But it's a valid key; isn't it? I haven't seen that JSON requires trimming the keys for whitespace.
It's useful if you have data that's ordered and needs to be indexable.
Of course, it's easy to construct the list yourself, so if you don't feel this is in line with the library, just close the issue.
DU support in Defaults.
While migrating to this library, it's useful to have interop with Newtonsoft.
Provide a way to call the serialisation infrastructure through reflection for any given type, throwing a runtime exception if serialisation functions are unavailable.
You have a serialise example; could you show the deserialise example?
type TestUnion =
| One of string
| Two of int * bool
static member ToJson (x: TestUnion) =
match x with
| One (s) -> Json.write "one" s
| Two (i, b) -> Json.write "two" (i, b)
Consider how they should be handled; since they're not valid JSON, consider throwing an exception (partial function approach), or writing it as a string.
I would like to 'flatten' the json output of a nested type when serializing to json. To illustrate what I mean, below is the the logic I would like to express:
type EffortData =
{
lap : int
distance : distance
splits : SplitTracker []
}
static member ToJson (x:EffortData) =
Json.write "lap" x.lap
*> Json.write "distance" (x.distance.ToString())
for i in 0 .. x.splits.Length do
*> Json.write x.splits.[i].name x.splits.[i].duration
How can I accomplish this?
The current primary interface for using Chiron is based around functions which use statically-resolved type parameters (SRTP). This API should remain in place, but it should instead be based on an API that doesn't rely on SRTP.
type Decoder<'a> = Json -> JsonResult<'a>
type Encoder<'a> = 'a -> Json -> Json
ToJsonDefaults
and FromJsonDefaults
into functions under Encode
and Decode
modules, respectivelyDecoder<'a>
Encoder<'a>
fromJson {}
computation expression, adapted from json {}
toJson {}
computation expression.Add a compiler directive CHIRON_PUBLIC
to distinguish between the cases where Chiron is being published as a NuGet package versus as a single-file include. This same mechanism can also allow Chiron to be included once but shared within an application.
let tryDeserializeWith (fromJson : Json<'a>) : Json -> Choice<'a, string> =
fun json ->
match fromJson json with
| JsonResult.Value x, _ -> Choice.create x
| JsonResult.Error err, _ -> Choice.createSnd err
let tryDeserializeWithP (_, fromJson : Json<'a>) : Json -> Choice<'a, string> =
tryDeserializeWith fromJson
let deserializeWith (fromJson : Json<'a>) : Json -> 'a =
fun json ->
match tryDeserializeWith fromJson json with
| Choice1Of2 x -> x
| Choice2Of2 err -> failwith err
let deserializeWithP (_, fromJson : Json<'a>) : Json -> 'a =
deserializeWith fromJson
When Json is something like
"{"query":{"count":1,"created":"2015-08-25T13:35:55Z","lang":"en-US","results":{"quote":{"symbol":"ORCL","AverageDailyVolume":"15153400","Change":"+0.00","DaysLow":null,"DaysHigh":null,"YearLow":"35.14","YearHigh":"46.71","MarketCapitalization":"156.45B","LastTradePriceOnly":"36.08","DaysRange":null,"Name":"Oracle Corporation Common Stock","Symbol":"ORCL","Volume":"2200","StockExchange":"NYQ"}}}}"
When I try to parse with Json.read
DaysLow
for example I get:
> System.Exception: couldn't use lens (<fun:totalLensPartialIsomorphism@98>, <fun:totalLensPartialIsomorphism@99-1>) on json 'Null null'
at Microsoft.FSharp.Core.Operators.FailWith[T](String message)
at Network.Yahoo.Finance.parseResponse(String json) in H:\YFinance.fs\src\YFinanceFs\Finance.fs:line 58
at Network.Yahoo.Finance.getStockQuoteAsync@88-1.Invoke(String _arg1) in H:\YFinance.fs\src\YFinanceFs\Finance.fs:line 88
at Microsoft.FSharp.Control.AsyncBuilderImpl.args@835-1.Invoke(a a)
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.FSharp.Control.AsyncBuilderImpl.commit[a](Result`1 res)
at Microsoft.FSharp.Control.CancellationTokenOps.RunSynchronously[a](CancellationToken token, FSharpAsync`1 computation, FSharpOption`1 timeout)
at Microsoft.FSharp.Control.FSharpAsync.RunSynchronously[T](FSharpAsync`1 computation, FSharpOption`1 timeout, FSharpOption`1 cancellationToken)
at Network.Yahoo.Finance.getStockQuote(String symbol) in H:\YFinance.fs\src\YFinanceFs\Finance.fs:line 105
at <StartupCode$FSI_0003>.$FSI_0003.main@()
Using Json.readOrDefault "DaysLow" String.Empty
didn't solved the problem.
If json contains a null value I'd like to supply a default. Is this possible?
Thank you.
I don't think it's needed, so we can remove it from the API.
How do I read a collection of a known type?
type Folder =
{ _url : Uri option
email : string
files : File list
folders : Folder list
id : string
name : string }
static member FromJson (_ : Folder) =
(fun url email files folders id name ->
{ _url = Some (Uri url)
email = email
files = files
folders = folders
id = id
name = name })
<!> Json.read "@url"
<*> Json.read "Email"
<*> Json.read "Files" // error: no overloads match...
<*> Json.read "Folders"
<*> Json.read "Id"
<*> Json.read "Name"
I'm trying to read a File and a Folder list; File already has ToJson
and FromJson
.
Have you considered signing your assemblies with a strong name? I really like your project, and it works really well with F# compared to alternatives like the built in type providers and the reliable Newtonsoft.
The problem with not signing your assemblies is that you alienate the inclusion of your library in a project that is strongly named. I can get around this temporarily, but I have to actually sign your dlls with my own key. This is a bit of a hassle, especially if I want to release my work as an open source project.
Please consider this.
I've been trying to find a better way to write this with the current API, but can't think of it at the moment:
let writeJsonBody : SessionOptions -> Json<unit> =
List.fold (fun acc -> function
| LockDelay dur -> acc *> Json.write "LockDelay" (Duration.consulString dur)
| Node node -> acc *> Json.write "Node" node
| Name name -> acc *> Json.write "Name" name
| Checks checks -> acc *> Json.write "Checks" checks
| Behaviour sb -> acc *> Json.write "Behavior" sb
| TTL dur -> acc *> Json.write "TTL" (Duration.consulString dur))
(fun json -> Value (), Json.Object Map.empty)
let reqBody = Json.format (snd (writeJsonBody sessionOpts (Json.Null ())))
I'm mostly thinking how the last two lines with contents both declare Json data and how I have to use snd
to get the Json result of the monad value.
In your README.md
you say:
This is a fairly early work in progress, but usable enough to get feedback.
My feedback is that the library will benefit from adding even a minimal documentation, also some minimal sample(s) on README.md
itself (or link to a blog post).
Otherwise code is well written and usable and fills the gap of Json functional style library.
Excellent work! ๐
I've used it for porting an Haskell library dependent on Data.Aeson to F#.
I don't like how I've traversed Json AST, but I think that this can be enhanced and made clean using Lens (and it's not up to Chiron itself).
Thanks for sharing this project.
https://bugzilla.xamarin.com/show_bug.cgi?id=29240
Suggested fix: move to a 'full' framework instead.
type Account =
{ _url : Uri
active : bool
description : string
number : uint16
sru : uint16
year : uint16 }
static member FromJson (_ : Account) =
(fun u a d n s y ->
{ _url = Uri(u)
active = a
description = d
number = n
sru = s
year = y })
<!> Json.read "@url"
<*> Json.read "Active"
<*> Json.read "Description"
<*> Json.read "Number"
<*> Json.read "SRU"
<*> Json.read "Year"
static member ToJson (a : Account) =
Json.write "@url" (a._url.ToString())
*> Json.write "Active" a.active
*> Json.write "Description" a.description
*> Json.write "Number" a.number
*> Json.write "SRU" a.sru
*> Json.write "Year" a.year
let getAccounts (context : ConfiguredApi) : Async<_> = async {
let! resp = stdReq context Get "/accounts" |> getResponseAsync
let json = resp.EntityBody |> Option.map Json.parse |> Option.get
let (page : Page), (accounts : _ list) =
match json with
| Object values ->
match values |> Map.find "Accounts" with
| Array accounts ->
values |> Map.find "MetaInformation" |> (Json.deserialize : _ -> Page),
accounts |> List.map (Json.deserialize : _ -> Account)
| x -> failwithf "unexpected %A" x
| x -> failwithf "unexpected %A" x
return PaginatedResult (accounts, resp, page)
}
In particular where I want to zoom in on the object and pick out pieces of it.
Hi,
I have a question regarding a list serialization. Since I could not find any pointers in the tests or docs. I would be great if someone could point me in the right direction.
Given I have a type like this:
type Post = {
Id: Guid
Title: string
Body: string
} with static member ToJson(x:Post) = json{
do! Json.write "id" x.Id
do! Json.write "title" x.Title
do! Json.write "body" x.Body
}
and a list of posts like this
let post1 = {Id= Guid.NewGuid();Title="Title hello world post 1";Body="My body"}
let post2 = {Id= Guid.NewGuid();Title="Title hello world post 2";Body="My body"}
let listOfPosts = [post1;post2]
listOfPosts
|> Json.serialize
|> Json.formatWith JsonFormattingOptions.Pretty
this would results obviously in this
"[
{
"body": "My body",
"id": "0356c901-e1fa-4bae-8fe3-70061d2e05f3",
"title": "Title hello world post 1"
},
{
"body": "My body",
"id": "292a275b-960e-46b4-b04b-c3e214c78b0d",
"title": "Title hello world post 2"
}
]"
But how can I write a custom list serialization function that would produce something like this to an object graph:
"{
"0356c901-e1fa-4bae-8fe3-70061d2e05f3":{
"body": "My body",
"title": "Title hello world post 1"
},
"292a275b-960e-46b4-b04b-c3e214c78b0d":{
"body": "My body",
"title": "Title hello world post 2"
}
}"
Thanks for any help.
Currently decimals may lose data when roundtripping between values representing number with > 128bit precision. The parser should potentially be changed to accomodate this, or potentially warn/throw.
Title says it
Like:
type T =
| A
| B
static member ToJson (t : T) =
match t with
| A -> Json.write "a" (None : string option)
| B -> Json.write "b" (None : string option)
instead perhaps
Json.writeNullProp "a"
I have some types that are out of my control and I can add a custom SomeTypeToJson and SomeTypeFromJson functions. Serialization works fine using Json.serializeWith
but I've looked through the source and tests and just don't see how to use my custom deserializer. A Json.deserializeWith
function would make sense, but I really don't see anything like that or any other way to use it so I think I'm just missing it. Can you point me in the right direction, please?
Chiron will still provide Aether-compatible optics for consumption by others, but shouldn't directly depend on Aether itself.
Is it possible to extend the serialisation functions from another assembly? I'm trying to, but it seems the compiler isn't finding the static methods/functions.
I have a piece of data wrapped in an object naming it:
{"Voucher":{"@url":"https:\/\/
^^^ here starts the real object
^^^ wrapper object
I want to optionally 'unwrap' the outer object's property; what's the best way to do that?
Hello!
It would be great to support .NET Core that is getting more and more mature these days.
There is unfortunately the dependency to FParsec, they also got an issue raised on BitBucket:
https://bitbucket.org/fparsec/main/issues/38/support-net-core
Thanks in advance!
It was removed in the commit to re-create the nuget. This is useful for debugging/browsing into the library when I didn't compile it myself. Commit c3cc7e6
Per the advice given in a recent keynote by the Visual F# team, we should look at providing Chiron (and Aether) as portable libraries that can be consumed on .NET Core as well.
let inline read key =
fromJson >> Json.ofResult
=<< Json.getLensPartial (objectKeyPLens key)
// snip
let getLensPartial l : Json<_> =
fun json ->
match Lens.getPartial l json with
| Some x -> Value x, json
| _ -> Error "", json
returns an empty error message; it could possibly re-use they key to provide a nice error message "The property 'someKey' did not exist in the JSON structure".
Prefer JsonResult<'a>
as the return type for deserialization.
JsonError
, consisting of all potential JSON deserialization failures
Tag
case to allow structured reportingOtherDeserializationError
to allow consumers to extend with their own failuresJsonResult<'a>
to have a JsonError list
in the Error
caseJsonResult<'a>
formatError : JsonError -> string
functionJsonResult.toOption
functionJsonResult.toChoice
functionHello guys,
I would like to support this type. The goal is to allow me to wrap my json response in this type to avoid to repeat myself. All the sub type which are going to replace the 'T type are supported by chiron.
type JsonResult<'T> =
{ Code: int
Content: 'T
}
static member ToJson (x: JsonResult<'T>) =
Json.write "Code" x.Code
*> Json.write<'T> "Content" x.Content
The problem is I got this error:
This code is not sufficiently generic. The type variable ^T when ( ^T or ToJsonDefaults) : (static member ToJson : ^T -> Json<unit>) could not be generalized because it would escape its scope.
Is this feature possible ? If yes could you help me please :)
[Edit]: I suppose I need to restrict the Type of 'T but don't know how.
While the round-trip serialization of a List<'a>
is consistent, the JSON produced by serialization contains the serialized List<'a>
in reverse order. This can cause problems if the produced JSON is used by non-Chiron consumers and the order of the elements is important.
A default function for 'this property might not exist, because it was introduced later:
(fun ... -> ())
<!> // ...
<*> (Json.bind (Json.tryRead "images")
(function | None -> Json.init []
| Some x -> Json.init x))
By default I think it's a good idea to be backwards-compatible with schema, so if the library's user knows that a property has been introduced later, then there should be an easy way (i.e. one that doesn't require reasoning about the monad's structure) to read that properly optionally, like above.
If they are tagged, then issues can be filed again specific versions.
Breaking change, but standardizes on the new Result type provided by FSharp.Core
in 4.1.0
I'm trying to render long strings as json. A (minimal) example would is
String.replicate 30000 "a" |> Json.serialize |> Json.format |> printfn "%s"
The F# program (or script) with Chiron is taking much more time to render than comparable programs in, e.g., Go.
Is there anything that I could change, like maybe a more efficient string implementation, to improve the situation?
Thanks!
See 61cffa5
Another sample I'd like to see in the code base, let's say I have:
{ "entity": 45 }
OR
{ "entity": "SEK" }
And I want to match based on whether it's a string or a number; how would I do that best in FromJson
?
Wouldn't it be a good idea to type it all in Choice<,> instead of Option<>? That way your can pass errors on outside the Json monad.
fsharpi build.fsx
/Users/henrik.feldt/dev/haf/chiron/build.fsx(1,1): warning FS0211: The search directory '/Users/henrik.feldt/dev/haf/chiron/packages/FAKE/tools' could not be found
/Users/henrik.feldt/dev/haf/chiron/build.fsx(2,1): error FS0078: Unable to find the file 'packages/FAKE/tools/FakeLib.dll' in any of
/Library/Frameworks/Mono.framework/Versions/3.12.1/lib/mono/4.5
/Users/henrik.feldt/dev/haf/chiron
/Library/Frameworks/Mono.framework/Versions/3.12.1/lib/mono/4.0/
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.