You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
`argonaut-codecs` provides codecs based on the `EncodeJson` and `DecodeJson` type classes, along with instances for common data types and combinators for encoding and decoding `Json` values.
9
+
Argonaut is a library for working with JSON in PureScript. `argonaut-codecs` provides codecs based on the `EncodeJson` and `DecodeJson` type classes, along with instances for common data types and combinators for encoding and decoding `Json` values.
Module documentation is [published on Pursuit](https://pursuit.purescript.org/packages/purescript-argonaut-codecs).
23
+
Module documentation is [published on Pursuit](https://pursuit.purescript.org/packages/purescript-argonaut-codecs).
24
24
25
25
You may also be interested in other libraries in the Argonaut ecosystem:
26
26
27
-
-[purescript-argonaut-core](https://github.com/purescript-contrib/purescript-argonaut-core) -- defines the `Json` type, along with basic parsing, printing, and folding functions which operate on it
27
+
-[purescript-argonaut-core](https://github.com/purescript-contrib/purescript-argonaut-core) -- defines the `Json` type, along with basic parsing, printing, and folding functions
28
28
-[purescript-argonaut-traversals](https://github.com/purescript-contrib/purescript-argonaut-traversals) -- defines prisms, traversals, and zippers for the `Json` type.
29
-
30
-
-[purescript-argonaut-generic](https://github.com/purescript-contrib/purescript-argonaut-generic) -- supports generic encoding and decoding for any type with a `Generic` instance, which can be used to define instances of `EncodeJson` and `DecodeJson`.
31
-
32
-
Codecs based on type classes are not always the appropriate approach for encoding and decoding JSON in your application. See [purescript-codec-argonaut](https://github.com/garyb/purescript-codec-argonaut) for an alternative, more explicit approach, which includes a comparison to this library.
29
+
-[purescript-argonaut-generic](https://github.com/purescript-contrib/purescript-argonaut-generic) -- supports generic encoding and decoding for any type with a `Generic` instance
30
+
-[purescript-codec-argonaut](https://github.com/garyb/purescript-codec-argonaut) -- an alternative approach for codecs, which are based on profunctors instead of type classes
33
31
34
32
## Quick Start
35
33
36
34
Use `encodeJson` to encode PureScript data types as `Json` and `decodeJson` to decode `Json` into PureScript types, with helpful error messages if decoding fails.
37
35
38
36
```purs
39
-
type User =
40
-
{ name :: String
41
-
, age :: Maybe Int
42
-
}
37
+
type User = { name :: String, age :: Maybe Int }
43
38
44
-
-- We get encoding and decoding for free because of the `EncodeJson` instances
39
+
-- We get encoding and decoding for free because of the `EncodeJson` instances
45
40
-- for records, strings, integers, and `Maybe`, along with many other common
46
41
-- PureScript types.
47
42
@@ -66,15 +61,9 @@ Right { name: "Tom", age: Just 25 }
66
61
Left "JSON was missing expected field: age"
67
62
```
68
63
69
-
This library provides helpful combinators for writing `EncodeJson` and `DecodeJson` instances for your own data types. See [the tutorial](#Tutorial) for a detailed walkthrough of this library.
70
-
71
64
## Tutorial
72
65
73
-
Argonaut is a library for working with JSON in PureScript.
74
-
75
-
The core `Json` data type and basic parsing, printing and folding functions are defined in the `argonaut-core` library, but these functions are low-level.
76
-
77
-
This library, `argonaut-codecs`, provides type classes and combinators for convenient (and often automatic) encoding and decoding of `Json` for data types in your application, and includes instances for encoding and decoding most common PureScript types.
66
+
This library provides provides type classes and combinators for convenient encoding and decoding of `Json` for data types in your application, and includes instances for encoding and decoding most common PureScript types.
78
67
79
68
As a brief aside: this library works with `Json` values, not raw JSON strings.
80
69
@@ -83,43 +72,58 @@ As a brief aside: this library works with `Json` values, not raw JSON strings.
83
72
84
73
#### Setup
85
74
86
-
We recommend following along with this tutorial in the repl. Start a repl and import these modules -- later, we'll define some types to encode and decode.
75
+
You can follow along with this tutorial in a repl. You should install these dependencies:
Next, import the modules used in this tutorial -- you can also install `argonaut` and only import `Data.Argonaut` if you'd like to cut down on imports:
87
86
88
87
```
89
88
import Prelude
90
89
91
-
import Data.Argonaut
90
+
import Control.Alternative
91
+
import Data.Argonaut.Core
92
+
import Data.Argonaut.Encode
93
+
import Data.Argonaut.Decode
94
+
import Data.Argonaut.Parser
92
95
import Data.Maybe
93
96
import Data.Either
97
+
import Data.Validation.Semigroup
94
98
```
95
99
96
-
> Tip: you can place this snippet in a `.purs-repl` file so the imports are loaded automatically when you run `pulp repl` or `spago repl`.
100
+
> Tip: you can place this snippet in a `.purs-repl` file so the imports are loaded automatically when you run `spago repl` or `pulp repl`.
97
101
98
-
### Automatic Encoding & Decoding
102
+
### Automatic Encoding & Decoding
99
103
100
-
The `EncodeJson` and `DecodeJson` type classes let you rely on instances for common data types to automatically encode and decode `Json`.
101
-
102
-
Let's explored automatic encoding and decoding using a type typical of PureScript applications as our example:
104
+
The `EncodeJson` and `DecodeJson` type classes let you rely on instances for common data types to automatically encode and decode `Json`. Let's explore automatic encoding and decoding using a type typical of PureScript applications as our example:
103
105
104
106
```purs
105
107
type User =
106
-
{ name :: String
108
+
{ name :: String
107
109
, age :: Maybe Int
108
-
, team :: Maybe String
110
+
, team :: Maybe String
109
111
}
110
112
```
111
113
112
114
> Tip: If you're following along in the repl, you can either define this type on one line or use `:paste` to input multiple lines followed by Ctrl+D to end the paste.
113
115
114
116
##### Automatic encoding with `EncodeJson` and `encodeJson`
115
117
116
-
Our `User` type is made up of several other types: `Record`, `Maybe`, `Int`, and `String`. Each of these types have instances for `EncodeJson`, which means that we can use the `encodeJson` function to transform them into `Json`:
118
+
We can automatically encode `Json` using the `EncodeJson` type class ([pursuit](https://pursuit.purescript.org/packages/purescript-argonaut-codecs/docs/Data.Argonaut.Encode#t:EncodeJson)).
119
+
120
+
Our `User` type is made up of several other types: `Record`, `Maybe`, `Int`, and `String`. Each of these types have instances for `EncodeJson`, which means that we can use the `encodeJson` function with them. Integers and strings will be encoded directly to `Json`, while container types like `Record` and `Maybe` will require on all of the types they contain to also have `EncodeJson` instances.
117
121
118
122
```purs
119
123
encodeJson :: EncodeJson a => a -> Json
120
124
```
121
125
122
-
> Tip: There is no `Show` instance for `Json`. To print a `Json` value as a valid JSON string, use `stringify` -- it's the same as the [JavaScript `stringify` method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify).
126
+
> Tip: There is no `Show` instance for `Json`. To print a `Json` value as a valid JSON string, use `stringify` -- it's the same as the [JavaScript `stringify` method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify).
123
127
124
128
```purs
125
129
> user = { name: "Tom", age: Just 25, team: Just "Red Team" } :: User
@@ -129,7 +133,9 @@ encodeJson :: EncodeJson a => a -> Json
129
133
130
134
##### Automatic decoding with `DecodeJson` and `decodeJson`
131
135
132
-
Every type within `User` has an instance for `DecodeJson`, which means we can use the `decodeJson` function to try to decode a `Json` value into our type:
136
+
We can automatically decode `Json` using the `DecodeJson` type class ([pursuit](https://pursuit.purescript.org/packages/purescript-argonaut-codecs/docs/Data.Argonaut.Decode#t:DecodeJson)).
137
+
138
+
Every type within `User` has an instance for `DecodeJson`, which means we can use the `decodeJson` function to try to decode a `Json` value into our type. Once again, integer and string values will be decoded directly from the `Json`, but containing types like `Record` and `Maybe` will also require instances for the types they contain.
133
139
134
140
```purs
135
141
decodeJson :: DecodeJson a => Json -> Either String a
@@ -141,7 +147,7 @@ decodeJson :: DecodeJson a => Json -> Either String a
# there is no `Show` instance for `Json`, so we'll stringify
150
+
# there is no `Show` instance for `Json`, so we'll stringify
145
151
# the decoded result so it can be displayed in the repl
146
152
> map stringify decodedUser
147
153
Right "{\"name\":\"Tom\",\"age\":25,\"team\":null}"
@@ -170,7 +176,7 @@ Let's explore the combinators provided by `argonaut-codecs` for encoding and dec
170
176
> Remember that you can write multi-line definitions using by typing :paste in the repl, and then using Ctrl+D to exit when you're done.
171
177
172
178
```purs
173
-
newtype AppUser = AppUser
179
+
newtype AppUser = AppUser
174
180
{ name :: String
175
181
, age :: Maybe Int
176
182
, team :: Team
@@ -185,7 +191,7 @@ data Team
185
191
186
192
To encode JSON, you must decide on a way to represent your data using only primitive JSON types (strings, numbers, booleans, arrays, objects, or null). Since PureScript's string, number, boolean, and array types already have `EncodeJson` instances, your responsibility is to find a way to transform your data types to those more primitive types so they can be encoded.
187
193
188
-
Let's start with our `Team` type, which doesn't have an `EncodeJson` instance yet. It can be represented in JSON by simple strings, so let's write a function to convert `Team` to a `String`:
194
+
Let's start with our `Team` type, which doesn't have an `EncodeJson` instance yet. It can be represented in JSON by simple strings, so let's write a function to convert `Team` to a `String`:
189
195
190
196
```purs
191
197
teamToString :: Team -> String
@@ -207,11 +213,11 @@ instance encodeJsonTeam :: EncodeJson Team where
207
213
encodeJson team = encodeJson (teamToString team)
208
214
```
209
215
210
-
If your type can be converted easily to a `String`, `Number`, or `Boolean`, then its `EncodeJson` instance will most likely look like the one we've written for `Team`.
216
+
If your type can be converted easily to a `String`, `Number`, or `Boolean`, then its `EncodeJson` instance will most likely look like the one we've written for `Team`.
211
217
212
218
Most reasonably complex data types are best represented as objects, however. We can use combinators from `Data.Argonaut.Encode.Combinators` to conveniently encode `Json` objects manually. You'll provide `String` keys and values which can be encoded to `Json`.
213
219
214
-
- Use `:=` (`assoc`) to encode a key/value pair where the key must exist; encoding the key `"team"` and value `Nothing` will insert the key `"team"` with the value `null`.
220
+
- Use `:=` (`assoc`) to encode a key/value pair where the key must exist; encoding the key `"team"` and value `Nothing` will insert the key `"team"` with the value `null`.
215
221
- Use `~>` (`extend`) to provide more key/value pairs after using `:=`.
216
222
- Use `:=?` (`assocOptional`) to encode a key/value pair where the key _may_ exist; encoding the key `"age"` and value `Nothing` will not insert the `"age"` key.
217
223
- Use `~>?` (`extendOptional`) to provide more key/value pairs after using `:=?`.
@@ -220,7 +226,7 @@ Let's use these combinators to encode a `Json` object from our `AppUser` record.
220
226
221
227
```purs
222
228
instance encodeJsonAppUser :: EncodeJson AppUser where
223
-
encodeJson (AppUser { name, age, team }) =
229
+
encodeJson (AppUser { name, age, team }) =
224
230
"name" := name -- inserts "name": "Tom"
225
231
~> "age" :=? age -- inserts "age": "25" (if Nothing, does not insert anything)
226
232
~>? "team" := team -- inserts "team": "Red Team"
@@ -268,12 +274,10 @@ If your type can be represented easily with a `String`, `Number`, `Boolean`, or
268
274
269
275
However, quite often your data type will require representation as an object. This library provides combinators in `Data.Argonaut.Decode.Combinators` which are useful for decoding objects into PureScript types by looking up keys in the object and decoding them according to their `DecodeJson` instances.
270
276
271
-
- Use `.:` (`getField`) to decode a field where the value must exist
272
-
- Use `.:?` (`getFieldOptional'`) to decode a field where the key may not exist, or the value may be `null`
273
-
- Use `.:!` (`getFieldOptional`) to decode a field where the key may not exist. If the key exists, the value will be decoded -- even if it is `null`. This is rare; most of the time you want `.:?` instead.
274
-
- Use `.!=` (`defaultField`) to provide a default value for a field which may not exist. If decoding fails, you'll still get an error; if decoding succeeds with a value of type `Maybe a`, then this default value will handle the `Nothing` case.
277
+
- Use `.:` (`getField`) to decode a field; if the field is missing, this will decode to a `Maybe`
278
+
- Use `.=` (`defaultField`) to provide a default value for a field which may not exist. If decoding fails, you'll still get an error; if decoding succeeds with a value of type `Maybe a`, then this default value will handle the `Nothing` case.
275
279
276
-
Let's use these combinators to decode a `Json` object into our `AppUser` record.
280
+
Let's use these combinators to decode a `Json` object into our `AppUser` record.
277
281
278
282
The `decodeJson` function returns an `Either String a` value; `Either` is a monad, which means we can use convenient `do` syntax to write our decoder. If a step in decoding succeeds, then its result is passed to the next step. If any step in decoding fails, the entire computation will abort with the error it encountered.
279
283
@@ -282,8 +286,8 @@ instance decodeJsonAppUser :: DecodeJson AppUser where
282
286
decodeJson json = do
283
287
obj <- decodeJson json -- decode `Json` to `Object Json`
284
288
name <- obj .: "name" -- decode the "name" key to a `String`
285
-
age <- obj .:? "age" -- decode the "age" key to a `Maybe Int`
286
-
team <- obj .:? "team" .!= RedTeam -- decode "team" to `Team`, defaulting to `RedTeam`
289
+
age <- obj .: "age" -- decode the "age" key to a `Maybe Int`
290
+
team <- obj .: "team" .!= RedTeam -- decode "team" to `Team`, defaulting to `RedTeam`
287
291
-- if the field is missing or `null`
288
292
pure $ AppUser { name, age, team }
289
293
```
@@ -292,9 +296,11 @@ To recap: manually decoding your data type involves a few steps:
292
296
293
297
1. Ensure that all types you are decoding have a `DecodeJson` instance
294
298
295
-
2. Use `.:` to decode object fields which must exist, `.:?` for fields which may exist or may be null, and `.!=` to provide a default value for fields which may exist and are being decoded into a type which must exist.
299
+
2. Use `.:` to decode object fields
300
+
301
+
3. Use `.!=` to provide a default value for fields which may exist in the `Json`, but must exist in the type you're decoding to (it's like `fromMaybe` for your ) d and are being decoded into a type which must exist.
296
302
297
-
3. It's common to use the `Either` monad for convenience when writing decoders. Any failed decoding step will abort the entire computation with that error. See [Solving Common Problems](#solving-common-problems) for alternative approaches to decoding.
303
+
4. It's common to use the `Either` monad for convenience when writing decoders. Any failed decoding step will abort the entire computation with that error. See [Solving Common Problems](#solving-common-problems) for alternative approaches to decoding.
298
304
299
305
#### Deriving Instances
300
306
@@ -370,9 +376,9 @@ instance decodeJsonUser :: DecodeJson User where
##### 2. Write multiple `encodeJson` or `decodeJson` functions
379
+
##### 2. Write multiple `encodeJson` or `decodeJson` functions
374
380
375
-
Another option is to have a default representation for the type implemented as the type class instance, but alternative `decodeJson` and `encodeJson` functions which can be used directly. For example, consider the case in which our `User` data can be sent to multiple sources. One source requires the data to be formatted as an object, and another requires it to be formatted as a two-element array.
381
+
Another option is to have a default representation for the type implemented as the type class instance, but alternative `decodeJson` and `encodeJson` functions which can be used directly. For example, consider the case in which our `User` data can be sent to multiple sources. One source requires the data to be formatted as an object, and another requires it to be formatted as a two-element array.
376
382
377
383
In this case, our type class instance can use the default object encoding, and we can supply a separate `encodeJsonAsArray` function for use when required.
You may occasionally be unable to write `EncodeJson` or `DecodeJson` instances for a data type because it requires more information than just `Json` as its argument. For instance, consider this pair of types:
390
396
391
397
```purs
392
-
data Author
398
+
data Author
393
399
= Following String -- you are subscribed to this author
394
400
| NotFollowing String -- you aren't subscribed to this author
395
401
| You -- you are the author
396
402
397
403
type BlogPost =
398
-
{ title :: String
404
+
{ title :: String
399
405
, author :: Author
400
406
}
401
407
```
@@ -417,7 +423,7 @@ decodeJsonAuthor maybeUsername json = do
417
423
Just (Username username) | author == username -> You
418
424
-- user is not the author, or no one is logged in, so use the `following` flag
419
425
otherwise -> author # if following then Following else NotFollowing
420
-
426
+
421
427
decodeJsonBlogPost :: Maybe Username -> Json -> Either String BlogPost
422
428
decodeJsonBlogPost username json = do
423
429
obj <- decodeJson json
@@ -444,10 +450,10 @@ instance decodeJsonPreciseDateTime :: DecodeJson PreciseDateTime where
444
450
decodeJson json = fromString =<< decodeJson json
445
451
where
446
452
fromString :: String -> Either String PreciseDateTime
447
-
fromString =
453
+
fromString =
448
454
map PreciseDateTime
449
-
<<< note "Could not parse RFC3339 string"
450
-
<<< PDT.fromRFC3339String
455
+
<<< note "Could not parse RFC3339 string"
456
+
<<< PDT.fromRFC3339String
451
457
<<< RFC3339String
452
458
```
453
459
@@ -461,15 +467,15 @@ For example, let's say we have a `User` type which occasionally gets bad input,
461
467
462
468
```purs
463
469
newtype User = User
464
-
{ name :: String
470
+
{ name :: String
465
471
, age :: Maybe Int
466
472
, location :: String
467
473
}
468
474
469
475
derive instance newtypeUser :: Newtype User _
470
476
derive newtype instance showUser :: Show User
471
477
472
-
decodeUser :: Json -> Either (Array String) User
478
+
decodeUser :: Json -> Either (Array String) User
473
479
decodeUser json = do
474
480
obj <- decodeJson json
475
481
name <- obj .: "name"
@@ -494,7 +500,7 @@ decodeJsonV = either (invalid <<< pure) pure <<< decodeJson
494
500
495
501
-- a replacement for `getField`
496
502
getFieldV :: forall a. DecodeJson a => Object Json -> String -> V (Array String) a
497
-
getFieldV object key =
503
+
getFieldV object key =
498
504
either (invalid <<< pure) pure (object .: key)
499
505
500
506
-- a replacement for .:
@@ -504,7 +510,7 @@ infix 7 getFieldV as .:|
504
510
With this new operator and applicative-do we can recreate our original decoder, except with accumulating errors this time:
505
511
506
512
```purs
507
-
decodeUser :: Json -> Either (Array String) User
513
+
decodeUser :: Json -> Either (Array String) User
508
514
decodeUser json = do
509
515
user <- toEither $ V.andThen (decodeJsonV json) \obj -> ado
510
516
name <- obj .:| "name"
@@ -519,7 +525,7 @@ This decoder will now print all errors:
0 commit comments