F# Data: JSON Parser
The F# JSON Type Provider is built on top of an efficient JSON parser written in F#. This parser is based on a JSON parser available in F# 3.0 Sample Pack, but F# Data adds a simple API that can be used to access values dynamically.
When working with well-defined JSON documents, it is easier to use the type provider, 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
(when using F# Interactive) or to add reference to a project.
FSharp.Data namespace contains the
JsonValue type that can be used
to parse strings formatted using JSON as follows:
1: 2: 3: 4:
The parsed value can be processed using pattern matching - the
is a discriminated union with cases such as
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
namespace. Once opened, we can write:
value.AsBoolean()returns the value as boolean if it is either
value.AsInteger()returns the value as integer if it is numeric and can be converted to an integer;
value.AsString()returns the value as a string.
value.AsDateTime()parses the string as a
DateTimevalue using either the ISO 8601 format, or using the
\/Date(...)\/JSON format containing number of milliseconds since 1/1/1970.
value.AsDateTimeOffset()parses the string as a
DateTimeOffsetvalue using either the ISO 8601 format, or using the
\/Date(...[+/-]offset)\/JSON format containing number of milliseconds since 1/1/1970, [+/-] the 4 digit offset. Example-
value.AsTimeSpan()parses the string as a
value.AsGuid()parses the string as a
value?childuses the dynamic operator to obtain a record member named
child; alternatively, you can also use
value.GetProperty(child)or an indexer
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,
[ for v in value -> v ]treats
valueas a collection and iterates over it; alternatively, it is possible to obtain all elements as an array using
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
AsDateTime) receive an optional culture parameter.
The following example shows how to process the sample JSON value:
1: 2: 3: 4: 5: 6: 7: 8: 9:
Note that the
JsonValue type does not actually implement the
interface (meaning that it cannot be passed to
Seq.xyz functions). It only has
GetEnumerator method, which makes it possible to use it in sequence expressions
and with the
Parsing WorldBank response
To look at a more complex example, consider a sample document
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). The document looks as follows:
1: 2: 3: 4: 5: 6: 7:
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:
Note that we can also load the data directly from the web, and there's an asynchronous version available too:
1: 2: 3: 4: 5: 6:
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
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
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
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
after parsing. Also note that the
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
JsonValue automatically parses
the string into the desired format. In general, the API attempts to be as tolerant
as possible when parsing the file.
- F# Data: JSON Type Provider - discusses a F# type provider that provides type-safe access to JSON data
- F# Data: WorldBank Provider - the WorldBank type provider can be used to easily access data from the WorldBank
- API Reference: JsonValue discriminated union
- API Reference: JsonExtensions module
type JsonValue =
| String of string
| Number of decimal
| Float of float
| Record of properties: (string * JsonValue) 
| Array of elements: JsonValue 
| Boolean of bool
member private PrepareRequest : httpMethod:string option * headers:#seq<string * string> option -> HttpRequestBody * (string * string) list * string
member Request : url:string * ?httpMethod:string * ?headers:seq<string * string> -> HttpResponse
member RequestAsync : url:string * ?httpMethod:string * ?headers:seq<string * string> -> Async<HttpResponse>
override ToString : unit -> string
member ToString : saveOptions:JsonSaveOptions -> string
member WriteTo : w:TextWriter * saveOptions:JsonSaveOptions -> unit
static member AsyncLoad : uri:string * ?encoding:Encoding -> Async<JsonValue>
static member private JsonStringEncodeTo : w:TextWriter -> value:string -> unit
static member Load : reader:TextReader -> JsonValue
static member Load : stream:Stream -> JsonValue
type JsonExtensions =
static member AsArray : x:JsonValue -> JsonValue 
static member AsBoolean : x:JsonValue -> bool
static member AsDateTime : x:JsonValue * ?cultureInfo:CultureInfo -> DateTime
static member AsDateTimeOffset : x:JsonValue * ?cultureInfo:CultureInfo -> DateTimeOffset
static member AsDecimal : x:JsonValue * ?cultureInfo:CultureInfo -> decimal
static member AsFloat : x:JsonValue * ?cultureInfo:CultureInfo * ?missingValues:string  -> float
static member AsGuid : x:JsonValue -> Guid
static member AsInteger : x:JsonValue * ?cultureInfo:CultureInfo -> int
static member AsInteger64 : x:JsonValue * ?cultureInfo:CultureInfo -> int64
static member AsString : x:JsonValue * ?cultureInfo:CultureInfo -> string
static member JsonValue.Load : stream:System.IO.Stream -> JsonValue
static member JsonValue.Load : uri:string * ?encoding:System.Text.Encoding -> JsonValue