Module

Tramaj.Json

Package
purescript-tramaj-purs
Repository
lucasdicioccio/purescript-tramaj-purs

JSON as Tramaj reads and writes it: like any JSON value, except that a number is an integer or a float, decided by its text (reference.md §3, node-json.md Numbers, decisions.md §18).

This module exists because the host's own JSON cannot carry that difference. JSON.parse reads 3 and 3.0 as the same number, and JSON.stringify writes both as 3, so Data.Argonaut.Core.Json, which is that host value, has nowhere to keep the type. Everything Tramaj takes in or hands out as JSON (the context, a Node's payloads, an expression program's result, the symbolic envelope) is therefore this Json, read by jsonParser and written by stringify.

The names follow Data.Argonaut.Core and Data.Argonaut.Parser where they mean the same thing, so that moving a caller over is mostly a change of import. fromArgonaut and toArgonaut bridge to a host value, and say what each direction costs.

Integer range. This implementation has the guaranteed range only, -(2^53 - 1) to 2^53 - 1 (reference.md §13): an integer is held in a double, which is exact there and nowhere beyond.

#Json Source

data Json

A JSON value whose numbers keep their type.

JInt and JFloat both hold a double. A JInt is a whole number, and a value Tramaj produced is always inside the integer range, finite and not a negative zero. jsonParser is more lenient on purpose, so that refusing a number is left to whoever decodes the value (see normalizeNumbers): it reads an integer-form number beyond the range as the JInt of the nearest double, and a float too large for a double as a JFloat infinity.

Constructors

Instances

  • Eq Json

    Structural, with object keys unordered. An integer and a float are never equal, whatever they hold: 3 and 3.0 are two values.

  • Show Json

#jsonNull Source

#fromBoolean Source

#fromInt Source

fromInt :: Int -> Json

An integer, from the host's 32-bit one.

#fromFloat Source

#fromString Source

#toNumber Source

toNumber :: Json -> Maybe Number

The value of a number of either type. Match on JInt/JFloat to tell them apart.

#jsonParser Source

jsonParser :: String -> Either String Json

Parses JSON text (RFC 8259), typing each number by its text: one with neither a fraction nor an exponent is an integer, one with either is a float, so 3 is an integer and 3.0 and 3e0 are floats.

No number is refused here: see Json for what a number beyond the value domain is read as, and normalizeNumbers for where it is refused. Of two equal keys in one object, the last wins.

#stringify Source

stringify :: Json -> String

Compact JSON, object keys in the order the object holds them, numbers by formatInteger and formatFloat.

#stringifyWithIndent Source

stringifyWithIndent :: Int -> Json -> String

As stringify, on several lines, each level indented by the given number of spaces. An empty array or object stays on one line.

#formatInteger Source

formatInteger :: Number -> String

An integer as its decimal digits. ECMAScript's Number::toString writes every whole number below 1e21 that way, which covers the integer range; show is that text with .0 appended.

#formatFloat Source

formatFloat :: Number -> String

A float as the shortest round-trip text of ECMAScript's Number::toString, with .0 appended when that text has neither a fraction nor an exponent: 1.0, 0.1, 100000000000.0, 1e+21. That is exactly what PureScript's show gives a Number.

An infinity has no JSON text. It is written as a float no double holds, which reads back as the same infinity.

#inIntegerRange Source

inIntegerRange :: Number -> Boolean

Whether a double is an integer this implementation holds exactly: a whole number in the guaranteed range, Number.isSafeInteger.

#normalizeNumbers Source

normalizeNumbers :: Json -> Either String Json

The numbers of a JSON value as Tramaj values, at any depth, or why one of them cannot be one. This is the one place that decides what a JSON number outside the value domain becomes (reference.md §3, node-json.md Decoding):

  • an integer-form number outside the integer range is refused, not rounded;
  • a float too large for a double is refused, not read as an infinity;
  • a negative zero is zero: -0.0 is 0.0 and -0 is 0.

#fromArgonaut Source

fromArgonaut :: Json -> Json

A host JSON value, as JSON.parse or a host program built it. A host number has no type of its own, so this is where this implementation classifies one: a whole number in the integer range is an integer, and any other number is a float. A float that happens to be whole (3.0) therefore arrives as the integer 3; a host that must pass it as a float builds a JFloat, or reads its JSON text with jsonParser.

#toArgonaut Source

toArgonaut :: Json -> Json

Down to a host JSON value, where the two number types become one again: JSON.stringify of the result writes the float 3.0 as 3. Use stringify to write JSON text that keeps the type.