Skip to content

Commit 565c7e6

Browse files
committed
Improve docs
1 parent fea201b commit 565c7e6

File tree

1 file changed

+85
-29
lines changed

1 file changed

+85
-29
lines changed

README.md

Lines changed: 85 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,41 +22,65 @@ Module documentation is [published on Pursuit](http://pursuit.purescript.org/pac
2222
Some of Argonaut's functions might seem a bit arcane at first, so it can help
2323
to understand the underlying design decisions which make it the way it is.
2424

25-
One approach for modelling JSON values would be to define an ADT, like this:
25+
One approach for modelling JSON values would be to define an algebraic data
26+
type, like this:
2627

2728
```purescript
2829
data Json
2930
= JNull
31+
| JString String
32+
| JNumber Number
3033
| JBoolean Boolean
3134
| JArray (Array Json)
3235
| JObject (StrMap Json)
33-
[...]
3436
```
3537

3638
And indeed, some might even say this is the obvious approach.
3739

3840
Because Argonaut is written with the compilation target of JavaScript in mind,
3941
it takes a slightly different approach, which is to reuse the existing data
4042
types which JavaScript already provides. This way, the result of JavaScript's
41-
`JSON.stringify` function is already a `Json` value, and no extra processing is
43+
`JSON.parse` function is already a `Json` value, and no extra processing is
4244
needed before you can start operating on it. This ought to help your program
4345
both in terms of speed and memory churn.
4446

4547
Much of the design of Argonaut follows naturally from this design decision.
4648

49+
### Types
50+
51+
The most important type in this library is, of course, `Json`, which is the
52+
type of JSON data in its native JavaScript representation.
53+
54+
As the (hypothetical) algebraic data type declaration above indicates, there
55+
are six possibilities for a JSON value: it can be `null`, a string, a number, a
56+
boolean, an array of JSON values, or an object mapping string keys to JSON
57+
values.
58+
59+
For convenience, and to ensure that values have the appropriate underlying
60+
data representations, Argonaut also declares types for each of these individual
61+
possibilities, whose names correspond to the data constructor names above.
62+
63+
Therefore, `JString`, `JNumber`, and `JBoolean` are synonyms for the primitive
64+
PureScript types `String`, `Number`, and `Boolean` respectively; `JArray` is a
65+
synonym for `Array Json`; and `JObject` is a synonym for `StrMap Json`.
66+
Argonaut defines a type `JNull` as the type of the `null` value in JavaScript.
67+
4768
### Introducing Json values
4869

4970
(Or, where do `Json` values come from?)
5071

51-
Usually, a `Json` value will be introduced into your program via either the FFI
52-
or via the construction functions in `Data.Argonaut.Core`. Here are some
53-
examples:
72+
If your program is receiving JSON data as a string, you probably want the
73+
`parseJson` function in `Data.Argonaut.Parser`, which is a very simple wrapper
74+
around JavaScript's `JSON.parse`.
75+
76+
Otherwise, `Json` values can be introduced into your program via the FFI or via
77+
the construction functions in `Data.Argonaut.Core`. Here are some examples:
5478

5579
```javascript
5680
// In an FFI module.
5781
exports.someNumber = 23.6;
5882
exports.someBoolean = false;
59-
exports.someObject = {people: [{name: "john"}, {name: "jane"}],
83+
exports.someObject = {people: [{name: "john"}, {name: "jane"}], common_interests: []};
6084
```
6185

6286
```purescript
@@ -65,10 +89,9 @@ foreign import someBoolean :: Json
6589
foreign import someObject :: Json
6690
```
6791

68-
Generally, if a JavaScript value could be returned from a call to
69-
`JSON.stringify`, it's fine to import it from the FFI as `Json`. So, for
70-
example, objects, booleans, numbers, strings, and arrays are all fine, but
71-
functions are not.
92+
Generally, if a JavaScript value could be returned from a call to `JSON.parse`,
93+
it's fine to import it from the FFI as `Json`. So, for example, objects,
94+
booleans, numbers, strings, and arrays are all fine, but functions are not.
7295

7396
The construction functions (that is, `fromX`, or `jsonX`) can be used as
7497
follows:
@@ -84,7 +107,8 @@ someObject = A.fromObject (StrMap.fromFoldable [
84107
Tuple "people" (A.fromArray [
85108
A.jsonSingletonObject "name" (A.fromString "john"),
86109
A.jsonSingletonObject "name" (A.fromString "jane")
87-
])
110+
]),
111+
Tuple "common_interests" A.jsonEmptyArray
88112
])
89113
```
90114

@@ -96,21 +120,44 @@ This function is necessary because `Json` is not an algebraic data type. If
96120
function, because we could perform pattern matching with a `case ... of`
97121
expression instead.
98122

123+
The type of `foldJson` is:
124+
125+
```purescript
126+
foldJson :: forall a.
127+
(JNull -> a) -> (JBoolean -> a) -> (JNumber -> a) ->
128+
(JString -> a) -> (JArray -> a) -> (JObject -> a) ->
129+
Json -> a
130+
```
131+
132+
That is, `foldJson` takes six functions, which all must return values of some
133+
particular type `a`, together with one `Json` value. `foldJson` itself also
134+
returns a value of the same type `a`.
135+
136+
A use of `foldJson` is very similar to a `case ... of` expression, as it allows
137+
you to handle each of the six possibilities for the `Json` value you passed in.
138+
Thinking of it this way, each of the six function arguments is like one of the
139+
case alternatives. Just like in a `case ... of` expression, the final value
140+
that the whole expression evaluates to comes from evaluating exactly one of the
141+
'alternatives' (functions) that you pass in. In fact, you can tell that this
142+
is the case just by looking at the type signature of `foldJson`, because of a
143+
property called *parametricity* (although a deeper explanation of parametricity
144+
is outside the scope of this tutorial).
145+
99146
For example, imagine we had the following values defined in JavaScript and
100147
imported via the FFI:
101148

102149
```javascript
103-
exports.someNumber = 0.0;
104-
exports.someArray = [0.0, {foo: 'bar'}, false];
105-
exports.someObject = {foo: 1, bar: [2,2]};
150+
exports.anotherNumber = 0.0;
151+
exports.anotherArray = [0.0, {foo: 'bar'}, false];
152+
exports.anotherObject = {foo: 1, bar: [2,2]};
106153
```
107154

108155
Then we can match on them in PureScript using `foldJson`:
109156

110157
```purescript
111-
foreign import someNumber :: Json
112-
foreign import someArray :: Json
113-
foreign import someObject :: Json
158+
foreign import anotherNumber :: Json
159+
foreign import anotherArray :: Json
160+
foreign import anotherObject :: Json
114161
115162
basicInfo :: Json -> String
116163
basicInfo = foldJson
@@ -127,29 +174,38 @@ basicInfo = foldJson
127174
```
128175

129176
```purescript
130-
basicInfo someNumber -- => "Got a number: 0.0"
131-
basicInfo someArray -- => "Got an array, which had 3 items."
132-
basicInfo someObject -- => "Got an object, which had 2 items."
177+
basicInfo anotherNumber -- => "Got a number: 0.0"
178+
basicInfo anotherArray -- => "Got an array, which had 3 items."
179+
basicInfo anotherObject -- => "Got an object, which had 2 items."
133180
```
134181

135-
All the other functions for matching on `Json` values can be expressed in terms
136-
of `foldJson`, but a few others are provided for convenience. For example, the
137-
`foldJsonX` functions can be used to match on a specific type. The first
138-
argument acts as a default value, to be used if the `Json` value turned out not
139-
to be that type. For example, we can write a function which tests whether a
140-
JSON value is the string "lol" like this:
182+
`foldJson` is the fundamental function for pattern matching on `Json` values;
183+
any kind of pattern matching you might want to do can be done with `foldJson`.
184+
185+
However, `foldJson` is not always comfortable to use, so Argonaut provides a
186+
few other simpler versions for convenience. For example, the `foldJsonX`
187+
functions can be used to match on a specific type. The first argument acts as a
188+
default value, to be used if the `Json` value turned out not to be that type.
189+
For example, we can write a function which tests whether a JSON value is the
190+
string "lol" like this:
141191

142192
```purescript
193+
foldJsonString :: forall a. a -> (JString -> a) -> Json -> a
194+
143195
isJsonLol = foldJsonString false (_ == "lol")
144196
```
145197

146198
If the `Json` value is not a string, the default `false` is used. Otherwise,
147199
we test whether the string is equal to "lol".
148200

149-
The `toX` functions also occupy a similar role. We could have written
150-
`isJsonLol` like this, too:
201+
The `toX` functions also occupy a similar role: they attempt to convert `Json`
202+
values into a specific type. If the json value you provide is of the right
203+
type, you'll get a `Just` value. Otherwise, you'll get `Nothing`. For example,
204+
we could have written `isJsonLol` like this, too:
151205

152206
```purescript
207+
toString :: Json -> Maybe JString
208+
153209
isJsonLol json =
154210
case toString json of
155211
Just str -> str == "lol"

0 commit comments

Comments
 (0)