How to test JSON decoding when you need a raw Json Value?

159 views
Skip to first unread message

Paul Blair

unread,
Jul 29, 2016, 2:52:32 PM7/29/16
to Elm Discuss
I'm using the nested Elm architecture pattern in an application. The nested components respond to messages coming in on a port. The messages are handled by the top-level component before being handed on to the nested components. So the outer data structure looks something like this:

type alias FromServer =
  { message_type: MessageType
  , data: Json.Decode.Value 
  }

The data field is a Json.Decode.Value because the top-level component doesn't know about the message types defined by the nested components; it does a lookup on the incoming message type and decides to which inner component it will forward the message. So the top level looks like this:

subscriptions : Model.Model -> Sub Message
subscriptions model =
  theIncomingPort toMessage


toMessage : FromServer -> Message
toMessage incoming =
  case Dict.get incoming.message_type componentDictionary of
    Just MyInnerComponent ->
      InnerComponentModule.toMessage incoming |> MyInnerComponentMessageWrapper
    Nothing ->
      ...

The inner component's toMessage function also uses the message_type field to choose the decoder to apply to the Json.Decode.Value in the data field.

I want to test the inner component's toMessage function. This means that I need to create a FromServer record with the data contained in a Json.Decode.Value -- ideally created from a Json string.

However, from what I can tell the only way to create this kind of raw Json.Decode.Value is by passing the string in through a port. This seems like a pretty complicated way to set up a test. Is there some other way to accomplish this?

Paul Blair

unread,
Jul 29, 2016, 3:10:01 PM7/29/16
to Elm Discuss
To answer my own question -- it looks like Native.Json has an identity function which does just what I want, but which I can't import. However, it also looks like many of Json.Encode's functions just call Native.Json.identity. So this works:

data = Encode.string complicatedNestedJson

Paul Blair

unread,
Jul 29, 2016, 3:29:03 PM7/29/16
to Elm Discuss
Spoke too soon; that doesn't work.  For example:

> foo = Json.Encode.string "[]"
"[]" : Json.Encode.Value

> bar = Json.Decode.decodeValue (Json.Decode.list Json.Decode.string)  foo
Err "Expecting a List but instead got: \"[]\""
    : Result.Result String (List String)

Duane Johnson

unread,
Jul 29, 2016, 3:48:01 PM7/29/16
to elm-d...@googlegroups.com

On Fri, Jul 29, 2016 at 12:52 PM, Paul Blair <psfb...@gmail.com> wrote:
toMessage : FromServer -> Message
toMessage incoming =
  case Dict.get incoming.message_type componentDictionary of
    Just MyInnerComponent ->
      InnerComponentModule.toMessage incoming |> MyInnerComponentMessageWrapper
    Nothing ->
      ...


I'm still learning Elm, so forgive that this is not necessarily going to be expert advice :)

My first question is, what is the type of `componentDictionary`? It looks to me like it has `String` type keys, but then I don't understand what the type signature of the value is. `Maybe type`? How can you match on `Just MyInnerComponent`?

Second question: it seems like the top-level component *must* know something about the messages in order to route to subcomponents. Can you include a list of json keys in that routing information, so that you can pass a narrower scope of data into each subcomponent? It seems like if you can, that would make your testing story a lot easier.

Duane

Nick H

unread,
Jul 29, 2016, 3:51:41 PM7/29/16
to elm-d...@googlegroups.com
You are on the right track with looking in the Json.Encode module. What you tried in the REPL is almost correct. Try this instead:

> foo = Json.Encode.list []
> bar = Json.Decode.decodeValue (Json.Decode.list Json.Decode.string)

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

Joey Eremondi

unread,
Jul 29, 2016, 3:53:36 PM7/29/16
to elm-d...@googlegroups.com
To be clear, the problem is that "Json.Encode.string" isn't saying "take the string you give me and parse it into JSON", it's saying "take the string you gave me and make it a JSON String".

Paul Blair

unread,
Jul 29, 2016, 4:25:57 PM7/29/16
to Elm Discuss
Yes, I can see that. What I was looking for is instead a function that says "take the string you give me and give me a Json.Value." 

It looks like my only alternative is instead to create the Json.Value out of a complex nested structure of Json.Encode.object, and corresponding Elm tuples, lists and primitives. This isn't ideal, since what I want to test is the real-world scenario of how my code handles the Json strings that are going to be coming in, rather than how it handles an involved construct that I hope corresponds to those strings.

Paul Blair

unread,
Jul 29, 2016, 4:33:48 PM7/29/16
to Elm Discuss
Duane, the dictionary has string keys; its values are just values of a union type--they are not modules in themselves, but just tags that the case statement can key on in order to decide on which module to send the message to.

On the second point, the top-level component knows enough about the incoming message to get the value from the message_type key. The type of the data field is opaque to the top-level; the top-level just sees it as Json.Value. This does in fact work; it's just hard to test.


--
You received this message because you are subscribed to a topic in the Google Groups "Elm Discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elm-discuss/fwDl6deEC1Q/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss...@googlegroups.com.

Duane Johnson

unread,
Jul 29, 2016, 4:54:02 PM7/29/16
to elm-d...@googlegroups.com
I wonder if the `json-to-elm` project includes what you're looking for.  I see a Types.elm file that includes a toValue function:

toValue : String -> Json.Value
toValue =
    Native.Types.toValue

And in the Native.Types module, a definition of toValue that uses native javascript:

    var toValue = function(text){
        try {
            return JSON.parse(text);
        } catch (e) {
            return {};
        }
    };




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

Paul Blair

unread,
Jul 29, 2016, 4:57:03 PM7/29/16
to elm-d...@googlegroups.com
Thanks, Duane; that looks promising. I'll try that out.

Paul Blair

unread,
Aug 4, 2016, 3:11:29 PM8/4/16
to Elm Discuss
Just wanted to update with an experience report.  I wound up writing a native module myself, since the json-to-elm project doesn't have a published package and the native module was not in the Elm 0.17 format. (It also returns an empty JSON object if there is a parse exception, rather than a Result, which I need.)

Everything worked nicely once I figured out the format for writing native modules (which I understand is subject to change).

To unsubscribe from this group and all its topics, send an email to elm-discuss+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
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+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to a topic in the Google Groups "Elm Discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elm-discuss/fwDl6deEC1Q/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss+unsubscribe@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages