purescript-contrib/purescript-argonaut-core

Name: purescript-argonaut-core

Owner: PureScript Contrib

Description: A fast, native representation for JSON, with serialization and folding

Created: 2015-07-10 18:06:56.0

Updated: 2018-03-07 10:12:09.0

Pushed: 2018-05-24 17:15:57.0

Homepage:

Size: 44

Language: PureScript

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

purescript-argonaut-core

Latest release Build Status Maintainer: slamdata

Core part of purescript-argonaut that contains basic types for Json, case analysis, printer and parser.

Installation
r install purescript-argonaut-core
Documentation

Module documentation is published on Pursuit.

Tutorial

Some of Argonaut's functions might seem a bit arcane at first, so it can help to understand the underlying design decisions which make it the way it is.

One approach for modelling JSON values would be to define an algebraic data type, like this:

 Json
JNull
JString String
JNumber Number
JBoolean Boolean
JArray (Array Json)
JObject (Object Json)

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

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

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

Introducing Json values

(Or, where do Json values come from?)

If your program is receiving JSON data as a string, you probably want the jsonParser function in Data.Argonaut.Parser, which is a very simple wrapper around JavaScript's JSON.parse.

Otherwise, Json values can be introduced into your program via the FFI or via the construction functions in Data.Argonaut.Core. Here are some examples:

n an FFI module.
rts.someNumber = 23.6;
rts.someBoolean = false;
rts.someObject = {people: [{name: "john"}, {name: "jane"}], common_interests: []};
urescript
ign import someNumber :: Json
ign import someBoolean :: Json
ign import someObject :: Json

Generally, if a JavaScript value could be returned from a call to JSON.parse, it's fine to import it from the FFI as Json. So, for example, objects, booleans, numbers, strings, and arrays are all fine, but functions are not.

The construction functions (that is, fromX, or jsonX) can be used as follows:

rt Data.Tuple (Tuple(..))
rt Foreign.Object as StrMap
rt Data.Argonaut.Core as A

Number = A.fromNumber 23.6
Boolean = A.fromBoolean false
Object = A.fromObject (StrMap.fromFoldable [
            Tuple "people" (A.fromArray [
              A.jsonSingletonObject "name" (A.fromString "john"),
              A.jsonSingletonObject "name" (A.fromString "jane")
            ]),
            Tuple "common_interests" A.jsonEmptyArray
          ])
Eliminating/matching on Json values

We can perform case analysis for Json values using the caseJson function. This function is necessary because Json is not an algebraic data type. If Json were an algebraic data type, we would not have as much need for this function, because we could perform pattern matching with a case ... of expression instead.

The type of caseJson is:

Json
 forall a
 (Unit -> a)
 (Boolean -> a)
 (Number -> a)
 (String -> a)
 (Array Json -> a)
 (Object Json -> a)
 Json
 a

That is, caseJson takes six functions, which all must return values of some particular type a, together with one Json value. caseJson itself also returns a value of the same type a.

A use of caseJson is very similar to a case ... of expression, as it allows you to handle each of the six possibilities for the Json value you passed in. Thinking of it this way, each of the six function arguments is like one of the case alternatives.

The function that takes Unit as an argument is for matching null values. As there is only one possible null value, we use the PureScript Unit type, as correspondingly there is only one possible Unit value.

Just like in a case ... of expression, the final value that the whole expression evaluates to comes from evaluating exactly one of the 'alternatives' (functions) that you pass in. In fact, you can tell that this is the case just by looking at the type signature of caseJson, because of a property called parametricity (although a deeper explanation of parametricity is outside the scope of this tutorial).

For example, imagine we had the following values defined in JavaScript and imported via the FFI:

rts.anotherNumber = 0.0;
rts.anotherArray = [0.0, {foo: 'bar'}, false];
rts.anotherObject = {foo: 1, bar: [2,2]};

Then we can match on them in PureScript using caseJson:

ign import anotherNumber :: Json
ign import anotherArray :: Json
ign import anotherObject :: Json

cInfo :: Json -> String
cInfo = caseJson
onst "It was null")
b -> "Got a boolean: " <>
        if b then "it was true!" else "It was false.")
x -> "Got a number: " <> show x)
s -> "Got a string, which was " <> Data.String.length s <>
       " characters long.")
xs -> "Got an array, which had " <> Data.Array.length xs <>
       " items.")
obj -> "Got an object, which had " <> Foreign.Object.size obj <>
       " items.")
urescript
cInfo anotherNumber -- => "Got a number: 0.0"
cInfo anotherArray  -- => "Got an array, which had 3 items."
cInfo anotherObject -- => "Got an object, which had 2 items."

caseJson is the fundamental function for pattern matching on Json values; any kind of pattern matching you might want to do can be done with caseJson.

However, caseJson is not always comfortable to use, so Argonaut provides a few other simpler versions for convenience. For example, the caseJsonX functions can be used to match on a specific type. The first argument acts as a default value, to be used if the Json value turned out not to be that type. For example, we can write a function which tests whether a JSON value is the string “lol” like this:

JsonString :: forall a. a -> (String -> a) -> Json -> a

onLol = caseJsonString false (_ == "lol")

If the Json value is not a string, the default false is used. Otherwise, we test whether the string is equal to “lol”.

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

ring :: Json -> Maybe String

onLol json =
se toString json of
Just str -> str == "lol"
Nothing  -> false

This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.