I am finding it very hard to migrate old Model state to a new Model state.
Let's say initially our code had Model, and we persisted it to localstorage.
Now we want to add one additional field, "createdAt" to our Model, so I created a new type alias for the same.
import Json.Decode as D
import Html as H
type alias Todo = {id:Int, title: String}
jsonV1 = """
{"id":1, "title":"Elm-Rocks"}
"""
jsonV2 = """
{"id":1, "title":"Elm-Rocks", "createdAt":1479633701604}
"""
type alias TodoV2 = {id:Int, title: String, createdAt:Int}
decode = D.map2 Todo
(D.field "id" D.int)
(D.field "title" D.string)
decodeV2 = D.map3 TodoV2
(D.field "id" D.int)
(D.field "title" D.string)
(D.field "createdAt" D.int)
result1 = D.decodeString decode jsonV1
result2 = D.decodeString decodeV2 jsonV2
type alias Model = {todos: List Todo}
type alias ModelV2 = {todos: List TodoV2}
main = H.div[] [
H.div[][H.text (toString result1)]
, H.div[][H.text (toString result2)]
]
How to write a decoder/function, that accepts any of v1/v2 format json string and give me a ModelV2
record.
I am aware of Decoder.andThen but I don't know how to write the implementation for todoDecoderV1: ??? -> TodoV2
You can use Json.Decode.oneOf
to try parsers and provide a default fallback by using Json.Decode.succeed
. If you wanted to represent the absence of createdAt
as a 0
, you could write your decoder like this:
decode = D.map3 TodoV2
(D.field "id" D.int)
(D.field "title" D.string)
(D.oneOf [(D.field "createdAt" D.int), D.succeed 0])
However, to more accurately represent reality, I would recommend that your Model be changed so that createdAt
is optional by changing its type to a Maybe Int
. This is a great way to make impossible states impossible.
type alias TodoV3 = {id:Int, title: String, createdAt:Maybe Int}
decodeV3 = D.map3 TodoV3
(D.field "id" D.int)
(D.field "title" D.string)
(D.maybe (D.field "createdAt" D.int))