Maximilian Goldacker
Maximilian Goldacker

Reputation: 53

Parsing JSON in Elm

I want to parse a JSON like this:

{ "shapes":[
{
    "shape": "circle",
    "x": "50",
    "y": "50",
    "r": "40"
},
{
    "shape": "rect",
    "x": "100",
    "y": "100",
    "width": "50",
    "height": "60"
},
]}

and this is my Elm code:

type alias Model =
    { input : Shape
    , saved : List Shape
    , modal1 : String
    , modal2 : String
    , modal3 : String
    , region : String
    }

type Shape
    = Circle String String String
    | Rectangle String String String String

type Msg
    = LoadJson
    | JsonLoaded (Result Http.Error (List Shape))

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of

    LoadJson -> 
        (model, getJson)

    JsonLoaded (Ok shapes) -> 
        ({ model | saved = shapes }, Cmd.none)

    JsonLoaded (Err _) ->
        (model, Cmd.none)

getJson =
    let
        url =
          "http://www.mywebsite.de/shapes.json"
    in
        Http.send JsonLoaded (Http.get url decoder)

decoder = 
    at ["shapes"] (list shapeDecoder)

shapeDecoder = 
    decode func 
        |> required "shape" string
        |> required "x" string
        |> required "y" string
        |> optional "r" string ""
        |> optional "width" string ""
        |> optional "height" string ""

func shape x y r width height = 
    case shape of
        "circle" ->
            Circle x y r

        "rect" ->
            Rectangle x y width height

        _ -> 
            Circle "" "" ""

All shapes in the list which is called "saved" are supposed to be listed in a table.

If I write some elements in this list, they are shown in the table but not if I'm trying to get them from my JSON. Something has to be wrong with my decoder but I don't know what.

Thank you for your help.

Upvotes: 4

Views: 561

Answers (2)

Maximilian Goldacker
Maximilian Goldacker

Reputation: 53

Here is my working decoder which can now parse polygons:

module Main exposing (main)

import Html exposing (Html, text)
import Json.Decode exposing (decodeString, string, list, at)
import Json.Decode.Pipeline exposing (decode, required, optional)

jsonString =
"{\"shapes\":[\n{\n\"shape\": \"circle\",\n \"x\": \"50\",\n \"y\": \"50\",\n \"r\": \"40\"\n},\n{\n\"shape\": \"rect\",\n \"x\": \"100\",\n \"y\": \"100\",\n \"width\": \"50\",\n \"height\": \"60\"\n},\n{\n\"shape\": \"polygon\",\n\"points\":[\n{\n\"x\": \"80\",\n\"y\": \"80\"\n}\n]}\n]}"

main : Html msg
main =
    text <| toString <| decodeString decoder jsonString


type Shape
    = Circle String String String
    | Rectangle String String String String
    | Polygon (List ( ( String, String ) ))


decoder = 
    at ["shapes"] (list shapeDecoder)

shapeDecoder = 
    decode toShape
        |> required "shape" string
        |> optional "x" string ""
        |> optional "y" string ""
        |> optional "r" string ""
        |> optional "width" string ""
        |> optional "height" string ""
        |> optional "points" (list pointDecoder) []

pointDecoder = 
    decode toPoint
        |> required "x" string
        |> required "y" string

toShape shape x y r width height points = 
    case shape of
        "circle" ->
            Circle x y r

        "rect" ->
            Rectangle x y width height

        "polygon" -> 
            Polygon points

        _ -> 
            Circle "" "" ""

toPoint x y = 
    (x, y)

The Json looks like this:

{ "shapes":[
{
    "shape": "circle",
    "x": "50",
    "y": "50",
    "r": "40"
},
{
    "shape": "circle",
    "x": "300",
    "y": "50",
    "r": "40"
},
{
    "shape": "rect",
    "x": "100",
    "y": "100",
    "width": "50",
    "height": "60"
},
{
"shape": "polygon",
"points":
  [ { "x":"200", "y":"150"}
    , { "x":"250", "y":"400"}
    , { "x":"120", "y":"340"}
    , { "x":"160", "y":"250"}
    , { "x":"180", "y":"170"}
  ]
}
]
}

You can try it here: Elli-App

Upvotes: 1

francium
francium

Reputation: 2480

You can simplify your decoder to this. Using the oneOf we can try decoding the json against a decoder for each shape. I can't run this against your website because that's not a real website, but you can run a demo of this decoder here https://ellie-app.com/cFrSPbrYna1/0

Running this decoder against the json you provided, you'd get

Ok ([Circle "50" "50" "40",Rectangle "100" "100" "50" "60"])

Decoder:

import Json.Decode as Decode

decoder =
    Decode.at [ "shapes" ] (Decode.list decodeShape)


decodeShape =
    Decode.oneOf [ circleDecode, rectDecode ]


circleDecode =
    Decode.map4
        circle
        (Decode.field "shape" Decode.string)
        (Decode.field "x" Decode.string)
        (Decode.field "y" Decode.string)
        (Decode.field "r" Decode.string)


rectDecode =
    Decode.map5
        rect
        (Decode.field "shape" Decode.string)
        (Decode.field "x" Decode.string)
        (Decode.field "y" Decode.string)
        (Decode.field "width" Decode.string)
        (Decode.field "height" Decode.string)


circle shape x y r =
    -- Here you can, if you wanted to, have an assertion that the
    -- `shape` is indeed a "circle" and not something else
    -- In which case you probably want to return something like
    -- `Maybe Shape` or `MaybeCircle` instead of just a Circle.
    -- Same thing for the `rect` function below
    Circle x y r


rect shape x y w h =
    Rectangle x y w h

Upvotes: 5

Related Questions