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