Decoder headache.

79 views
Skip to first unread message

Rupert Smith

unread,
Dec 21, 2016, 7:38:14 AM12/21/16
to Elm Discuss
Struggling to figure this out, but it may be because I'm in an open plan office and its just too noisy to concentrate...

Suppose I have decoders for two records:

type alias Circle =  { radius : Float }
type alias Rectangle = { width: Float, height: Float }

On the server side though, these are actually a class hierarchy, with

Circle <: Shape
Rectangle <: Shape

Where <: means 'subtype of'.

When serializing over json, a discriminator field is added, so the json looks like:

{
  @type : "Circle",
  radius : ...
}

{
  @type : "Rectangle",
  width : ...
}

I have decoders for circles and rectangles already defined:

circleDecoder : Decoder Circle
rectangleDecoder : Decoder Rectangle

and am now trying to write the decoder for Shape:

type Shape = 
   CircleAsShape Circle
 | RectangleAsShape Rectangle

shapeDecoder : Decoder Shape
shapeDecoder = 
  succeed (some function from '@type' to circle or rectangle decoder mapped to shape decoder)
  |: (field "@type" Decode.string)

Feels about the right form for it. Exception this will return a Decoder (Decoder Shape) instead of a Decoder Shape.

Can someone explain how to do this?

Peter Damoc

unread,
Dec 21, 2016, 8:18:52 AM12/21/16
to Elm Discuss
Isn't this supposed to be implemented like this:

import Html exposing (text)

import Json.Decode exposing (..) 

type alias Circle =  { radius : Float }
type alias Rectangle = { width: Float, height: Float }

type Shape = 
   CircleAsShape Circle
 | RectangleAsShape Rectangle

circleDecoder = 
  map Circle (field "radius" float)

rectangleDecoder = 
  map2 Rectangle 
    (field "width" float)
    (field "height" float) 
    
shapeDecoder = 
  field "@type" string
    |> andThen toShape

toShape str =
  case str of
    "Circle" -> 
      map CircleAsShape circleDecoder
    "Rectangle" -> 
      map RectangleAsShape rectangleDecoder
    _ -> 
      fail ("unknown shape: " ++ str) 
    

rawJSON = """
[ { "@type" : "Circle", "radius": 3}
, { "@type" : "Rectangle", "width" : 1, "height": 2 }
]
"""


main =
  text <| toString <| decodeString  (list shapeDecoder) rawJSON



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



--
There is NO FATE, we are the creators.
blog: http://damoc.ro/

Simon

unread,
Dec 21, 2016, 8:19:43 AM12/21/16
to Elm Discuss

Rupert Smith

unread,
Dec 21, 2016, 8:53:56 AM12/21/16
to Elm Discuss
On Wednesday, December 21, 2016 at 1:18:52 PM UTC, Peter Damoc wrote:   
shapeDecoder = 
  field "@type" string
    |> andThen toShape

toShape str =
  case str of
    "Circle" -> 
      map CircleAsShape circleDecoder
    "Rectangle" -> 
      map RectangleAsShape rectangleDecoder
    _ -> 
      fail ("unknown shape: " ++ str) 

Thanks guys. :-) 

Brian Hicks

unread,
Dec 21, 2016, 9:44:13 AM12/21/16
to Elm Discuss
A previous version of the `andThen` documentation actually used circles and rectangles as the motivating example. It's since switched to versioned objects, which are maybe a little more realistic. Did you choose circles and rectangles as your example here for that reason, or are you really working with shapes? (I also wrote a blog post about `andThen` that explains that example, but beware the 0.17 syntax.)

If you don't have any reason to separate your `Circle` and `Rectangle` types, you might also consider defining them in `Shape` directly. That way `CircleAsShape` would become `Circle`.

Rupert Smith

unread,
Dec 21, 2016, 9:53:52 AM12/21/16
to Elm Discuss
On Wednesday, December 21, 2016 at 2:44:13 PM UTC, Brian Hicks wrote:
A previous version of the `andThen` documentation actually used circles and rectangles as the motivating example. It's since switched to versioned objects, which are maybe a little more realistic. Did you choose circles and rectangles as your example here for that reason, or are you really working with shapes? (I also wrote a blog post about `andThen` that explains that example, but beware the 0.17 syntax.)

If you don't have any reason to separate your `Circle` and `Rectangle` types, you might also consider defining them in `Shape` directly. That way `CircleAsShape` would become `Circle`.

No I am not really working with Circles and Rectangles. I am working with a data model and writing a code generator for it that automatically writes the Elm Encoder and Decoder for me. Fields can be optional, there can be recursion and there can be mutual recursion and their can be sub-typing. So I have to write these encoders and decoders fairly defensively to allow for the wide range of possible json they may encounter.

I did think about using Json.Decode.oneOf, but that depends on the order of testing the various subtypes, as one may have a set of fields that is a superset of another. I would have to do a topological sort over the partial super-set relationship amongst their fields. Even that might not work as with fields being optional, or two fields with the same name but different type, their might be no practical way to tell apart two different sub-types without a discriminator field.

I had not seen your helpful example at:


only one on stack overflow that advocated using the oneOf approach.

Brian Hicks

unread,
Dec 21, 2016, 9:58:10 AM12/21/16
to Rupert Smith

In the simpler cases you can use oneOf with the subtypes first, but I get your point. Plus, andThen will be much faster. ;)

--
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/0I25pg51rK8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages