module Json.Decode where
{-| A way to turn JSON strings and JS values into Elm values.
# Run a Decoder
@docs decodeString, decodeValue
# Primitives
@docs string, int, float, bool, null
# Arrays
@docs list, array,
tuple1, tuple2, tuple3, tuple4, tuple5, tuple6, tuple7, tuple8
# Objects
@docs (:=), at,
object1, object2, object3, object4, object5, object6, object7, object8,
keyValuePairs, dict
# Oddly Shaped Values
@docs maybe, oneOf, map, fail, succeed, andThen
# "Creative" Values
@docs value, customDecoder
-}
import Native.Json
import Array (Array)
import Dict
import Dict (Dict)
import Json.Encode as JsonEncode
import List
import Maybe (Maybe)
import Result (Result)
type Decoder a = Decoder
type alias Value = JsonEncode.Value
{-| Transform the value returned by a decoder. Most useful when paired with
the `oneOf` function.
nullOr : Decoder a -> Decoder (Maybe a)
nullOr decoder =
oneOf
[ null Nothing
, map Just decoder
]
type UserID = OldID Int | NewID String
-- 1234 or "1234abc"
userID : Decoder UserID
userID =
oneOf
[ map OldID int
, map NewID string
]
-}
map : (a -> b) -> Decoder a -> Decoder b
map =
Native.Json.decodeObject1
decodeString : Decoder a -> String -> Result String a
decodeString =
Native.Json.runDecoderString
-- OBJECTS
{-| Access a nested field, making it easy to dive into big structures. This is
really a helper function so you do not need to write `(:=)` so many times.
-- object.target.value = 'hello'
value : Decoder String
value =
at ["target", "value"] string
at fields decoder =
List.foldr (:=) decoder fields
-}
at : List String -> Decoder a -> Decoder a
at fields decoder =
List.foldr (:=) decoder fields
{-| Decode an object if it has a certain field.
nameAndAge : Decoder (String,Int)
nameAndAge =
object2 (,)
("name" := string)
("age" := int)
optionalProfession : Decoder (Maybe String)
optionalProfession =
maybe ("profession" := string)
-}
(:=) : String -> Decoder a -> Decoder a
(:=) =
Native.Json.decodeField
object1 : (a -> value) -> Decoder a -> Decoder value
object1 =
Native.Json.decodeObject1
{-| Use two different decoders on a JS value. This is nice for extracting
multiple fields from an object.
point : Decoder (Float,Float)
point =
object2 (,)
("x" := float)
("y" := float)
-}
object2 : (a -> b -> value) -> Decoder a -> Decoder b -> Decoder value
object2 =
Native.Json.decodeObject2
{-| Use two different decoders on a JS value. This is nice for extracting
multiple fields from an object.
type Task = { task : String, id : Int, completed : Bool }
point : Decoder (Float,Float)
point =
object3 Task
("task" := string)
("id" := int)
("completed" := bool)
-}
object3 : (a -> b -> c -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder value
object3 =
Native.Json.decodeObject3
object4 : (a -> b -> c -> d -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder value
object4 =
Native.Json.decodeObject4
object5 : (a -> b -> c -> d -> e -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder value
object5 =
Native.Json.decodeObject5
object6 : (a -> b -> c -> d -> e -> f -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder value
object6 =
Native.Json.decodeObject6
object7 : (a -> b -> c -> d -> e -> f -> g -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder g -> Decoder value
object7 =
Native.Json.decodeObject7
object8 : (a -> b -> c -> d -> e -> f -> g -> h -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder g -> Decoder h -> Decoder value
object8 =
Native.Json.decodeObject8
{-| Turn any object into a list of key-value pairs.
-- { tom: 89, sue: 92, bill: 97, ... }
grades : Decoder (List (String, Int))
grades =
keyValuePairs int
-}
keyValuePairs : Decoder a -> Decoder (List (String, a))
keyValuePairs =
Native.Json.decodeKeyValuePairs
{-| Turn any object into a dictionary of key-value pairs.
-- { mercury: 0.33, venus: 4.87, earth: 5.97, ... }
planetMasses : Decoder (Dict String Int)
planetMasses =
dict float
-}
dict : Decoder a -> Decoder (Dict String a)
dict decoder =
map Dict.fromList (keyValuePairs decoder)
{-| Try out a couple different decoders. This is helpful when you are dealing
with something with a very strange shape and when `andThen` does not help
narrow things down so you can be more targeted.
-- [ [3,4], { x:0, y:0 }, [5,12] ]
points : Decoder (List (Float,Float))
points =
list point
point : (Float,Float)
point =
oneOf
[ tuple2 (,) float float
, object2 (,) ("x" := float) ("y" := float)
]
-}
oneOf : List (Decoder a) -> Decoder a
oneOf =
Native.Json.oneOf
{-| Extract a string.
-- ["John","Doe"]
name : Decoder (String, String)
name =
tuple2 (,) string string
-}
string : Decoder String
string =
Native.Json.decodeString
{-| Extract a float.
-- [ 6.022, 3.1415, 1.618 ]
numbers : Decoder (List Float)
numbers =
list float
-}
float : Decoder Float
float =
Native.Json.decodeFloat
{-| Extract an integer.
-- { ... age: 42 ... }
age : Decoder Int
age =
"age" := int
-}
int : Decoder Int
int =
Native.Json.decodeInt
{-| Extract a boolean.
-- { ... checked: true ... }
checked : Decoder Bool
checked =
"checked" := true
-}
bool : Decoder Bool
bool =
Native.Json.decodeBool
{-| Extract a list from a JS array.
-- [1,2,3,4]
numbers : Decoder [Int]
numbers =
list int
-}
list : Decoder a -> Decoder (List a)
list =
Native.Json.decodeList
{-| Extract an Array from a JS array.
-- [1,2,3,4]
numbers : Decoder (Array Int)
numbers =
array int
-}
array : Decoder a -> Decoder (Array a)
array =
Native.Json.decodeArray
{-| Extract a null value. Primarily useful for creating *other* decoders.
nullOr : Decoder a -> Decoder (Maybe a)
nullOr decoder =
oneOf
[ null Nothing
, map Just decoder
]
numbers : Decoder [Int]
numbers =
list (oneOf [ int, null 0 ])
-}
null : a -> Decoder a
null =
Native.Json.decodeNull
{-| Great for handling optional fields. The following code decodes JSON
objects that may not have a profession field.
-- { name: "Tom", age: 31, profession: "plumber" }
-- { name: "Sue", age: 42 }
type alias Person =
{ name : String
, age : Int
, profession : Maybe String
}
person : Decoder Person
person =
object3 Person
("name" := string)
("age" := int)
(maybe ("profession" := string))
-}
maybe : Decoder a -> Decoder (Maybe a)
maybe =
Native.Json.decodeMaybe
{-| Bring in an arbitrary JSON value. Useful if you need to work with crazily
formatted data. For example, this lets you create a parser for "variadic" lists
where the first few types are different, followed by 0 or more of the same
type.
variadic2 : (a -> b -> List c -> value) -> Decoder a -> Decoder b -> Decoder (List c) -> Decoder value
variadic2 f a b cs =
customDecoder (list value) \jsonList ->
case jsonList of
one :: two :: rest ->
Result.map3 f
(decodeValue a one)
(decodeValue b two)
(decodeValue cs rest)
_ -> Result.Err "expecting at least two elements in the array"
-}
value : Decoder Value
value =
Native.Json.decodeValue
decodeValue : Decoder a -> Value -> Result String a
decodeValue =
Native.Json.runDecoderValue
customDecoder : Decoder a -> (a -> Result String b) -> Decoder b
customDecoder =
Native.Json.customDecoder
{-| Helpful when one field will determine the shape of a bunch of other fields.
type Shape
= Rectangle Float Float
| Circle Float
shape : Decoder Shape
shape =
("tag" := string) `andThen` shapeInfo
shapeInfo : String -> Decoder Shape
shapeInfo tag =
case tag of
"rectangle" ->
object2 Rectangle
("width" := float)
("height" := float)
"circle" ->
object1 Circle
("radius" := float)
_ ->
fail (tag ++ " is not a recognized tag for shapes")
-}
andThen : Decoder a -> (a -> Decoder b) -> Decoder b
andThen =
Native.Json.andThen
{-| A decoder that always fails. Useful when paired with `andThen` or `oneOf`
to improve error messages when things go wrong. For example, the following
decoder is able to provide a much more specific error message when `fail` is
the last option.
point : (Float,Float)
point =
oneOf
[ tuple2 (,) float float
, object2 (,) ("x" := float) ("y" := float)
, fail "expecting some kind of point"
]
-}
fail : String -> Decoder a
fail =
Native.Json.fail
{-| A decoder that always succeeds. Useful when paired with `andThen` or
`oneOf` but everything is supposed to work out at the end. For example,
maybe you have an optional field that can have a default value when it is
missing.
-- { x:3, y:4 } or { x:3, y:4, z:5 }
point3D : Decoder (Float,Float,Float)
point3D =
object (,,)
("x" := float)
("y" := float)
(oneOf [ "z" := float, succeed 0 ])
-}
succeed : a -> Decoder a
succeed =
Native.Json.succeed
-- TUPLES
tuple1 : (a -> value) -> Decoder a -> Decoder value
tuple1 =
Native.Json.decodeTuple1
{-| Handle an array with exactly two values. Useful for points and simple
pairs.
-- [3,4] or [0,0]
point : Decoder (Float,Float)
point =
tuple2 (,) float float
-- ["John","Doe"] or ["Hermann","Hesse"]
name : Decoder Name
name =
tuple2 Name string string
type alias Name = { first : String, last : String }
-}
tuple2 : (a -> b -> value) -> Decoder a -> Decoder b -> Decoder value
tuple2 =
Native.Json.decodeTuple2
{-| Handle an array with exactly three values.
-- [3,4,5] or [0,0,0]
point3D : Decoder (Float,Float,Float)
point3D =
tuple3 (,,) float float float
-}
tuple3 : (a -> b -> c -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder value
tuple3 =
Native.Json.decodeTuple3
tuple4 : (a -> b -> c -> d -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder value
tuple4 =
Native.Json.decodeTuple4
tuple5 : (a -> b -> c -> d -> e -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder value
tuple5 =
Native.Json.decodeTuple5
tuple6 : (a -> b -> c -> d -> e -> f -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder value
tuple6 =
Native.Json.decodeTuple6
tuple7 : (a -> b -> c -> d -> e -> f -> g -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder g -> Decoder value
tuple7 =
Native.Json.decodeTuple7
tuple8 : (a -> b -> c -> d -> e -> f -> g -> h -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder g -> Decoder h -> Decoder value
tuple8 =
Native.Json.decodeTuple8