Should decoder and record be fields order independant?

#1

Hello,

UserRecordDecoder =
  D.map2 UserRecord
    (D.field "id" D.int)
    (D.field "level" D.int)
 
type alias UserRecord =
  { id : Int
  , level : Int
  }

will do the job however if we inverse the order of the fields like this

type alias UserRecord =
  { level : Int
  , id : Int
  }

for a user { id = 1, level = 10 } you will get { id = 10, level = 1 }

Would it be better and possible not to assert that the orders of decoder and record match when decoding json and decode just according to the field names?

Tks

1 Like
#2

This package helps with decoding json in a way that the order does not matter. It also has some more info on the subject.

2 Likes
#3

It looks pretty good, tks!

#4

I started to use it, documentation is great, thanks to have spent time on it!
With elm-format the nested scopes show up !
And I got a better understanding of the do notation (Decode.succeed == return :sunny: ), I love functional ~

UserRecord_decoder : D.Decoder UserRecord
UserRecord_decoder =
    Field.require "name" D.string
        <| \name ->
            Field.require "email" D.string
                <| \email ->
                    Field.require "phone" D.string
                        <| \phone ->
                            Decode.succeed
                                { email = email
                                , phone = phone
                                , name = name
                                }
#5

I am glad you found it helpful. The indenting done by elm-format can be annoying sometimes, at least when you have many fields.

The do notation-ish syntax can be used with any type that has the andThen function and can be quite useful sometimes.

Here is an example with Maybe. (Note that Just == return in this case)

do : Maybe a -> (a -> Maybe b) -> Maybe b
do mb continuation =
    Maybe.andThen continuation mb

getSomething : String -> Dict String String -> Maybe String
getSomething key dict =
    do (Dict.get key dict) <| \value1 ->
    do (Dict.get value1 dict) <| \value2 ->
    do (Dict.get value2 dict) <| \value3 ->
    Just value3
1 Like
#6
do : Maybe a -> (a -> Maybe b) -> Maybe b
do mb continuation =
    Maybe.andThen continuation mb

getSomething acc key dict = -- acc: accumulator
    do (Dict.get key dict) <| \value1 ->
    do (Dict.get value1 dict) <| \value3 ->
    (dict, Just value3 :: acc)

getSomethingElse acc key dict =
    do (Dict.get key dict) <| \ values ->
    do (Dict.get value1 dict) <| \value2 ->
    (dict, Just value2 :: acc])

do (getSomething [] "Lisa" dict) <| \(dict, acc) -> getSomethingElse acc "Tom" dict -- return (dict,  [ Just value2, Just value3])

In another way how can I make it composable?
Is the code above making sense?
Is there a better way?

#7

You can also not use the alias constructor, which is what relies on order, like this:

userRecordDecoder =
    D.map2 (\id level -> { id = id, level = level })
        (D.field "id" D.int)
        (D.field "level" D.int)

And then your decoder is independent of the type reordering it’s fields.

3 Likes
#8

It is worth mentioning that!

closed #9

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.