git.iko.soy/rowtype-yoga/purescript-yoga-json
on v5.2.0 help sign in
162 lines 5.7 kB history raw
1# purescript-yoga-json
3`yoga-json` is a light-weight and simple json library for purescript.
5**Note**: This library was initially forked from the amazing [simple-json](https://github.com/justinwoo/purescript-simple-json) ([MIT Licence](./LICENSE/simple-json.LICENSE)).
6Replace your imports from `Simple.JSON` to `Yoga.JSON` to migrate.
7## Table of Contents
8* [features](#features)
9* [installation](#installation)
10* [usage](#usage)
12## Features
14* ๐Ÿ˜Œ simple and easy to use json codecs
15* ๐Ÿชถ light-weight
16* ๐Ÿค– built-in support for many common types (`Sum` types, Tuples, BigInts, Maps, JSDate, DateTime, Eithers, NonEmptyStrings )
17* ๐Ÿ– human-friendly error reporting
19## Installation
21```
22spago install yoga-json
23```
25## Usage
27`purescript-yoga-json` basically provides two functions `writeJSON` and `readJSON`.
29Use `writeJSON` to serialise a type to a JSON string:
31```purescript
32import Yoga.JSON as JSON
34serialised :: String
35serialised =
36 JSON.writeJSON { first_name: "Lola", last_name: "Flores" }
37 -- {"last_name":"Flores","first_name":"Lola"}
38```
40Use `readJSON` to deserialise a JSON string:
41```purescript
42import Yoga.JSON as JSON
44deserialised :: Either MultipleErrors { first_name :: String, last_name :: String }
45deserialised = JSON.readJSON """{ "first_name": "Lola", "last_name": "Flores" }"""
46-- Right { first_name: "Lola", last_name: "Flores" }
47```
49As the parsing can fail, `readJSON` returns an `Either`, potentially containing a `Left` data constructor with `MultipleErrors`. If you don't care about the specific errors, you can use `readJSON_`, which returns a `Maybe`:
51```purescript
52deserialised :: Maybe { first_name :: String, last_name :: String }
53deserialised = JSON.readJSON_ """{ "first_name": "Lola", "last_name": "Flores" }"""
54-- Just { first_name: "Lola", last_name: "Flores" }
55```
58## Sum types
60`purescript-yoga-json` provides utility functions to easily generate serialisers and deserialisers for your sum types.
62Let's start with a simple example of a sum type where our data constructors do not contain any further values:
64```
65import Data.Either (Either)
66import Data.Generic.Rep (class Generic)
67import Yoga.JSON as JSON
68import Yoga.JSON.Generics (genericReadForeignEnum, genericWriteForeignEnum)
69import Yoga.JSON.Generics.EnumSumRep as Enum
71data MyEnum = Enum1 | Enum2 | Enum3
72```
74Now, we need to derive a `Generic` instance for it:
76```
77derive instance Generic MyEnum _
78-- and optionally a Show instance
79instance Show MyEnum where
80 show = genericShow
81```
83Next, we define a `WriteForeign` instance and implement the `writeImpl` function using `genericWriteForeignEnum Enum.defaultOptions`.
85```purescript
86instance WriteForeign MyEnum
87 where
88 writeImpl = genericWriteForeignEnum Enum.defaultOptions
89```
91Similarly, we implement the `ReadForeign` instance using `genericReadForeignEnum Enum.defaultOptions`
92```purescript
93instance ReadForeign MyEnum
94 where
95 readImpl = genericReadForeignEnum Enum.defaultOptions
96```
98That's all, we can now serialise our data type:
99```purescript
100serialised = writeJSON { "myEnum": Enum3 }
101-- {"myEnum":"Enum3"}
102```
103and deserialise it:
104```purescript
105deserialised :: E { "myEnum" :: MyEnum }
106deserialised = readJSON serialised
107-- Right { myEnum: Enum3 }
108```
110### Writing custom codecs
112In order to write your own, custom codecs you will need to provide instances for `WriteForeign` and `ReadForeign`.
114Let's see an example. We define a simple data type `TrafficLight` that has three data constructors `Red`, `Yellow` and `Green`:
115```purescript
116data TrafficLight = Red | Yellow | Green
117```
118We would like to serialise these constructors as lower case strings `"red"`, `"yellow"` and `"green"`.
120First we need to provide an implementation for the `WriteForeign` type class, that tells `yoga-json` how to serialise the type. We pattern match on the three different data constructors and write them as the three `String`s red, yellow and green, using the same `writeImpl` function:
121```purescript
122instance WriteForeign TrafficLight where
123 writeImpl Red = writeImpl "red"
124 writeImpl Yellow = writeImpl "yellow"
125 writeImpl Green = writeImpl "green"
126```
127This works, because `yoga-json` already knows how to serialise a `String`.
129Similarly, we need to provide an implementation for the `ReadForeign` type class, that tells `yoga-json` how to deserialise the type. We start by deserialising into a primitive type, typically a String or an Object, and then convert it to our desired Purescript type.
131Since we don't know the input `String`, it might contain invalid data and therefore deserialisation might fail. `yoga-json` uses the `ExceptT` monad to reflect this. We can return valid values using `pure` and `fail` on invalid values:
133```purescript
134instance ReadForeign TrafficLight where
135 readImpl json = do
136 -- deserialise into a string
137 str :: String <- readImpl json
138 -- now we pattern match on our valid types
139 case str of
140 "red" -> pure Red
141 "yellow" -> pure Yellow
142 "green" -> pure Green
143 -- and fail if we get an invalid type
144 other -> fail $ ForeignError $ "Failed to parse " <> other <> " as TrafficLight"
145```
147Now we can serialise our data type:
148```purescript
149serialised = writeJSON { "trafficLight": Red }
150-- {"trafficLight":"red"}
151```
153and deserialise it:
154```purescript
155deserialised :: E { "trafficLight" :: TrafficLight }
156deserialised = readJSON """{ "trafficLight": "green" }"""
157-- Right { trafficLight: Green }
159deserialisedUnknown :: E { "trafficLight" :: TrafficLight }
160deserialisedUnknown = readJSON """{ "trafficLight": "purple" }"""
161-- Left (NonEmptyList (NonEmpty (ErrorAtProperty "trafficLight" (ForeignError "Failed to parse purple as TrafficLight")) Nil))
162```