Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Json mapper for F#

License

NotificationsYou must be signed in to change notification settings

fsprojects/Fleece

Repository files navigation

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.

The Json library could beSystem.Json,System.Text.Json,FSharp.Data's orNewtonSoft's Json.NET.

Its design is strongly influenced by Haskell'sAeson. Like Aeson, Fleece is designed around two typeclasses (inFSharpPlus style) ToJson and OfJson.

Download binaries

Example

For example, given this data type:

typePerson={    Name:string    Age:int    Children:Person list}

You can map it to JSON like this:

openFleeceopenFleece.OperatorstypePersonwithstatic memberToJson(x:Person)=        jobj["name".= x.Name"age".= x.Age"children".= x.Children]letp={ Person.Name="John"      Age=44      Children=[{ Person.Name="Katy"          Age=5          Children=[]}{ Person.Name="Johnny"          Age=7          Children=[]}]}// Test with System.Text.JsonopenFleece.SystemTextJsonprintfn"%s"(toJsonText p)

And you can map it from JSON like this:

typePersonwithstatic memberOfJson json=match jsonwith| JObject o->letname= o.@"name"letage= o.@"age"letchildren= o.@"children"match name, age, childrenwith| Decode.Success name, Decode.Success age, Decode.Success children->                Decode.Success{                    Person.Name= name                    Age= age                    Children= children}| x-> Error<| Uncategorized(sprintf"Error parsing person:%A" x)| x-> Decode.Fail.objExpected xletjohn:Person ParseResult= ofJsonText"""{"name": "John", "age": 44, "children": [{"name": "Katy", "age": 5, "children": []}, {"name": "Johnny", "age": 7, "children": []}]}"""

Though it's much easier to do this in a monadic or applicative way. For example, usingFSharpPlus (which is already a dependency of Fleece):

openFSharpPlustypePersonwithstatic memberCreate name age children={ Person.Name= name; Age= age; Children= children}static memberOfJson json=match jsonwith| JObject o-> Person.Create<!>(o.@"name")<*>(o.@"age")<*>(o.@"children")| x-> Decode.Fail.objExpected x

or with applicatives:

openFSharpPlustypePersonwithstatic memberOfJson json=match jsonwith| JObject o->             monad{let!name= o.@"name"and! age= o.@"age"and! children= o.@"children"return{                    Person.Name= name                    Age= age                    Children= children}}| x-> Decode.Fail.objExpected x

Or you can use the Choice monad/applicative inFSharpx.Extras instead, if you prefer.

You can see more examples in theEdmundsNet project.

CODEC

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.

typePerson={     name:string* string    age:int option    children:Person list}withstatic memberget_Codec()=fun f l a c->{ name=(f, l); age= a; children= c}<!> jreq"firstName"(Some<<fun x-> fst x.name)<*> jreq"lastName"(Some<<fun x-> snd x.name)<*> jopt"age"(fun x-> x.age)// Optional fields can use 'jopt'<*> jreq"children"(fun x-> Some x.children)|> ofObjCodecletjohn:Person ParseResult= ofJsonText"""{"name": "John", "age": 44, "children": [{"name": "Katy", "age": 5, "children": []}, {"name": "Johnny", "age": 7, "children": []}]}"""

Discriminated unions can be modeled with alternatives:

typeShape=| Rectangleofwidth:float*length:float| Circleofradius:float| Prismofwidth:float*float*height:floatwithstatic memberget_Codec()=(Rectangle<!> jreq"rectangle"(function Rectangle(x, y)-> Some(x, y)|_-> None))<|>(Circle<!> jreq"radius"(function Circle x-> Some x|_-> None))<|>(Prism<!> jreq"prism"(function Prism(x, y, z)-> Some(x, y, z)|_-> None))|> ofObjCodec

or using the jchoice combinator:

typeShapewithstatic memberJsonObjCodec=            jchoice[                    Rectangle<!> jreq"rectangle"(function Rectangle(x, y)-> Some(x, y)|_-> None)                    Circle<!> jreq"radius"(function Circle x-> Some x|_-> None)                    Prism<!> jreq"prism"(function Prism(x, y, z)-> Some(x, y, z)|_-> None)]|> ofObjCodec

