Elm: Json decoder timestamp to Date - elm

I'm trying to convert a timestamp (ex: "1493287973015") from a JSON to a Date type.
So far I created this custom decoder:
stringToDate : Decoder String -> Decoder Date
stringToDate decoder =
customDecoder decoder Date.fromTime
But it doesn't work because it has return a Result, not a Date:
Function `customDecoder` is expecting the 2nd argument to be:
Time.Time -> Result String a
But it is:
Time.Time -> Date.Date
Is there a way to do a conversion?

Assuming your JSON is actually placing the numeric value inside quotes (meaning you are parsing the JSON value "1493287973015" and not 1493287973015), your decoder could look like this:
import Json.Decode exposing (..)
import Date
import String
stringToDate : Decoder Date.Date
stringToDate =
string
|> andThen (\val ->
case String.toFloat val of
Err err -> fail err
Ok ms -> succeed <| Date.fromTime ms)
Notice that stringToDate doesn't get passed any parameters, as opposed to your example in which you were attempting to pass a Decoder String as a parameter. That's not quite how decoders work.
Instead, this can be done by building upon more primitive decoders, in this case, we start with the decoder string from Json.Decode.
The andThen portion then takes the string value given by the decoder, and tries to parse it to a float. If it is a valid Float, it is fed into Date.fromTime, otherwise, it's a failure.
The fail and succeed functions wrap up the normal values you're dealing with into Decoder Date.Date contexts so they can be returned.

Two things, a JSON may actually have the milliseconds as an integer, not a string and things have changed since v 0.19 of Elm.
Given that your JSON looks something like.
{
...
"someTime": 1552483995395,
...
}
Then this would decode to a Time.Posix:
import Json.Decode as Decode
decodeTime : Decode.Decoder Time.Posix
decodeTime =
Decode.int
|> Decode.andThen
(\ms ->
Decode.succeed <| Time.millisToPosix ms
)

Related

Elm Http.post needs to produces Http.Request Int but send needs Http.Request String

I decode response from Http.post request, where return type is Integer:
responseDecoder : Decoder Int
responseDecoder =
field "data" (field "createDeadline" (field "id" int))
My problem is that I use Http.send which needs String:
createDeadline value =
Http.send Resolved (Http.post deadlineUrl (encodeBody value |> Http.jsonBody) responseDecoder)
And I dont know how to change the return type. My error message is following:
The 2nd argument to `send` is not what I expect:
113| Http.send Resolved (Http.post deadlineUrl (encodeBody value |> Http.jsonBody) responseDecoder)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This `post` call produces:
Http.Request Int
But `send` needs the 2nd argument to be:
Http.Request String
Hint: I always figure out the argument types from left to right. If an argument
is acceptable, I assume it is “correct” and move on. So the problem may actually
be in one of the previous arguments!
Hint: Want to convert an Int into a String? Use the String.fromInt function!
Can anyone help with this? I'm just playing around with Elm, but I got stuck here.
My problem is that I use Http.send which needs String
According to the docs, send function requires the argument to be of type Request a, where a can be any type (Int or String or something else).
The problem that you have is just what the hint in the compile error says:
Hint: I always figure out the argument types from left to right. If an argument
is acceptable, I assume it is “correct” and move on. So the problem may actually
be in one of the previous arguments!
Thus, it seems that you've already defined somewhere, that you're expecting String and the compiler infered the type to Request String. For example, you might have the Resolved defined something like the following:
type Msg = Resolved (Result Http.Error String)
And the compiler inferred the polymorphic type of send : (Result Error a -> msg) -> Request a -> Cmd msg to something specific, because it already sees that the first argument is or type (Result Error String -> msg):
send : (Result Error String -> msg) -> Request String -> Cmd msg
So, in this case the solution is either to change the expecting type:
type Msg = Resolved (Result Http.Error Int)
Or change the decoder and decode the response to String:
responseDecoder : Decoder String
responseDecoder =
Json.Decode.map String.fromInt (field "data" (field "createDeadline" (field "id" int)))

Avoid Http Race Condition in Elm

