zwilias/json-decode-exploration/4.2.0

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))
import Array
import Dict

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.

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.

""" [ 1, 2, 3 ] """
    |> decodeString (array int)
--> Success <| Array.fromList [ 1, 2, 3 ]
dict : Decoder v -> Decoder (Dict String v)

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

""" { "foo": "bar", "bar": "hi there" } """
    |> decodeString (dict string)
--> Success <| Dict.fromList
-->   [ ( "bar", "hi there" )
-->   , ( "foo", "bar" )
-->   ]
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
-->   ]

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 []))
-->     []
check : Decoder a -> a -> Decoder b -> Decoder b

Useful for checking a value in the JSON matches the value you expect it to have. If it does, succeeds with the second decoder. If it doesn't it fails.

This can be used to decode union types:

type Pet = Cat | Dog | Rabbit


petDecoder : Decoder Pet
petDecoder =
    oneOf
        [ check string "cat" <| succeed Cat
        , check string "dog" <| succeed Dog
        , check string "rabbit" <| succeed Rabbit
        ]


""" [ "dog", "rabbit", "cat" ] """
    |> decodeString (list petDecoder)
--> Success [ Dog, Rabbit, Cat ]
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.

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"

Last but not least, an extra import to allow elm-verify-examples to actually verify all the examples:

import DocVerificationHelpers exposing (Pet(..))