@@ -16,3 +16,198 @@ bower install purescript-argonaut-core
16
16
## Documentation
17
17
18
18
Module documentation is [ published on Pursuit] ( http://pursuit.purescript.org/packages/purescript-argonaut-core ) .
19
+
20
+ ## Tutorial
21
+
22
+ Some of Argonaut's functions might seem a bit arcane at first, so it can help
23
+ to understand the underlying design decisions which make it the way it is.
24
+
25
+ One approach for modelling JSON values would be to define an algebraic data
26
+ type, like this:
27
+
28
+ ``` purescript
29
+ data Json
30
+ = JNull
31
+ | JString String
32
+ | JNumber Number
33
+ | JBoolean Boolean
34
+ | JArray (Array Json)
35
+ | JObject (StrMap Json)
36
+ ```
37
+
38
+ And indeed, some might even say this is the obvious approach.
39
+
40
+ Because Argonaut is written with the compilation target of JavaScript in mind,
41
+ it takes a slightly different approach, which is to reuse the existing data
42
+ types which JavaScript already provides. This way, the result of JavaScript's
43
+ ` JSON.parse ` function is already a ` Json ` value, and no extra processing is
44
+ needed before you can start operating on it. This ought to help your program
45
+ both in terms of speed and memory churn.
46
+
47
+ Much of the design of Argonaut follows naturally from this design decision.
48
+
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
+
68
+ ### Introducing Json values
69
+
70
+ (Or, where do ` Json ` values come from?)
71
+
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:
78
+
79
+ ``` javascript
80
+ // In an FFI module.
81
+ exports .someNumber = 23.6 ;
82
+ exports .someBoolean = false ;
83
+ exports .someObject = {people: [{name: " john" }, {name: " jane" }], common_interests: []};
84
+ ```
85
+
86
+ ``` purescript
87
+ foreign import someNumber :: Json
88
+ foreign import someBoolean :: Json
89
+ foreign import someObject :: Json
90
+ ```
91
+
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.
95
+
96
+ The construction functions (that is, ` fromX ` , or ` jsonX ` ) can be used as
97
+ follows:
98
+
99
+ ``` purescript
100
+ import Data.Tuple (Tuple(..))
101
+ import Data.StrMap as StrMap
102
+ import Data.Argonaut.Core as A
103
+
104
+ someNumber = A.fromNumber 23.6
105
+ someBoolean = A.fromBoolean false
106
+ someObject = A.fromObject (StrMap.fromFoldable [
107
+ Tuple "people" (A.fromArray [
108
+ A.jsonSingletonObject "name" (A.fromString "john"),
109
+ A.jsonSingletonObject "name" (A.fromString "jane")
110
+ ]),
111
+ Tuple "common_interests" A.jsonEmptyArray
112
+ ])
113
+ ```
114
+
115
+ ### Eliminating/matching on ` Json ` values
116
+
117
+ We can perform case analysis for ` Json ` values using the ` foldJson ` function.
118
+ This function is necessary because ` Json ` is not an algebraic data type. If
119
+ ` Json ` were an algebraic data type, we would not have as much need for this
120
+ function, because we could perform pattern matching with a ` case ... of `
121
+ expression instead.
122
+
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
+
146
+ For example, imagine we had the following values defined in JavaScript and
147
+ imported via the FFI:
148
+
149
+ ``` javascript
150
+ exports .anotherNumber = 0.0 ;
151
+ exports .anotherArray = [0.0 , {foo: ' bar' }, false ];
152
+ exports .anotherObject = {foo: 1 , bar: [2 ,2 ]};
153
+ ```
154
+
155
+ Then we can match on them in PureScript using ` foldJson ` :
156
+
157
+ ``` purescript
158
+ foreign import anotherNumber :: Json
159
+ foreign import anotherArray :: Json
160
+ foreign import anotherObject :: Json
161
+
162
+ basicInfo :: Json -> String
163
+ basicInfo = foldJson
164
+ (const "It was null")
165
+ (\b -> "Got a boolean: " <>
166
+ if b then "it was true!" else "It was false.")
167
+ (\x -> "Got a number: " <> show x)
168
+ (\s -> "Got a string, which was " <> Data.String.length s <>
169
+ " characters long.")
170
+ (\xs -> "Got an array, which had " <> Data.Array.length xs <>
171
+ " items.")
172
+ (\obj -> "Got an object, which had " <> Data.StrMap.size obj <>
173
+ " items.")
174
+ ```
175
+
176
+ ``` purescript
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."
180
+ ```
181
+
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:
191
+
192
+ ``` purescript
193
+ foldJsonString :: forall a. a -> (JString -> a) -> Json -> a
194
+
195
+ isJsonLol = foldJsonString false (_ == "lol")
196
+ ```
197
+
198
+ If the ` Json ` value is not a string, the default ` false ` is used. Otherwise,
199
+ we test whether the string is equal to "lol".
200
+
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:
205
+
206
+ ``` purescript
207
+ toString :: Json -> Maybe JString
208
+
209
+ isJsonLol json =
210
+ case toString json of
211
+ Just str -> str == "lol"
212
+ Nothing -> false
213
+ ```
0 commit comments