Let's assume we have a text input field and on every change of its content we send an Http request to a search API. Now, we don't have any guarantee that the Http responses get back to elm in the same order that we sent the requests.
What's the easiest way to make sure we react to the response corresponding to the latest request – rather than the latest response, which might correspond to an outdated search string? Is there an easy way to attach the query string to the message returned by Elm's http effect? Or any other way we can link the response to the request by which it was triggered?
I'd like to avoid including the query in the response of the search API if possible. Another remedy would be to debounce the search, but that would just decrease the probability of using the wrong response, whereas we'd like to eliminate it.
Thanks for your help!
Example:
import Html
import Html exposing (..)
import Html.Events exposing (onClick, onInput)
import Http
import Json.Decode as Decode
main = Html.program
{ init = ( { searchText = "", result = "" }, Cmd.none )
, update = update
, subscriptions = (\model -> Sub.none)
, view = view
}
type alias Model =
{ searchText : String
, result: SearchResult
}
type alias SearchResult = String
type Msg
= NewSearchText String
| ReceivedResponse (Result Http.Error SearchResult)
update msg model =
case msg of
NewSearchText newText ->
( { model | searchText = newText}
, getSearchResult newText
)
ReceivedResponse (Result.Ok response) ->
( { model | result = response }
, Cmd.none
)
ReceivedResponse (Result.Err error) ->
Debug.crash <| (toString error)
getSearchResult : String -> Cmd Msg
getSearchResult query =
let
url = "http://thebackend.com/search?query=" ++ query
request : Http.Request SearchResult
request = Http.get url Decode.string
in
Http.send ReceivedResponse request
view model =
div []
[ Html.input [onInput (\text -> NewSearchText text)] []
, Html.text model.result
]
Yes, it is possible to attach the query string to the response. First, augment your message type to handle the additional data:
type Msg
= NewSearchText String
| ReceivedResponse String (Result Http.Error SearchResult)
Then, change your Http.send call to attach the query text to the ReceivedResponse message:
Http.send (ReceivedResponse query) request
Finally, in your update, grab the query in your pattern match on the resulting Msg:
case msg of
ReceivedResponse query (Ok response) ->
...
ReceivedResponse query (Err err) ->
...
Why does this work?
The Http.send function's first argument can be an arbitrary function that consumes a Result Http.Error SearchResult and turns it into a Msg. In your original code, that function is just ReceivedResponse, the Msg constructor. When the Msg type is updated so that ReceivedResponse takes two arguments, the ReceivedResponse constructor function becomes a curried two-argument function, and ReceivedResponse "some query here" is a one-argument function that takes in a Result and returns a Msg.
Here's one way:
Add two integers to your model:
requestsSent : Int -- the number of requests made.
lastReceived : Int -- the latest request that you've processed.
Modify ReceivedResponse to have an Int as the first value:
| ReceivedResponse Int (Result Http.Error SearchResult)
Now, whenever you make a request, increment requestsSent by 1 in the model and "tag" the request by partially applying ReceivedResponse:
Http.send (ReceivedResponse model.requestsSent) request
In your update function, check if the Int in the ReceivedResponse is greater than lastReceived or not. If it is, process it, and set the value of lastReceived to this response's Int. If it isn't, discard it, because you've already processed a newer request.

Decode JSON where value can be a string or an array of different values

Yet another question on how to decode things with Elm...
So the problem is that I need to decode a value which can be either a string, for example
"price_unknown"
or it can be an array of 2 elements, where first one is a string and the second is a float:
["price", 50.5]
And for the eventual value I have a type:
type Something
= PriceUnknown
= Price Float
into which I need to convert the value from the json response.
I tried to use a bunch of things using somewhat between the lines of:
decode MyString
|> required "other_value" Json.Decode.string
|> required "price" JD.oneOf [JD.string, JD.list (JD.oneOf [JD.string, JD.float])] |> JD.map mapper
( I use json_decode_pipeline package here )
But obviously it complains about different values in the lists and whatnot so I am stuck.
Thank you in advance.
You are pretty close, but all of the Decoders in oneOf have to have the same type. Also, destructuring mixed lists can be a pain. This uses elm-community/json-extra to make a manual decoding step easier.
myDecoder : Decoder SomethingElse
myDecoder =
decode MyString
|> required "other_value" Json.Decode.string
|> required "price" priceDecoder
priceDecoder : Decoder Something
priceDecoder =
JD.oneOf
[ priceUnknownDecoder
, priceKnownDecoder
]
priceUnknownDecoder : Decoder Something
priceUnknownDecoder =
JD.string
|> JD.andThen
(\string ->
if string == "price_unknown" then
JD.succeed PriceUnknown
else
JD.fail "Invalid unknown price string."
)
priceKnownDecoder : Decoder Something
priceKnownDecoder =
listTupleDecoder
JD.string
JD.float
|> JD.andThen
(\(tag, price) ->
if tag == "price" then
JD.succeed (Price price)
else
JD.fail "Invalid tag string."
)
listTupleDecoder : Decoder a -> Decoder b -> Decoder (a, b)
listTupleDecoder firstD secondD =
JD.list JD.value
|> JD.andThen
(\values ->
case values of
[first, second] ->
Result.map2
(,)
JD.decodeValue firstD first
JD.decodeValue secondD second
|> JD.Extra.fromResult
_ ->
JD.fail "There aren't two values in the list."
)

How to convert Task Http.RawError Http.Response to Task String (Int, Int)

There are a type and a task
type Msg
= Fail String
| Success (Int, Int)
makeRequest =
let
req =
{ verb = "GET"
, headers = []
, url = "http://localhost:8080"
, body = empty
}
in
Task.perform Fail Success <| send defaultSettings req
The argument of Fail constructor is for error message (just "Error"), first argument of Succeess is for status from Http.Response, second is for size of value from Http.Response.
How to convert Task Http.RawError Http.Response to Task String (Int, Int)?
I'm looking at Task.map and Tsk.mapError and I don't understand how to combine them. Am I on a right way?
Yes, you can use Task.map and Task.mapError to achieve your results.
First off, you'll need a way to determine the size of your Http Response. Since it can be either a string or binary blob, and blob is not yet supported, you could define a function like this:
httpValueSize : Http.Value -> Int
httpValueSize val =
case val of
Text str -> String.length str
Blob blob -> Debug.crash "Blobs have no implementation yet"
Now you can use the mapping functions in your task like this:
send defaultSettings req
|> Task.map (\r -> (r.status, httpValueSize r.value))
|> Task.mapError (always "Error")
|> Task.perform Fail Success
You could also do this without the mapping functions like so:
send defaultSettings req
|> Task.perform (always <| Fail "Error") (\r -> Success (r.status, httpValueSize r.value))

In Elm what is the correct way to implement my own toString

In Elm what is the correct way to take my Model and implement a toString function?
The type I am looking for would be toString : Model -> String, I am able to make a similar function with the type of toStr : Model -> String but I would think I would want the function to be called toString.
Example program (the Coin Changer kata):
module CoinChanger where
import Html exposing (..)
import StartApp.Simple as StartApp
import Signal exposing (Address)
import Html.Attributes exposing (..)
import Html.Events exposing (on, targetValue)
import String
---- MAIN ----
main =
StartApp.start
{
model = emptyModel
,update = update
,view = view
}
---- Model ----
type alias Model =
{
change : List Int
}
emptyModel : Model
emptyModel =
{
change = []
}
---- VIEW ----
toStr : Model -> String
toStr model =
model.change
|> List.map (\coin -> (toString coin) ++ "¢")
|> String.join ", "
view : Address String -> Model -> Html
view address model =
div []
[
input
[
placeholder "amount to make change for"
, on "input" targetValue (Signal.message address)
, autofocus True
-- style
]
[]
, div []
[
text (toStr model)
]
]
---- UPDATE ----
changeFor : Int -> List Int
changeFor amount =
[ 25, 10, 5, 1 ]
|> List.foldl
(\coin (change, amount)
-> ( change ++ List.repeat (amount // coin) coin
, amount % coin)
)
([], amount)
|> fst
update : String -> Model -> Model
update change model =
{ model | change =
case String.toInt change of
Ok amount
-> changeFor amount
Err msg
-> []
}
I would think the correct way to do this would be to call the function toString, but that gives me the following error from the compiler:
Detected errors in 1 module.
-- TYPE MISMATCH ----------------------------------------------- CoinChanger.elm
The type annotation for toString does not match its definition.
42│ toString : Model -> String
^^^^^^^^^^^^^^^ The type annotation is saying:
{ change : List Int } -> String
But I am inferring that the definition has this type:
{ change : List { change : List Int } } -> String
Renaming the function to toStr (or something not called toString) fixes the issue but seems wrong. What is the correct way to do this?
The problem is that, calling your function toString, you are overriding the toString function of the Basics module, which you are using at line 45.
To avoid this, you'll need to import the Basics module and use Basics.toString instead of simply toString to eliminare the ambiguity
The accepted answer is well out of date for anyone writing Elm 0.19+. The current solution is to write your own toString function for the type you want converted. There is a Debug.toString for use during development but its use in your code will prevent building for production.