zwilias/json-decode-exploration/4.1.1

NOTE: this package is not compatible with Elm 0.19.1

Json.Decode.Exploration

Like the regular decoders, except

Examples assume imports:

import Json.Encode as Encode
import List.Nonempty as Nonempty exposing (Nonempty(Nonempty))

Primitives

type Decoder a

Kind of the core idea of this library. Think of it as a piece of data that described how to read and transform JSON. You can use decodeString and decodeValue to actually execute a decoder on JSON.

string : Decoder String

Decode a string.

""" "hello world" """
    |> decodeString string
--> Success "hello world"




""" 123 """
    |> decodeString string
--> Errors (Nonempty (Failure "Expected a string" (Encode.int 123)) [])
bool : Decoder Bool

Decode a boolean value.

""" [ true, false ] """
    |> decodeString (list bool)
--> Success [ True, False ]
int : Decoder Int

Decode a number into an Int.

""" 123 """
    |> decodeString int
--> Success 123




""" 0.1 """
    |> decodeString int
--> Errors <|
-->   Nonempty
-->     (Failure "Expected an integer number" (Encode.float 0.1))
-->     []
float : Decoder Float

Decode a number into a Float.

""" 12.34 """
    |> decodeString float
--> Success 12.34




""" 12 """
    |> decodeString float
--> Success 12




""" null """
    |> decodeString float
--> Errors (Nonempty (Failure "Expected a number" Encode.null) [])

Data Structures

nullable : Decoder a -> Decoder (Maybe a)

Decodes successfully and wraps with a Just. If the values is null succeeds with Nothing.

""" [ { "foo": "bar" }, { "foo": null } ] """
    |> decodeString (list <| field "foo" <| nullable string)
--> Success [ Just "bar", Nothing ]
list : Decoder a -> Decoder (List a)

Decode a list of values, decoding each entry with the provided decoder.

""" [ "foo", "bar" ] """
    |> decodeString (list string)
--> Success [ "foo", "bar" ]




""" [ "foo", null ] """
    |> decodeString (list string)
--> Errors <|
-->   Nonempty
-->     (BadIndex 1 <|
-->       Nonempty (Failure "Expected a string" Encode.null) []
-->     )
-->     []
array : Decoder a -> Decoder (Array a)

Convenience function. Decode a JSON array into an Elm Array.

dict : Decoder v -> Decoder (Dict String v)

Convenience function. Decode a JSON object into an Elm Dict String.

keyValuePairs : Decoder a -> Decoder (List ( String, a ))

Decode a JSON object into a list of key-value pairs. The decoder you provide will be used to decode the values.

""" { "foo": "bar", "hello": "world" } """
    |> decodeString (keyValuePairs string)
--> Success [ ( "foo", "bar" ), ( "hello", "world" ) ]

Object Primitives

field : String -> Decoder a -> Decoder a

Decode the content of a field using a provided decoder.

""" { "foo": "bar" } """
    |> decodeString (field "foo" string)
--> Success "bar"




""" [ { "foo": "bar" }, { "foo": "baz", "hello": "world" } ] """
    |> decodeString (list (field "foo" string))
--> WithWarnings expectedWarnings [ "bar", "baz" ]




expectedWarnings : Warnings
expectedWarnings =
    UnusedValue (Encode.string "world")
        |> Nonempty.fromElement
        |> InField "hello"
        |> Nonempty.fromElement
        |> AtIndex 1
        |> Nonempty.fromElement
at : List String -> Decoder a -> Decoder a

Decodes a value at a certain path, using a provided decoder. Essentially, writing at [ "a", "b", "c" ] string is sugar over writing field "a" (field "b" (field "c" string))}.

""" { "a": { "b": { "c": "hi there" } } } """
    |> decodeString (at [ "a", "b", "c" ] string)
--> Success "hi there"
index : Int -> Decoder a -> Decoder a

Decode a specific index using a specified Decoder.

""" [ "hello", 123 ] """
    |> decodeString (map2 (,) (index 0 string) (index 1 int))
--> Success ( "hello", 123 )




""" [ "hello", "there" ] """
    |> decodeString (index 1 string)
--> WithWarnings
-->   (Nonempty (AtIndex 0 <| Nonempty (UnusedValue (Encode.string "hello")) []) [])
-->   "there"

Inconsistent Structure

maybe : Decoder a -> Decoder (Maybe a)

Decodes successfully and wraps with a Just, handling failure by succeeding with Nothing.

""" [ "foo", 12 ] """
    |> decodeString (list <| maybe string)
--> WithWarnings expectedWarnings [ Just "foo", Nothing ]




expectedWarnings : Warnings
expectedWarnings =
    UnusedValue (Encode.int 12)
        |> Nonempty.fromElement
        |> AtIndex 1
        |> Nonempty.fromElement
oneOf : List (Decoder a) -> Decoder a

Tries a bunch of decoders. The first one to not fail will be the one used.

If all fail, the errors are collected into a BadOneOf.

""" [ 12, "whatever" ] """
    |> decodeString (list <| oneOf [ map toString int, string ])
--> Success [ "12", "whatever" ]




""" null """
    |> decodeString (oneOf [ string, map toString int ])
--> Errors <| Nonempty.fromElement <| BadOneOf
-->   [ Nonempty.fromElement <| Failure "Expected a string" Encode.null
-->   , Nonempty.fromElement <| Failure "Expected an integer number" Encode.null
-->   ]

Run Decoders

decodeString : Decoder a -> String -> DecodeResult a