But codecs for both types can easily be written with the codec computation expressions

typePerson={     name:string* string    age:int option    children:Person list}withstatic memberget_Codec()=            codec{let!f= jreq"firstName"(Some<<fun x-> fst x.name)and! l= jreq"lastName"(Some<<fun x-> snd x.name)and! a= jopt"age"(fun x-> x.age)// Optional fields can use 'jopt'and! c= jreq"children"(fun x-> Some x.children)return{ name=(f, l); age= a; children= c}}|> ofObjCodectypeShape=| Rectangleofwidth:float*length:float| Circleofradius:float| Prismofwidth:float*float*height:floatwithstatic memberget_Codec()=            codec{                Rectangle<!> jreq"rectangle"(function Rectangle(x, y)-> Some(x, y)|_-> None)                Circle<!> jreq"radius"(function Circle x-> Some x|_-> None)                Prism<!> jreq"prism"(function Prism(x, y, z)-> Some(x, y, z)|_-> None)}|> ofObjCodec

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.

We can also do that by hand, we can manipulate codecs by using functions in the Codec module. Here's an example:

openSystem.TextopenFleece.SystemTextJson.OperatorstypePerson={     name:string* string    age:int option    children:Person list}withstatic memberJsonObjCodec:Codec<PropertyList<Fleece.SystemTextJson.Encoding>,Person>= codec{let!f= jreq"firstName"(Some<<fun x-> fst x.name)and! l= jreq"lastName"(Some<<fun x-> snd x.name)and! a= jopt"age"(fun x-> x.age)// Optional fields can use 'jopt'and! c= jreq"children"(fun x-> Some x.children)return{ name=(f, l); age= a; children= c}}letpersonBytesCodec=    Person.JsonObjCodec|> Codec.compose jsonObjToValueCodec// this is the codec that fills the gap to/from JsonValue|> Codec.compose jsonValueToTextCodec// this is a codec between JsonValue and JsonText|> Codec.invmap(Encoding.UTF8.GetString: byte[]-> string) Encoding.UTF8.GetBytes// This is a pair of of isomorphic functionsletp={ name="John","Smith"; age= Some42; children=[]}letbytePerson= Codec.encode personBytesCodec p// val bytePerson : byte [] = [|123uy; 34uy; 102uy; 105uy; 114uy; 115uy; ... |]letp'= Codec.decode personBytesCodec bytePerson

Combinators

So far we've seen how Fleece is capable of encoding/decoding by deriving automatically a codec from static members in the type.

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.

To do so, a set of the available functions exists, ending with theWith suffix, which accepts a combinator as first parameter:

typeColor= Red| Blue| WhitetypeCar={    Id:string    Color:Color    Kms:int}letcolorDecoder=function| JString"red"-> Decode.Success Red| JString"blue"-> Decode.Success Blue| JString"white"-> Decode.Success White| JString  xas v-> Decode.Fail.invalidValue v("Wrong color:"+ x)| x-> Decode.Fail.strExpected  xletcolorEncoder=function| Red-> JString"red"| Blue-> JString"blue"| White-> JString"white"letcolorCodec()= colorDecoder<-> colorEncoderletcarCodec()=    codec{let!i= jreqWith Codecs.string"id"(fun x-> Some x.Id)and! c= jreqWith colorCodec"color"(fun x-> Some x.Color)and! k= jreqWith Codecs.int"kms"(fun x-> Some x.Kms)return{ Id= i; Color= c; Kms= k}}|> Codec.compose(Codecs.propList Codecs.id)letcar={ Id="xyz"; Color= Red; Kms=0}letjsonCar:Fleece.SystemTextJson.Encoding= Codec.encode(carCodec()) car// val jsonCar: SystemTextJson.Encoding = {"id":"xyz","color":"red","kms":0}

Json Lenses

Json lenses allow to focus on a specific part of the json structure to perform operations like view, write and update.

For a quick reference have a look atthis test file

Maintainer(s)


[8]ページ先頭

©2009-2025 Movatter.jp