Last active
November 17, 2016 06:34
-
-
Save kkruups/89ae8e58f54bb9b15357406abc914caf to your computer and use it in GitHub Desktop.
Elm: Medium post: Elm Insight A Road to a Better Understanding: How the elm JSON API, Elm-Decode-Pipeline and Json.Decode API Work
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-- | |
Copy-n-Paste this code into Elm Online Editor to see run: | |
http://elm-lang.org/try | |
Updated to reflect changes in elm release version 0.18 | |
field replaces (:=) | |
mapN replace objectN functions | |
All code except data blob -- jsonBlob is written by author (@Brian Hichs mention below) | |
--} | |
import Html exposing (text, div, span) | |
import Html.Attributes as Attributes | |
import Json.Decode as Json exposing (field, map2, string, int) | |
import String exposing (toLower) | |
{-- | |
To simulate Json error just change data type of any field | |
for example change "id":1 to "id": "one" or "id": one in jsonBlob below | |
--} | |
--data blob from Brian Hicks wonderful article on "Decoding Large JSON Objects: A Summary" | |
-- https://www.brianthicks.com/post/2016/08/22/decoding-large-json-objects-a-summary/ | |
jsonBlob = | |
"""{ | |
"login": "octocat", | |
"id": 1, | |
"avatar_url": "https://github.com/images/error/octocat_happy.gif", | |
"gravatar_id": "", | |
"url": "https://api.github.com/users/octocat", | |
"html_url": "https://github.com/octocat", | |
"followers_url": "https://api.github.com/users/octocat/followers", | |
"following_url": "https://api.github.com/users/octocat/following{/other_user}", | |
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", | |
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", | |
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions", | |
"organizations_url": "https://api.github.com/users/octocat/orgs", | |
"repos_url": "https://api.github.com/users/octocat/repos", | |
"events_url": "https://api.github.com/users/octocat/events{/privacy}", | |
"received_events_url": "https://api.github.com/users/octocat/received_events", | |
"type": "User", | |
"site_admin": false, | |
"name": "monalisa octocat", | |
"company": "GitHub", | |
"blog": "https://github.com/blog", | |
"location": "San Francisco", | |
"email": "octocat@github.com", | |
"hireable": false, | |
"bio": "There once was...", | |
"public_repos": 2, | |
"public_gists": 1, | |
"followers": 20, | |
"following": 0, | |
"created_at": "2008-01-14T04:33:35Z", | |
"updated_at": "2008-01-14T04:33:35Z" | |
}""" | |
--elm record data type | |
type alias User = | |
{ | |
login : String | |
,id : Int | |
,name : String | |
,company : String | |
,blog : String | |
,followers : Int | |
} | |
{-- pipe decoder helper : enables a tagger/function decoder | |
to be piped using the pipe operator, in order to be constructed by | |
its members" value decoders. It eliminates the limitations of using Json.Decode.objectN | |
functions by extending the limits of the number JSON objects which can be decoded by | |
enabling the chaining of tagger/function component decoders via the pipe operator --} | |
pipeDecoderBldr: String -> Json.Decoder a -> Json.Decoder ( a-> b) -> Json.Decoder b | |
pipeDecoderBldr key valueDecoder taggerDecoder = Json.map2 (|>) ( field key valueDecoder) taggerDecoder | |
--userDecoder : Json.Decoder User | |
{-- | |
userDecoder = | |
Debug.log "object3: " Json.object3 User | |
("login" := Json.string ) | |
( "id" := Json.int ) | |
("name" := Json.string) | |
--} | |
-- Helper converts a tagger/function to a tagger decoder, wraps Json.succeed, | |
-- the purpose of wrapping it is to improve the name of the function, | |
-- to have it match what it actually does | |
toDecoder: a -> Json.Decoder a | |
toDecoder tagger = Json.succeed tagger | |
-- userDecoder reduces the User function Json Decoder, created by toDecoder, down to a direct User Record Json Decoder | |
-- by piping subsequent returned function decoder to chained calls of pipedecode_bldr which explicity passes the Json field name and component Json decoder | |
-- corresponding to the data type of each field desired for extraction/decoding from the Json String | |
user_decoder = | |
toDecoder User -- returns/passes Decoder (String-> Int -> String -> String -> String -> Int -> User) | |
|>pipeDecoderBldr "login" Json.string -- returns/passes Decoder (Int -> String -> String -> String -> Int -> User) | |
|>pipeDecoderBldr "id" Json.int -- returns/passes Decoder (String -> String -> String -> Int -> User) | |
|>pipeDecoderBldr "name" Json.string -- returns/passes Decoder (String -> String -> Int -> User) | |
|>pipeDecoderBldr "company" Json.string -- returns/passes Decoder (String -> Int -> User) | |
|>pipeDecoderBldr "blog" Json.string -- returns/passes Decoder (Int -> User) | |
|>pipeDecoderBldr "followers" Json.int -- returns Decoder (User) /Final Step: User Decoder Constructed/ | |
userDecode json_string = Json.decodeString user_decoder json_string | |
userDecoderHandler: String -> String | |
userDecoderHandler j_blob = | |
let jresult = | |
userDecode j_blob | |
in | |
case jresult of | |
Ok user_record-> | |
toString <| user_record | |
Err error -> | |
"userDeocderHandler: " ++ error | |
(==>) = | |
(,) | |
--Helpers to allow strings to be passed to Html Helpers (style attributes and span ) | |
padding pad = | |
let | |
pad_ = toLower pad | |
in | |
if pad_ == "nothing" then | |
Nothing | |
else | |
Just pad_ | |
bkgnd_clr bkgnd = | |
let | |
bkgnd_ = toLower bkgnd | |
in | |
if bkgnd_ == "nothing" then | |
Nothing | |
else | |
Just bkgnd_ | |
txt_clr tx_clr = | |
let | |
tx_clr_ = toLower tx_clr | |
in | |
if tx_clr_ == "nothing" then | |
Nothing | |
else | |
Just tx_clr_ | |
display disp = | |
let | |
disp_ = toLower disp | |
in | |
if disp_ == "nothing" then | |
Nothing | |
else | |
Just disp_ | |
font_size fsize = | |
let | |
fsize_ = toLower fsize | |
in | |
if fsize_ == "nothing" then | |
Nothing | |
else | |
Just fsize_ | |
{-- refaactored to above: refactoring allows string arguments to Html helpers to be non-case specific | |
font_size fsize = | |
if fsize == "nothing" || fsize == "Nothing" || fsize == "NOTHING" then | |
Nothing | |
else | |
Just fsize | |
--} | |
-- ############################################################# | |
-- Html helpers; styleIT, styelIWithDefault, & spanIt | |
-- ============================================================= | |
--demonstrates building a list as well as setting defaults using Maybe String pattern | |
styleIt: String -> String -> String -> String -> String -> List (String, String) | |
styleIt p b t d f = | |
let list = ( "padding" ==> Maybe.withDefault "25px" (padding p) ) | |
:: ("background-color" ==> Maybe.withDefault "black" (bkgnd_clr b)) | |
:: ( "color" ==> Maybe.withDefault "white" (txt_clr t) ) | |
:: ( "display" ==> Maybe.withDefault "block" (display d) ) | |
:: ( "font-size" ==> Maybe.withDefault "25px" (font_size f) ) | |
::[] | |
in | |
list | |
--just change backgound color | |
--(defaults :padding: "25px", background-color: "black", text-color: "white", display: "block", font-size: "25px") | |
styleItWithDefault bkgnd_clr = | |
styleIt "NOTHING" bkgnd_clr "nothing" "nothing" "nothing" | |
{-- | |
--spanIt: List String -> String -> Html.Html msg | |
spanIt decorate txtmsg = | |
span[Attributes.style <| styleIt decorate ] [ text txtmsg ] | |
-} | |
--spanIt: (Maybe String, Maybe String, Maybe String, Maybe String) -> String -> Html.Html msg | |
-- demonstrates decomposing a tuple | |
spanIt styleTup txtmsg = | |
let | |
(p, b, t, d, f) = styleTup | |
in | |
span [ Attributes.style <| (styleIt (p) (b) (t) (d) (f) ) ] [ text txtmsg ] | |
-- ===================================== | |
main = | |
div[ Attributes.style <| styleItWithDefault "NOThinG" | |
] | |
[ spanIt ("2px", "WhitE", "BLacK", "block", "40px") "Result Ok (User Record)" | |
, text <| toString <| userDecode jsonBlob | |
, div[ Attributes.style[ | |
("padding", "20px") | |
, ("background-color", "Orange") | |
, ("color", "black") | |
, ("display", "block") | |
] | |
][ | |
spanIt ("0PX", "blue", "oranGe", "block", "35px") "Result.withDefault" | |
,text <| toString <| ( | |
Debug.log "Result.withDefault:" <| | |
Result.withDefault { | |
login="not logged in" | |
,id = 0 | |
,name = "no name" | |
,company = "no company" | |
,blog ="no blog url" | |
,followers = -1 | |
} <| userDecode jsonBlob | |
) | |
] | |
, div[ Attributes.style[ | |
("padding", "20px") | |
, ("background-color", "beige") | |
, ("color", "brown") | |
] | |
][ | |
div[ Attributes.style <| styleIt "0px" "beige" "Green" "nothinG" "35PX" | |
][ | |
text "userDecoderHandler: (case .. of pattern) " | |
] | |
,text ( | |
Debug.log "userDecoderHandler: (case..of pattern)" | |
<| userDecoderHandler jsonBlob | |
) | |
] | |
] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment