I'm receiving a JSON that looks like this:
{ name: "NAME1", value: "true" }
I would like to create a json decoder that would create a record like this:
{ name: "NAME1", value: True }
I'm trying to make a decoder that transforms "true" into True. I did this so far:
userProperties : Json.Decode.Decoder Props
userProperties =
Json.Decode.object2 (,)
("name" := Json.Decode.string)
("value" := Json.Decode.string)
`andThen` \val ->
let
newVal = -- Do something here?
in
Json.Decode.succeed <| newVal
There are a few problems in your example so let's step through each of them.
You haven't shown the definition of Props so I'm assuming, based on your example, that it is this:
type alias Props = { name : String, value : Bool }
You are passing (,) as the first argument to object2 which indicates that you will be returning a Decoder of type tuple. That should probably be:
Json.Decode.object2 Props
Now, the way you are using andThen won't compile because of its order of precedence. If you were to parenthesize the whole thing, it would look like this:
userProperties =
(Json.Decode.object2 Props
("name" := Json.Decode.string)
("value" := Json.Decode.string))
`andThen` \val ->
let
newVal = -- Do something here?
in
Json.Decode.succeed <| newVal
That isn't going to be correct since the thing you want to andThen is the string "true" in the "value" field. To do that, I would recommend creating a decoder which provides that boolean decoder:
stringBoolDecoder : Json.Decode.Decoder Bool
stringBoolDecoder =
string `andThen` \val ->
case val of
"true" -> succeed True
"false" -> succeed False
_ -> fail <| "Expecting \"true\" or \"false\" but found " ++ val
I'm only guessing at the handling of "false" and the catch-all underscore. Change their implementation as suits your business case.
When building complex decoders, it is often best to break up decoder definitions into the smallest chunk possible like in the above.
Lastly, we can now redefine your userProperties decoder to use the stringBoolDecoder in the appropriate place:
userProperties : Json.Decode.Decoder Props
userProperties =
Json.Decode.object2 Props
("name" := Json.Decode.string)
("value" := stringBoolDecoder)
Related
I'm new to Elm and I ran into this problem...
We get translations for our page using something like:
case (translate translation.id) of
Success: -> translation
Failure: -> translation.id
Where translate just finds translation.id in a dictionary and it may or may not be there.
There are no runtime errors because you get a string either way, but we would like to log the missing translation to a rest service logger. But Elm hates side effects in the view that doesn't stem from html events so I'm not sure how to handle this.
Obviously in regular JS you could just crowbar in a fetch inside the failure case block and then return a string afterwards but that doesn't seem to be possible in Elm?
You need to move the effects part of your code into the update function. In this case I suggest doing it when the translation arrives. There you will want something like
OnTranslation translation ->
( { model | translation = translation } -- attach to model
, case translate translation.id of
Ok _ ->
Cmd.none
Err err ->
-- register with the error logger
logMissingTranslation translation.id
)
#Odin Thorsen, use Html.node and Html.Attributes.attribute to reference a custom element in Elm:
-- VIEW
view : Model -> Html.Html ()
view model =
Html.div []
[ translation model "key"
, translation model "junk key"
]
translation : Model -> String -> Html.Html ()
translation { translations } id =
Html.node "translated-text"
[ Html.Attributes.attribute "translation-id" id
, Html.Attributes.attribute "translation-text" <| translate translations id
]
[]
translate : Dict.Dict String String -> String -> String
translate translations id =
case Dict.get id translations of
Just value ->
value
Nothing ->
""
And in Javascript, use customElements.define to create the custom element:
customElements.define("translated-text", class extends HTMLElement {
constructor() { super(); }
connectedCallback() { }
attributeChangedCallback() { this.setTextAndLogFailure(); }
static get observedAttributes() { return ["translation-id", "translation-text"]; }
setTextAndLogFailure() {
var id = this.getAttribute("translation-id");
var text = this.getAttribute("translation-text");
if (text === null) return;
this.textContent = text;
if (!text.length) alert("Unkown translation id: " + id);
}
});
You'd replace alert("Unkown translation id: " + id); with the logging fetch.
Here is an Ellie with a full solution.
Let’s say you have a basic API (GET/POST/PATCH/DELETE) backed by an SQL database.
The PATCH call should only update the fields in the JSON payload that the user sends, without touching any of the other fields.
Imagine the table (let's call it sample) has id, string_a and string_b columns, and the struct which corresponds to it looks like:
type Sample struct {
ID int `json:"id"`
StringA string `json:"stringA"`
StringB string `json:"stringB"`
}
Let's say the user passes in { "stringA": "patched value" } as payload. The json will be unmarshalled to something that looks like:
&Sample{
ID: 0,
StringA: "patched value",
StringB: "",
}
For a project using database/sql, you’d write the query to patch the row something like:
// `id` is from the URL params
query := `UPDATE sample SET string_a=$1, string_b=$2 WHERE id=$3`
row := db.QueryRow(query, sample.StringA, sample.StringB, id)
...
That query would update the string_a column as expected, but it’d also update the string_b column to "", which is undesired behavior in this case. In essence, I’ve just created a PUT instead of a PATCH.
My immediate thought was - OK, that’s fine, let’s use strings.Builder to build out the query and only add a SET statement for those that have a non-nil/empty value.
However, in that case, if a user wanted to make string_a empty, how would they accomplish that?
Eg. the user makes a PATCH call with { "stringA": "" } as payload. That would get unmarshalled to something like:
&Sample{
ID: 0,
StringA: "",
StringB: "",
}
The “query builder” I was theorizing about would look at that and say “ok, those are all nil/empty values, don’t add them to the query” and no columns would be updated, which again, is undesired behavior.
I’m not sure how to write my API and the SQL queries it runs in a way that satisfies both cases. Any thoughts?
I think reasonable solution for smaller queries is to build UPDATE query and list of bound parameters dynamically while processing payload with logic that recognizes what was updated and what was left empty.
From my own experience this is clear and readable (if repetitive you can always iterate over struct members that share same logic or employ reflection and look at struct tags hints, etc.). Every (my) attempt to write universal solution for this ended up as very convoluted overkill supporting all sorts of corner-cases and behavioral differences between endpoints.
func patchSample(s Sample) {
var query strings.Builder
params := make([]interface{}, 0, 2)
// TODO Check if patch makes sense (e.g. id is non-zero, at least one patched value provided, etc.
query.WriteString("UPDATE sample SET")
if s.StringA != "" {
query.WriteString(" stringA = ?")
params = append(params, s.StringA)
}
if s.StringB != "" {
query.WriteString(" stringB = ?")
params = append(params, s.StringB)
}
query.WriteString(" WHERE id = ?")
params = append(params, s.ID)
fmt.Println(query.String(), params)
//_, err := db.Exec(query.String(), params...)
}
func main() {
patchSample(Sample{1, "Foo", ""})
patchSample(Sample{2, "", "Bar"})
patchSample(Sample{3, "Foo", "Bar"})
}
EDIT: In case "" is valid value for patching then it needs to be distinguishable from the default empty value. One way how to solve that for string is to use pointer which will default to nil if value is not present in payload:
type Sample struct {
ID int `json:"id"`
StringA *string `json:"stringA"`
StringB *string `json:"stringB"`
}
and then modify condition(s) to check if field was sent like this:
if s.StringA != nil {
query.WriteString(" stringA = ?")
params = append(params, *s.StringA)
}
See full example in playground: https://go.dev/play/p/RI7OsNEYrk6
For what it's worth, I solved the issue by:
Converting the request payload to a generic map[string]interface{}.
Implementing a query builder that loops through the map's keys to create a query.
Part of the reason I went this route is it fit all my requirements, and I didn't particularly like having *strings or *ints laying around.
Here is what the query builder looks like:
func patchQueryBuilder(id string, patch map[string]interface{}) (string, []interface{}, error) {
var query strings.Builder
params := make([]interface{}, 0)
query.WriteString("UPDATE some_table SET")
for k, v := range patch {
switch k {
case "someString":
if someString, ok := v.(string); ok {
query.WriteString(fmt.Sprintf(" some_string=$%d,", len(params)+1))
params = append(params, someString)
} else {
return "", []interface{}{}, fmt.Errorf("could not process some_string")
}
case "someBool":
if someBool, ok := v.(bool); ok {
query.WriteString(fmt.Sprintf(" some_bool=$%d,", len(params)+1))
params = append(params, someBool)
} else {
return "", []interface{}{}, fmt.Errorf("could not process some_bool")
}
}
}
if len(params) > 0 {
// Remove trailing comma to avoid syntax errors
queryString := fmt.Sprintf("%s WHERE id=$%d RETURNING *", strings.TrimSuffix(query.String(), ","), len(params)+1)
params = append(params, id)
return queryString, params, nil
} else {
return "", []interface{}{}, nil
}
}
Note that I'm using PostgreSQL, so I needed to provide numbered parameters to the query, eg $1, which is what params is used for. It's also returned from the function so that it can be used as follows:
// Build the patch query based on the payload
query, params, err := patchQueryBuilder(id, patch)
if err != nil {
return nil, err
}
// Use the query/params and get output
row := tx.QueryRowContext(ctx, query, params...)
I'm currently learning elm, I just stumbled on this problem where the div returns a Html (String -> Msg) instead of Html Msg.
error message I'm receiving
This div call produces:
Html (String -> Msg)
But the type annotation on view says it should be:
Html Msg
type alias Model =
{
firstNum: String,
secondNum: String,
answer: String
}
init: Model
init = { firstNum = "",
secondNum = "",
answer = ""}
type Msg =
Add String| Minus String
update: Msg -> Model -> Model
update msg model =
case msg of
Add x -> { model | answer = x}
Minus y -> { model | answer = y}
view : Model -> Html Msg
view model =
div []
[
input [ placeholder "Text to reverse", value model.firstNum] [],
button [onClick Add] [text "add"],
div [] [text model.answer]
]
main =
Browser.sandbox
{ init = init,
update = update,
view = view
}
You define the Msg type as
type Msg =
Add String| Minus String
with Add taking a String argument, but when you use it here:
button [onClick Add] [text "add"],
you're not giving it any argument at all.
The underlying issue seems to be that your mental model of the Elm Architecture is wrong. You seem to consider messages as "operations" or function calls rather than events, where Add is a function that takes an argument to apply to the model.
You should instead consider a message as a description of what triggered it. Instead of Add String, you might call it AddButtonClicked, with no arguments (in this case). Then have the update function do what it should based on what's in the model alone, which I'm guessing is an arithmetic operation on firstNum and secondNum.
But you're also not populating those fields. To do so you need to use the onInput event, which does ask for a message that takes a String. You might add a new message FirstNumChanged String for example, then use it with input like this:
input [ placeholder "Text to reverse", onInput FirstNumChanged, value model.firstNum] [],
I'll leave it to you to figure out how to handle it in update.
I am doing a http request with elm and my response is an Array of Objects. Each object is as follows
obj = {
title: "Some Title",
words: [ "word1", "word2", "word3", "word4" ]
}
Here is my decoder so far:
type alias ThisRes = List ResObj
type alias ResObj =
title: String
words: List String
decoded : Decoder ThisRes
decoded =
decode ThisRes
I can't seem to get the decoder right and any help that can be provided would be appreciated.
obj =
"""
{
"title": "Some Title",
"words": [ "word1", "word2", "word3", "word4" ]
}
"""
type alias ResObj =
{ title : String, words : List String }
objDecoder =
map2 ResObj
(at [ "title" ] string)
(at [ "words" ] (list string))
headingFrom : Result String ResObj -> String
headingFrom result =
case result of
Ok resobj ->
resobj.title
Err reason ->
toString reason
main =
h1 [] [ text <| headingFrom <| decodeString objDecoder obj ]
Breaking this down:
obj is just a string representing some JSON, to illustrate the example.
You define the ResObj type alias, and get a type constructor for it. Now you can create ResObj values by calling ResObj "MyTitle" ["word1", "wordb", "wordFightingMongooses"].
objDecoder is a value that helps decode JSON into ResObj. It calls map2, which takes a type constructor, and then two decoded JSON fields. (at ["title"] string) says "turn the value at title in the JSON into a string" and you can guess what the next line does. So this evaluates to, ultimately, ResObj "SomeTitle" ["word1", "word2", "word3", "word4"] and creates your value.
Down in our main, we have the expression decodeString objDecoder obj. decodeString takes a decoder value, and a JSON string, and sees if it can decode it. But it doesn't return the ResObj itself -- because the decoding can fail, it returns a Result.
So we have to process that Result with our function headingFrom. If the decoding succeeded our result will be Ok resobj, and we can work with the resobj finally. If it failed it'll be Err reason.
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.