Decode a JSON string. If the string isn't valid JSON, this will fail with a BadJson result.

decodeValue : Decoder a -> Value -> DecodeResult a

Run a Decoder on a Value.

Note that this may still fail with a BadJson if there are non-JSON compatible values in the provided Value. In particular, don't attempt to use this library when decoding Events - it will blow up. Badly.

= BadJson
| Errors Errors
| WithWarnings Warnings a
| Success a

Decoding can have 4 different outcomes:

  • BadJson occurs when the JSON string isn't valid JSON, or the Value contains non-JSON primitives like functions.
  • Errors means errors occurred while running your decoder and contains the Errors that occurred.
  • WithWarnings means decoding succeeded but produced one or more Warnings.
  • Success is the best possible outcome: All went well!
type alias Value =
Value

A simple type alias for Json.Decode.Value.

type alias Errors =
Nonempty Error

Decoding may fail with 1 or more errors, so Errors is a Nonempty of errors.

type Error
= BadField String Errors
| BadIndex Int Errors
| BadOneOf (List Errors)
| Failure String Value

The most basic kind of an Error is Failure, which comes annotated with a string describing the failure, and the JSON Value that was encountered instead.

The other cases describe the "path" to where the error occurred.

type alias Warnings =
Nonempty Warning

Decoding may generate warnings. In case the result is a WithWarnings, you will have 1 or more warnings, as a Nonempty list.

type Warning
= InField String Warnings
| AtIndex Int Warnings
| UnusedValue Value

Like with errors, the most basic warning is an unused value. The other cases describe the path to the warnings.

Mapping

Note: If you run out of map functions, take a look at the pipeline module which makes it easier to handle large objects.

map : (a -> b) -> Decoder a -> Decoder b

Useful for transforming decoders.

""" "foo" """
    |> decodeString (map String.toUpper string)
--> Success "FOO"
map2 : (a -> b -> c) -> Decoder a -> Decoder b -> Decoder c

Combine 2 decoders.

map3 : (a -> b -> c -> d) -> Decoder a -> Decoder b -> Decoder c -> Decoder d

Combine 3 decoders.

(a -> b -> c -> d -> e)
-> Decoder a
-> Decoder b
-> Decoder c
-> Decoder d
-> Decoder e

Combine 4 decoders.

(a -> b -> c -> d -> e -> f)
-> Decoder a
-> Decoder b
-> Decoder c
-> Decoder d
-> Decoder e
-> Decoder f

Combine 5 decoders.

(a -> b -> c -> d -> e -> f -> g)
-> Decoder a
-> Decoder b
-> Decoder c
-> Decoder d
-> Decoder e
-> Decoder f
-> Decoder g

Combine 6 decoders.

(a -> b -> c -> d -> e -> f -> g -> h)
-> Decoder a
-> Decoder b
-> Decoder c
-> Decoder d
-> Decoder e
-> Decoder f
-> Decoder g
-> Decoder h

Combine 7 decoders.

(a -> b -> c -> d -> e -> f -> g -> h -> i)
-> Decoder a
-> Decoder b
-> Decoder c
-> Decoder d
-> Decoder e
-> Decoder f
-> Decoder g
-> Decoder h
-> Decoder i

Combine 8 decoders.

andMap : Decoder a -> Decoder (a -> b) -> Decoder b

Decode an argument and provide it to a function in a decoder.

decoder : Decoder String
decoder =
    succeed (String.repeat)
        |> andMap (field "count" int)
        |> andMap (field "val" string)




""" { "val": "hi", "count": 3 } """
    |> decodeString decoder
--> Success "hihihi"

Fancy Decoding

lazy : (() -> Decoder a) -> Decoder a

Required when using (mutually) recursive decoders.

value : Decoder Value

Extract a piece without actually decoding it.

If a structure is decoded as a value, everything in the structure will be considered as having been used and will not appear in UnusedValue warnings.

""" [ 123, "world" ] """
    |> decodeString value
--> Success (Encode.list [ Encode.int 123, Encode.string "world" ])
null : a -> Decoder a

Decode a null and succeed with some value.

""" null """
    |> decodeString (null "it was null")
--> Success "it was null"

Note that undefined and null are not the same thing. This cannot be used to verify that a field is missing, only that it is explicitly set to null.

""" { "foo": null } """
    |> decodeString (field "foo" (null ()))
--> Success ()




""" { } """
    |> decodeString (field "foo" (null ()))
--> Errors <|
-->   Nonempty
-->     (Failure "Expected an object with a field 'foo'" (Encode.object []))
-->     []
succeed : a -> Decoder a

A decoder that will ignore the actual JSON and succeed with the provided value. Note that this may still fail when dealing with an invalid JSON string.

If a value in the JSON ends up being ignored because of this, this will cause a warning.

""" null """
    |> decodeString (value |> andThen (\_ -> succeed "hello world"))
--> Success "hello world"




""" null """
    |> decodeString (succeed "hello world")
--> WithWarnings
-->     (Nonempty (UnusedValue Encode.null) [])
-->     "hello world"




""" foo """
    |> decodeString (succeed "hello world")
--> BadJson
fail : String -> Decoder a

Ignore the json and fail with a provided message.

""" "hello" """
    |> decodeString (fail "failure")
--> Errors (Nonempty (Failure "failure" (Encode.string "hello")) [])
andThen : (a -> Decoder b) -> Decoder a -> Decoder b

Chain decoders where one decoder depends on the value of another decoder.