Jigar Jigar - 19 days ago 6
JSON Question

How to write a Json decoder that converts from type ModelV1 to type ModelV2

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

Answer

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))