APIJson.Pull

Json.Pull

The JSON pull parser and the decoding protocol behind Json.Decode.

Everyday code imports Json. This module serves custom Decode instances and code that consumes a document token by token: the Pull effect and its handlers, container and path helpers, and the buffered Scan protocol that derived decoders run on.

Parsing rejects invalid UTF-8, malformed escapes, trailing input, and nesting deeper than 256 arrays or objects. An error is reported at the start of the offending token, whatever the input’s buffering; a missing field after its object, and a duplicate one at its value. Malformed or incomplete encoded input is reported at the first offending sequence, which is left unconsumed.

Error

type Error =    { message : String    , offset : Int    , line : Int    , column : Int    , path : String    }Source

A JSON failure and where it happened.

Fields

message

message : StringSource

What went wrong.

offset

offset : IntSource

The zero-based byte offset; -1 for a failure while emitting.

line

line : IntSource

The one-based line.

column

column : IntSource

The one-based column, counted in bytes.

path

path : StringSource

Where in the document, as slash-separated keys and indices, for a failure inside a decoded record, list, or value. An error found while scanning lookahead can come before its field or element is entered; typed arrays enter the index before scanning the element.

Instances:

Eq Error
Show Error

Number

type NumberSource

A JSON number kept as its lexeme, so it round-trips without conversion. Json.number makes one and numberText reads it.

Scan

type ScanSource

A pure, bounded view of one immutable text window. A completed value keeps its container allowance unchanged. Negative allowance marks lookahead that must be consumed through Pull before scanning can continue.

ScanStep

type ScanStep a    = ScanDone a Scan    | ScanContinue (Result (() ->{Pull} ScanStep a) (a, Scan))Source

Suspensions return a step, so a long sequence of refills or unknown keys runs through runScan’s tail loop instead of nesting continuation calls. What a suspended scan answers when run.

Constructors

ScanDone

ScanDone : a -> Scan -> ScanStep aSource

The value and the position after it.

ScanContinue

ScanContinue : Result (() ->{Pull} ScanStep a) (a, Scan) -> ScanStep aSource

More work, run in a loop with bounded stack.

Token

type Token    = BeginObject    | EndObject    | BeginArray    | EndArray    | Colon    | Comma    | StringToken String    | NumberToken String    | BooleanToken Bool    | NullToken    | EndInputSource

One lexical JSON token.

Constructors

BeginObject

BeginObject : TokenSource

{

BeginArray

BeginArray : TokenSource

[

EndObject

EndObject : TokenSource

}

EndArray

EndArray : TokenSource

]

Colon

Colon : TokenSource

:

Comma

Comma : TokenSource

,

StringToken

StringToken : String -> TokenSource

A string, unescaped.

NumberToken

NumberToken : String -> TokenSource

A number, as written.

BooleanToken

BooleanToken : Bool -> TokenSource

true or false.

EndInput

EndInput : TokenSource

The end of the input.

NullToken

NullToken : TokenSource

null.

Instances:

Eq Token
Show Token

Value

type Value    = Null    | Boolean Bool    | Numeric Number    | Text String    | Array (List Value)    | Object (List (String, Value))Source

A generic JSON document tree. Object members keep their order.

Constructors

Array

Array : List Value -> ValueSource

An array.

Boolean

Boolean : Bool -> ValueSource

true or false.

Null

Null : ValueSource

null.

Numeric

Numeric : Number -> ValueSource

A number.

Object

Object : List (String, Value) -> ValueSource

An object’s members, in order.

Text

Text : String -> ValueSource

A string.

Instances:

Decode Value
Encode Value

Decode

class Decode aSource

Types that decode from JSON. Derive it, or write scanValue; a decoder written with Pull operations is scanValue cursor = Json.Pull.scanFromPull cursor { ... }.

Methods

scanValue

scanValue : Decode a => Scan -> Result (() ->{Pull} ScanStep a) (a, Scan)Source

Starts a pure buffered scan of one value at cursor. Ok is the value and the position after it; Err suspends work that runScan will run, and is not a JSON error. Creating either consumes nothing, refills nothing, and performs no consumer effects; Ok keeps the nesting allowance decodeValue would.

Instances:

Decode ()
Decode Bool
Decode Char
Decode Float
Decode Int
Decode String
Decode Value
Decode a => Decode (List a)
Decode a => Decode (Maybe a)

Pull

effect PullSource

Reading a document token by token. withReader and withTextReader handle it.

Operations

next

next : () ->{Pull} TokenSource

Consumes and answers the next token, or the pending one from peek.

peek

peek : () ->{Pull} TokenSource

The next token, without consuming it.

at

at : () ->{Pull} ErrorSource

The current source position, as an Error with an empty message. It publishes buffered consumption.

nextElement

nextElement : Bool ->{Pull} BoolSource

Consumes an array’s separator or closing bracket and answers whether another element follows, leaving that element unscanned. first says whether no element has been read yet. It honors a token already obtained by peek.

nextKey

nextKey : Bool ->{Pull} Maybe StringSource

