forked from fsprojects/FSharp.Data
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathJsonValue.fsx
More file actions
192 lines (157 loc) · 7.75 KB
/
JsonValue.fsx
File metadata and controls
192 lines (157 loc) · 7.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
(**
---
category: Utilities
categoryindex: 1
index: 5
---
*)
(*** condition: prepare ***)
#r "../../src/FSharp.Data/bin/Release/netstandard2.0/FSharp.Data.dll"
(*** condition: fsx ***)
#if FSX
#r "nuget: FSharp.Data,{{fsdocs-package-version}}"
#endif // FSX
(*** condition: ipynb ***)
#if IPYNB
#r "nuget: FSharp.Data,{{fsdocs-package-version}}"
Formatter.SetPreferredMimeTypesFor(typeof<obj>, "text/plain")
Formatter.Register(fun (x:obj) (writer: TextWriter) -> fprintfn writer "%120A" x )
#endif // IPYNB
(**
[](https://mybinder.org/v2/gh/fsprojects/FSharp.Data/gh-pages?filepath={{fsdocs-source-basename}}.ipynb) 
[]({{root}}/{{fsdocs-source-basename}}.fsx) 
[]({{root}}/{{fsdocs-source-basename}}.ipynb)
# JSON Parser
The F# [JSON Type Provider](JsonProvider.html) is built on top of an efficient JSON parser written
in F#.
When working with well-defined JSON documents, it is easier to use the
[type provider](JsonProvider.html), but in a more dynamic scenario or when writing
quick and simple scripts, the parser might be a simpler option.
## Loading JSON documents
To load a sample JSON document, we first need to reference the `FSharp.Data` package.
*)
open FSharp.Data
(**
The `FSharp.Data` namespace contains the `cref:T:FSharp.Data.JsonValue` type that can be used
to parse strings formatted using JSON as follows:
*)
let info =
JsonValue.Parse("""
{ "name": "Tomas", "born": 1985,
"siblings": [ "Jan", "Alexander" ] } """)
(**
The parsed value can be processed using pattern matching - the `cref:T:FSharp.Data.JsonValue` type
is a discriminated union with cases such as `Record`, `Collection` and others that
can be used to examine the structure.
## Using JSON extensions
We do not cover this technique in this introduction. Instead, we look at a number
of extensions that become available after opening the `cref:T:FSharp.Data.JsonExtensionsModule`
module. Once opened, we can write:
* `value.AsBoolean()` returns the value as boolean if it is either `true` or `false`.
* `value.AsInteger()` returns the value as integer if it is numeric and can be
converted to an integer; `value.AsInteger64()`, `value.AsDecimal()` and
`value.AsFloat()` behave similarly.
* `value.AsString()` returns the value as a string.
* `value.AsDateTime()` parses the string as a `DateTime` value using either the
[ISO 8601](http://en.wikipedia.org/wiki/ISO_8601) format, or using the
`\/Date(...)\/` JSON format containing number of milliseconds since 1/1/1970.
* `value.AsDateTimeOffset()` parses the string as a `DateTimeOffset` value using either the
[ISO 8601](http://en.wikipedia.org/wiki/ISO_8601) format, or using the
`\/Date(...[+/-]offset)\/` JSON format containing number of milliseconds since 1/1/1970,
[+/-] the 4 digit offset. Example- `\/Date(1231456+1000)\/`.
* `value.AsTimeSpan()` parses the string as a `TimeSpan` value.
* `value.AsGuid()` parses the string as a `Guid` value.
* `value?child` uses the dynamic operator to obtain a record member named `child`;
alternatively, you can also use `value.GetProperty(child)` or an indexer
`value.[child]`.
* `value.TryGetProperty(child)` can be used to safely obtain a record member
(if the member is missing or the value is not a record then, `TryGetProperty`
returns `None`).
* `[ for v in value -> v ]` treats `value` as a collection and iterates over it;
alternatively, it is possible to obtain all elements as an array using
`value.AsArray()`.
* `value.Properties()` returns a list of all properties of a record node.
* `value.InnerText()` concatenates all text or text in an array
(representing e.g. multi-line string).
Methods that may need to parse a numeric value or date (such as `AsFloat` and
`AsDateTime`) receive an optional culture parameter.
The following example shows how to process the sample JSON value:
*)
open FSharp.Data.JsonExtensions
// Print name and birth year
let n = info?name
printfn "%s (%d)" (info?name.AsString()) (info?born.AsInteger())
// Print names of all siblings
for sib in info?siblings do
printfn "%s" (sib.AsString())
(**
Note that the `cref:T:FSharp.Data.JsonValue` type does not actually implement the `IEnumerable<'T>`
interface (meaning that it cannot be passed to `Seq.xyz` functions). It only has
the `GetEnumerator` method, which makes it possible to use it in sequence expressions
and with the `for` loop.
*)
(**
## Parsing WorldBank response
To look at a more complex example, consider a sample document
[`data/WorldBank.json`](../data/WorldBank.json) which was obtained as a response to
a WorldBank request (you can access the WorldBank data more conveniently using
[a type provider](WorldBank.html)). The document looks as follows:
[lang=js]
[ { "page": 1, "pages": 1, "total": 53 },
[ { "indicator": {"value": "Central government debt, total (% of GDP)"},
"country": {"id":"CZ","value":"Czech Republic"},
"value":null,"decimal":"1","date":"2000"},
{ "indicator": {"value": "Central government debt, total (% of GDP)"},
"country": {"id":"CZ","value":"Czech Republic"},
"value":"16.6567773464055","decimal":"1","date":"2010"} ] ]
The document is formed by an array that contains a record as the first element
and a collection of data points as the second element. The following code
reads the document and parses it:
*)
let value = JsonValue.Load(__SOURCE_DIRECTORY__ + "../../data/WorldBank.json")
(** Note that we can also load the data directly from the web, and there's an
asynchronous version available too: *)
let wbReq =
"http://api.worldbank.org/country/cz/indicator/" +
"GC.DOD.TOTL.GD.ZS?format=json"
let valueAsync =
JsonValue.AsyncLoad(wbReq)
(** To split the top-level array into the first record (with overall information)
and the collection of data points, we use pattern matching and match the `value`
against the `JsonValue.Array` constructor:
*)
match value with
| JsonValue.Array [| info; data |] ->
// Print overall information
let page, pages, total = info?page, info?pages, info?total
printfn
"Showing page %d of %d. Total records %d"
(page.AsInteger()) (pages.AsInteger()) (total.AsInteger())
// Print every non-null data point
for record in data do
if record?value <> JsonValue.Null then
printfn "%d: %f" (record?date.AsInteger())
(record?value.AsFloat())
| _ -> printfn "failed"
(**
The `value` property of a data point is not always available - as demonstrated
above, the value may be `null`. In that case, we want to skip the data point.
To check whether the property is `null` we simply compare it with `JsonValue.Null`.
The `date` values will be parsed as `DateTimeOffset` if there is an offset present.
However, for a mixed collection of `DateTime` (that is, without the offset) and
`DateTimeOffset` values, the type of the collection will be collection of `DateTime`
after parsing. Also note that the `date` and `value` properties are formatted as strings
in the source file (e.g. `"1990"`) instead of numbers (e.g. `1990`). When you try
accessing the value as an integer or float, the `cref:T:FSharp.Data.JsonValue` automatically parses
the string into the desired format. In general, the API attempts to be as tolerant
as possible when parsing the file.
## Related articles
* `cref:T:FSharp.Data.JsonValue`
* [JSON Type Provider](JsonProvider.html) - discusses a F# type provider
that provides type-safe access to JSON data
* [WorldBank Provider](WorldBank.html) - the WorldBank type provider
can be used to easily access data from the WorldBank
* API Reference: `cref:T:FSharp.Data.JsonValue`
* API Reference: `cref:T:FSharp.Data.JsonExtensions` type
* API Reference: `cref:T:FSharp.Data.JsonExtensionsModule` module
*)