Encoding/Decoding entire application state?

596 views
Skip to first unread message

Joe Fiorini

unread,
Jan 2, 2015, 4:39:59 PM1/2/15
to elm-d...@googlegroups.com
In my application I am implementing the ability to serialize the entire application state (base64 encoded) to the URL, and then deserialize it on page load. I'm using ports to handle the URL updating & base64 encoding/decoding, and Json.Encode/Decode in Elm to get the state. My AppState type currently consists on a single property of type "Board.Model". A board has properties of type "List Box.Model", "List Connection" and "Int". "Box.Model" has 9 properties on it of various types and "Connection.Model" has a few nested properties.

For serialization I went through every model and added a function called "encode" that takes a Model and returns a Json.Encode.Value. I have that working quite nicely and have moved on to deserializing. Now I'm running into a problem writing a decoder for "Box.Model", because it has 9 properties of differing types, but Json.Decode only contains up to object8. I've tried a few approaches to get what I need (I don't really want to implement my own Native code for this) and it just feels like I'm overcomplicating the problem.

Is this the sane approach to serializing/deserializing my object graph? If so, then how do I deal with a wide object like "Box.Model" with the limited options in Json.Decode?

Thanks!

Aaron VonderHaar

unread,
Jan 3, 2015, 2:07:58 AM1/3/15
to elm-d...@googlegroups.com
I don't have time to try this out at the moment, but I think you can
do something like this using `Json.Decode.andThen` to chain on more
attributes:

-- rough, untried code follows:

myDecoder : Decoder Box.Model
myDecoder =
object8 (,,,,,,,) ("a" := aDecoder) ... ("h" := hDecoder)
`andThen`
part2

part2 : (a,b,c,d,e,f,g,h) -> Decoder Box.Model
part2 (a,b,c,d,e,f,g,h) = object1 (Box.Model a b c d e f g h) ("i" := i)

(and also, you may want to consider if now is the time to make your
object graph skinnier and deeper)

Cheers,
--Aaron V.
> --
> You received this message because you are subscribed to the Google Groups
> "Elm Discuss" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to elm-discuss...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.



--
--Aaron V.
[ http://github.com/avh4 ]

Joe Fiorini

unread,
Jan 3, 2015, 1:47:40 PM1/3/15
to elm-d...@googlegroups.com
Thanks Aaron, I'll definitely try that out. I do want to refactor the object model but now is not the time for such a big change.

Evan Czaplicki

unread,
Jan 4, 2015, 12:54:34 AM1/4/15
to elm-d...@googlegroups.com
Hmm, I guess this would happen eventually. The scalable solution is something like this:

apply : Decoder (a -> b) -> Decoder a -> Decoder b
apply func value =
    map2 (<|) func value

myThing : Decoder Thing
    map Thing d1
        `apply` d2
        `apply` d3
        `apply` d4
        `apply` d5
        `apply` d6
        `apply` d7
        `apply` d8
        `apply` d9

You can chain this for as long as you want. I did not add it to the library because I was not sure what the right name was, but now that someone needs it, seems like it's a good idea to decide.


On Sat, Jan 3, 2015 at 7:47 PM, Joe Fiorini <j...@joefiorini.com> wrote:
Thanks Aaron, I'll definitely try that out. I do want to refactor the object model but now is not the time for such a big change.

Max Goldstein

unread,
Jan 4, 2015, 10:30:07 AM1/4/15
to elm-d...@googlegroups.com
I think map, apply, and andThen are good names that we should use consistently. (We talked about adding Signal.apply maybe a week ago?)

Joe Fiorini

unread,
Jan 4, 2015, 3:14:51 PM1/4/15
to elm-d...@googlegroups.com

apply : Decoder (a -> b) -> Decoder a -> Decoder b
apply func value =
    map2 (<|) func value

Are you referring to Json.Decode.map2 here? I don't think such a function exists (at least, I don't see it in the docs at http://package.elm-lang.org/packages/elm-lang/core/1.0.0/Json-Decode).

Joe Fiorini

unread,
Jan 4, 2015, 3:20:42 PM1/4/15
to elm-d...@googlegroups.com
I looked at the source and for now it appears I can use Native.Json.decodeObject2. I'll add map2 - map5 and send a PR.

Paul Chiusano

unread,
Jan 4, 2015, 6:11:56 PM1/4/15
to elm-d...@googlegroups.com
Fyi before you prep that PR, object2 IS map2. :) I dont agree with the reasoning, but I believe the claim was that calling it object2 was more intuitive or something.

Paul :)

Evan Czaplicki

unread,
Jan 5, 2015, 4:06:58 AM1/5/15
to elm-d...@googlegroups.com
Yeah, the objectN series is the same as mapN. I do think it is easier in real life, I did not have to hit this person with tons of concepts for them to do some simple decoding.

Joe, do you have feelings on this choice?

On Mon, Jan 5, 2015 at 12:11 AM, Paul Chiusano <paul.c...@gmail.com> wrote:
Fyi before you prep that PR, object2 IS map2. :) I dont agree with the reasoning, but I believe the claim was that calling it object2 was more intuitive or something.

Paul :)

Rehno Lindeque

unread,
Jan 5, 2015, 4:59:15 AM1/5/15
to elm-d...@googlegroups.com
By the way, I have `andApply` in elm-json-extra. It looks like Evan would like to standardize on `apply`, so I probably need to update the package (?). I also abused andThen for my implementation, map2 is probably better!

James MacAulay

unread,
Aug 9, 2015, 7:48:11 PM8/9/15
to Elm Discuss
I found this thread while searching how to get past the object8 limit, and was happy to find the `apply` solution, thanks Evan!

I wanted to see how I could tweak the `apply` interface to make it a bit more readable/intuitive for me. It took a while for me to figure out how the function works, but that investigation ended up being a great practical lesson for me on applicatives. I never really understood how applicatives worked until I started messing around with Json.Decode. In the end I didn't change the interface much, all I really needed was Applicative's pure:

import Json.Decode as Js exposing ((:=))

userDecoder : Js.Decoder User
userDecoder =
  let
    -- Applicative's `pure`:
    constructing = Js.succeed
    -- Applicative's `<*>`:
    apply = Js.object2 (<|)
  in
    constructing User
      `apply ` ("id" := Js.int)
      `apply ` ("name" := Js.string)
      `apply ` ("email" := Js.string)
      `apply ` ("created_at" := Js.customDecoder Js.string Date.fromString)

James
Reply all
Reply to author
Forward
0 new messages