Consumes an object’s separator, key, and colon and answers the key, or consumes the closing brace and answers Nothing. first says whether no member has been read yet. It honors a token already obtained by peek.

decodedString

decodedString : () ->{Pull} Result String StringSource

Consumes a scalar and answers it as a string, or a message after consuming a token of another type.

decodedInt

decodedInt : () ->{Pull} Result String IntSource

Consumes a scalar and answers it as an integer, or a message after consuming a token of another type or a number out of range.

decodedFloat

decodedFloat : () ->{Pull} Result String FloatSource

Consumes a scalar and answers it as a finite float, or a message after consuming a token of another type or a number out of range.

bufferedScan

bufferedScan : () ->{Pull} Maybe ScanSource

A speculative scan at the current position, or Nothing while a peek token is pending. It consumes nothing, and abandoning it unrun consumes nothing either.

currentScan

currentScan : () ->{Pull} ScanSource

A scan at the current position that also carries pending lookahead. It consumes nothing.

acceptScan

acceptScan : Scan ->{Pull} ()Source

Publishes the position a completed scan reached, consuming up to it. Call it right away, before other parser operations. A suspension may already have published a prefix; publishing the final position settles the rest. The reader is committed at the next refill, at, or scope exit.

beginArray

beginArray : () ->{Pull} ()Source

Consumes an array’s opening bracket, failing if the next token is another.

beginObject

beginObject : () ->{Pull} ()Source

Consumes an object’s opening brace, failing if the next token is another.

decodeValue

decodeValue : Decode a => () ->{Pull} aSource

Decodes the next value as a: starts scanValue at the cursor, runs any suspended work, and publishes the final position. Calling it for a child runs that child’s one parser.

fail

fail : String ->{Pull} aSource

Fails with message at the current path, positioned at the start of the token most recently returned by next or peek or consumed by a decoded operation, or at the current position once the cursor moved on another way.

import Bytes
import Json.Pull
import Reader
import Result exposing (Result(..))

failure : Result Json.Pull.Error Int
failure =
    Reader.withBytes (Bytes.fromString "[true]") { reader ->
        Json.Pull.withReader reader {
            Json.Pull.beginArray ()
            ignore (Json.Pull.peek ())
            Json.Pull.fail "no booleans"
        }
    }

Result.mapError { e -> (e.message, e.offset) } failure == Err ("no booleans", 1)

mapScan

mapScan : (a -> b) -> Result (() ->{Pull} ScanStep a) (a, Scan) -> Result (() ->{Pull} ScanStep b) (b, Scan)Source

Transforms a scan’s value, buffered or resumed alike.

numberText

numberText : Number -> StringSource

The number’s lexeme as written.

readValue

readValue : () ->{Pull} ValueSource

Consumes the next value and builds its generic tree.

runScan

runScan : Result (() ->{Pull} ScanStep a) (a, Scan) ->{Pull} (a, Scan)Source

Runs suspended scan work until it answers a value and position, or fails with a JSON or source failure.

scanFromPull

scanFromPull : Scan -> (() ->{Pull} a) -> Result (() ->{Pull} ScanStep a) (a, Scan)Source

A deferred action runs only after the pure scan reaches a boundary or a token/custom decoder. It publishes that prefix and returns its new window.

skipValue

skipValue : () ->{Pull} ()Source

Consumes and validates the next value without building it.

import Bytes
import Json.Pull
import Reader
import Result exposing (Result(..))

rest =
    Reader.withBytes (Bytes.fromString "[{\"a\": [1, 2]}, 3]") { reader ->
        Json.Pull.withReader reader {
            Json.Pull.beginArray ()
            Json.Pull.skipValue ()
            ignore (Json.Pull.nextElement False)
            Json.Pull.next ()
        }
    }

rest == Ok (Json.Pull.NumberToken "3")

withIndex

withIndex : Int -> (() ->{Pull} a) ->{Pull} aSource

Runs action with element index added to the path of errors raised inside it.

withKey

withKey : String -> (() ->{Pull} a) ->{Pull} aSource

Runs action with key added to the path of errors raised inside it.

withReader

withReader : Reader e -> (() ->{Pull} a) ->{e} Result Error aSource

Runs action with the Pull effect over a byte reader, decoding UTF-8. On return, normally or with Err, the reader is positioned after the last token scanned, peek lookahead included, as it is when an unrelated effect leaves the scope; inside, it may lag behind, so advance it only after the scope.

import Bytes
import Json.Pull
import Reader
import Result exposing (Result(..))

tokens = Reader.withBytes (Bytes.fromString "[1]") { reader -> Json.Pull.withReader reader { [Json.Pull.next (), Json.Pull.peek (), Json.Pull.next (), Json.Pull.next ()] } }

tokens == Ok [Json.Pull.BeginArray, Json.Pull.NumberToken "1", Json.Pull.NumberToken "1", Json.Pull.EndArray]

withTextReader

withTextReader : Reader e -> (() ->{Pull} a) ->{e} Result Error aSource

withReader over a text reader, whose encoding the caller chose.