├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── elm-package.json └── src └── Json └── Decode └── Extra.elm /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore build or dist files 2 | /elm-stuff 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #### 1.0.0 2 | - Upgrade for Elm 0.17 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 CircuitHub Inc., Elm Community members 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > **DEPRECATED: Renamed to [elm-community/json-extra](http://package.elm-lang.org/packages/elm-community/json-extra/latest)** 2 | 3 | # Convenience functions for working with Json 4 | Experimental package with convenience functions for working with Json. 5 | Note that this API is experimental and likely to go through many more iterations. 6 | 7 | Feedback and contributions are very welcome. 8 | -------------------------------------------------------------------------------- /elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.2", 3 | "summary": "DEPRECATED: Renamed to http://package.elm-lang.org/elm-community/json-extra", 4 | "repository": "https://github.com/elm-community/elm-json-extra.git", 5 | "license": "MIT", 6 | "source-directories": [ 7 | "src" 8 | ], 9 | "exposed-modules": [ 10 | "Json.Decode.Extra" 11 | ], 12 | "dependencies": { 13 | "elm-lang/core": "4.0.0 <= v < 5.0.0" 14 | }, 15 | "elm-version": "0.17.0 <= v < 0.18.0" 16 | } -------------------------------------------------------------------------------- /src/Json/Decode/Extra.elm: -------------------------------------------------------------------------------- 1 | module Json.Decode.Extra exposing (date, apply, (|:), set, dict2, withDefault, maybeNull, lazy) 2 | 3 | {-| Convenience functions for working with Json 4 | 5 | # Date 6 | @docs date 7 | 8 | # Incremental Decoding 9 | @docs apply, (|:) 10 | 11 | # Set 12 | @docs set 13 | 14 | # Dict 15 | @docs dict2 16 | 17 | # Maybe 18 | @docs withDefault, maybeNull 19 | 20 | # Recursively Defined Decoders 21 | @docs lazy 22 | 23 | -} 24 | 25 | import Json.Decode exposing (..) 26 | import Date 27 | import Dict exposing (Dict) 28 | import Set exposing (Set) 29 | 30 | 31 | {-| Can be helpful when decoding large objects incrementally. 32 | 33 | import Date (Date) 34 | 35 | type alias User = 36 | { id : Int 37 | , createdAt : Date 38 | , updatedAt : Date 39 | , deletedAt : Maybe Date 40 | , username : Maybe String 41 | , email : Maybe String 42 | , fullname : Maybe String 43 | , avatar : Maybe String 44 | , isModerator : Bool 45 | , isOrganization : Bool 46 | , isAdmin : Bool 47 | } 48 | 49 | metaDecoder : (Int -> Date -> Date -> Maybe Date -> b) -> Decoder b 50 | metaDecoder f = f 51 | `map` ("id" := int) 52 | `apply` ("createdAt" := date) 53 | `apply` ("updatedAt" := date) 54 | `apply` ("deletedAt" := maybe date) 55 | 56 | userDecoder : Decoder User 57 | userDecoder = metaDecoder User 58 | `apply` ("username" := maybe string) 59 | `apply` ("email" := maybe string) 60 | `apply` ("fullname" := maybe string) 61 | `apply` ("avatar" := maybe string) 62 | `apply` ("isModerator" := bool) 63 | `apply` ("isOrganization" := bool) 64 | `apply` ("isAdmin" := bool) 65 | 66 | This is a shortened form of 67 | 68 | metaDecoder : (Int -> Date -> Date -> Maybe Date -> b) -> Decoder b 69 | metaDecoder f = f 70 | `map` ("id" := int) 71 | `andThen` \f -> f `map` ("createdAt" := date) 72 | `andThen` \f -> f `map` ("updatedAt" := date) 73 | `andThen` \f -> f `map` ("deletedAt" := maybe date) 74 | 75 | userDecoder : Decoder User 76 | userDecoder = metaDecoder User 77 | `andThen` \f -> f `map` ("username" := maybe string) 78 | `andThen` \f -> f `map` ("email" := maybe string) 79 | `andThen` \f -> f `map` ("fullname" := maybe string) 80 | `andThen` \f -> f `map` ("avatar" := maybe string) 81 | `andThen` \f -> f `map` ("isModerator" := bool) 82 | `andThen` \f -> f `map` ("isOrganization" := bool) 83 | `andThen` \f -> f `map` ("isAdmin" := bool) 84 | 85 | -} 86 | apply : Decoder (a -> b) -> Decoder a -> Decoder b 87 | apply = 88 | object2 (<|) 89 | 90 | 91 | {-| Infix version of `apply` that makes for a nice DSL when decoding objects: 92 | 93 | locationDecoder : Decoder Location 94 | locationDecoder = 95 | succeed Location 96 | |: ("id" := int) 97 | |: ("name" := string) 98 | |: ("address" := string) 99 | 100 | 101 | type alias Location = 102 | { id : Int 103 | , name : String 104 | , address : String 105 | } 106 | 107 | If you're curious, here's how this works behind the scenes. 108 | 109 | `Location` is a type alias, and type aliases give you a convenience function 110 | that returns an instance of the record in question. Try this out in `elm repl`: 111 | 112 | > type alias Location = { id : Int, name: String, address: String } 113 | 114 | > Location 115 | : Int -> String -> String -> Repl.Location 116 | 117 | > Location 1 "The White House" "1600 Pennsylvania Ave" 118 | { id = 1, name = "The White House", address = "1600 Pennsylvania Ave" } 119 | 120 | In other words, if you call the `Location` function, passing three arguments, 121 | it will return a new `Location` record by filling in each of its fields. (The 122 | argument order is based on the order in which we listed the fields in the 123 | type alias; the first argument sets `id`, the second argument sets `name`, etc.) 124 | 125 | Now try running this through `elm repl`: 126 | 127 | > import Json.Decode exposing (succeed, int, string, (:=)) 128 | 129 | > succeed Location 130 | 131 | : Json.Decode.Decoder 132 | (Int -> String -> String -> Repl.Location) 133 | 134 | So `succeed Location` gives us a `Decoder (Int -> String -> String -> Location)`. 135 | That's not what we want! What we want is a `Decoder Location`. All we have so 136 | far is a `Decoder` that wraps not a `Location`, but rather a function that 137 | returns a `Location`. 138 | 139 | What `|: ("id" := int)` does is to take that wrapped function and pass an 140 | argument to it. 141 | 142 | > import Json.Decode exposing (succeed, int, string, (:=)) 143 | 144 | > ("id" := int) 145 | : Json.Decode.Decoder Int 146 | 147 | > succeed Location |: ("id" := int) 148 | 149 | : Json.Decode.Decoder 150 | (String -> String -> Repl.Location) 151 | 152 | Notice how the wrapped function no longer takes an `Int` as its first argument. 153 | That's because `|:` went ahead and supplied one: the `Int` wrapped by the decoder 154 | `("id" := int)` (which returns a `Decoder Int`). 155 | 156 | Compare: 157 | 158 | -- succeed Location 159 | Decoder (Int -> String -> String -> Location) 160 | 161 | -- succeed Location |: ("id" := int) 162 | Decoder (String -> String -> Location) 163 | 164 | We still want a `Decoder Location` and we still don't have it yet. Our decoder 165 | still wraps a function instead of a plain `Location`. However, that function is 166 | now smaller by one argument! 167 | 168 | Let's repeat this pattern to provide the first `String` argument next. 169 | 170 | -- succeed Location 171 | Decoder (Int -> String -> String -> Location) 172 | 173 | -- succeed Location |: ("id" := int) 174 | Decoder (String -> String -> Location) 175 | 176 | -- succeed Location |: ("id" := int) |: ("name" := string) 177 | Decoder (String -> Location) 178 | 179 | Smaller and smaller! Now we're down from `(Int -> String -> String -> Location)` 180 | to `(String -> Location)`. What happens if we repeat the pattern one more time? 181 | 182 | -- succeed Location 183 | Decoder (Int -> String -> String -> Location) 184 | 185 | -- succeed Location |: ("id" := int) 186 | Decoder (String -> String -> Location) 187 | 188 | -- succeed Location |: ("id" := int) |: ("name" := string) 189 | Decoder (String -> Location) 190 | 191 | -- succeed Location |: ("id" := int) |: ("name" := string) |: ("address" := string) 192 | Decoder Location 193 | 194 | Having now supplied all three arguments to the wrapped function, it has ceased 195 | to be a function. It's now just a plain old `Location`, like we wanted all along. 196 | 197 | We win! 198 | -} 199 | (|:) : Decoder (a -> b) -> Decoder a -> Decoder b 200 | (|:) = 201 | apply 202 | 203 | 204 | {-| Extract a date using [`Date.fromString`](http://package.elm-lang.org/packages/elm-lang/core/latest/Date#fromString) 205 | -} 206 | date : Decoder Date.Date 207 | date = 208 | customDecoder string Date.fromString 209 | 210 | 211 | {-| Extract a set. 212 | -} 213 | set : Decoder comparable -> Decoder (Set comparable) 214 | set decoder = 215 | (list decoder) 216 | `andThen` (Set.fromList >> succeed) 217 | 218 | 219 | {-| Extract a dict using separate decoders for keys and values. 220 | -} 221 | dict2 : Decoder comparable -> Decoder v -> Decoder (Dict comparable v) 222 | dict2 keyDecoder valueDecoder = 223 | (dict valueDecoder) 224 | `andThen` (Dict.toList >> (decodeDictFromTuples keyDecoder)) 225 | 226 | 227 | 228 | {- Helper function for dict -} 229 | 230 | 231 | decodeDictFromTuples : Decoder comparable -> List ( String, v ) -> Decoder (Dict comparable v) 232 | decodeDictFromTuples keyDecoder tuples = 233 | case tuples of 234 | [] -> 235 | succeed Dict.empty 236 | 237 | ( strKey, value ) :: rest -> 238 | case decodeString keyDecoder strKey of 239 | Ok key -> 240 | (decodeDictFromTuples keyDecoder rest) 241 | `andThen` ((Dict.insert key value) >> succeed) 242 | 243 | Err error -> 244 | fail error 245 | 246 | 247 | {-| Try running the given decoder; if that fails, then succeed with the given 248 | fallback value. 249 | 250 | -- If this field is missing or malformed, it will decode to []. 251 | ("optionalNames" := list string) 252 | |> (withDefault []) 253 | 254 | -} 255 | withDefault : a -> Decoder a -> Decoder a 256 | withDefault fallback decoder = 257 | maybe decoder 258 | `andThen` ((Maybe.withDefault fallback) >> succeed) 259 | 260 | 261 | {-| Extract a value that might be null. If the value is null, 262 | succeed with Nothing. If the value is present but not null, succeed with 263 | Just that value. If the value is missing, fail. 264 | 265 | -- Yields Nothing if middleName is null, and Just middleName if it's a string. 266 | "middleName" := maybeNull string 267 | 268 | -} 269 | maybeNull : Decoder a -> Decoder (Maybe a) 270 | maybeNull decoder = 271 | oneOf [ null Nothing, map Just decoder ] 272 | 273 | 274 | {-| Enable decoders defined in terms of themselves by lazily creating them. 275 | 276 | treeNode = 277 | object2 278 | instantiateTreeNode 279 | ("name" := string) 280 | ("children" := list (lazy (\_ -> treeNode))) 281 | 282 | -} 283 | lazy : (() -> Decoder a) -> Decoder a 284 | lazy getDecoder = 285 | customDecoder value 286 | <| \rawValue -> 287 | decodeValue (getDecoder ()) rawValue 288 | --------------------------------------------------------------------------------