Hi!
I use PostgREST on the backend, and I have a two-level relationship that I return from an RPC function:
party has many addresses
The structure I’d like to return is:
{"name":"Mac and sons",
"kind":"customer",
"slug":"mac-and-sons",
"addresses":[
{"name":"principal",
"address":"500 some St"}
]
}
I created an RPC because not all parties have addresses, hence I need a LEFT JOIN, which PostgREST does not support in the URL query language.
The issue I have is when a party doesn’t have any addresses. In that case, the returned JSON looks like this:
{"name":"Mac and sons",
"kind":"customer",
"slug":"mac-and-sons",
"addresses":[null]
}
Notice the stray null
in the addresses. This is due to my use of an array_agg
in the backend query in PostgreSQL. array_agg
is defined to return an array with a single null
element in it when there are no rows to aggregate.
I know that I can probably find a way around this in PostgreSQL itself, but because I’m a beginner Elm coder, I’d like to solve the problem in Elm-land. My types and decoders look like this:
type alias PartyAddress =
{ name : String
, address : String
}
type alias FullParty =
{ slug : Slug
, name : String
, kind : PartyKind
, addresses : List PartyAddress
}
partyKindDecoder =
let
mapper s =
case s of
"customer" ->
Customer
"supplier" ->
Supplier
"group" ->
Group
"troop" ->
Troop
_ ->
Customer
in
Decode.map mapper Decode.string
partyAddressDecoder =
Decode.map2 PartyAddress
(Decode.field "name" Decode.string)
(Decode.field "address" Decode.string)
completePartyDecoder =
Decode.map4 FullParty
(Decode.field "slug" Decode.string)
(Decode.field "name" Decode.string)
(Decode.field "kind" partyKindDecoder)
(Decode.field "addresses" (Decode.list partyAddressDecoder))
getParty : Maybe Token -> Slug -> Cmd Msg
getParty maybeToken slug =
Http.request
{ method = "GET"
, url = Builder.absolute [ "api", "rpc", "edit_party" ] [ Builder.string "slug" slug ]
, body = Http.emptyBody
, expect = Http.expectJson CompletePartyLoaded completePartyDecoder
, headers = buildHeadersForOne maybeToken
, timeout = Nothing
, tracker = Nothing
}
Elm correctly complains that it can’t parse the addresses
field:
Problem with the value at json.addresses[0]:
null
Expecting an OBJECT with a field named `name`
Json.Decode.list
has signature Decoder a -> Decoder (List a)
. It looks like I need to write a function like Decoder (List a) -> Decoder (List a)
, but I don’t know how to do that. Looking at the signature of andThen
, (a -> Decoder b) -> Decoder a -> Decoder b
, it looks like I should be using it, but I don’t know how to assemble the pieces to make Elm do what I want.
Elm - How to decode a json list of objects into a Dict seems tantalizingly close, is a more complex example, but I don’t have the expertise to understand exactly how it works. I see how the pieces are assembled, but not how to build such pieces myself.
JSON decoding in Elm, explained step by step has an example (near the bottom) where the 1st parameter of map3
is replaced by a function, and work can be done within the function.
Given this article, I tried the following:
fullPartyAssembler : Slug -> String -> PartyKind -> List PartyAddress -> FullParty
fullPartyAssembler slug name kind addresses =
FullParty
{ slug = slug
, name = name
, kind = kind
, addresses = List.filter (\x -> x) addresses
}
completePartyDecoder =
Decode.map4 fullPartyAssembler
(Decode.field "slug" Decode.string)
(Decode.field "name" Decode.string)
(Decode.field "kind" partyKindDecoder)
(Decode.field "addresses" (Decode.list partyAddressDecoder))
but this gives me errors:
Something is off with the body of the `fullPartyAssembler` definition:
859|> FullParty
860|> { slug = slug
861|> , name = name
862|> , kind = kind
863|> , addresses = List.filter (\x -> x) addresses
864|> }
This `FullParty` call produces:
String -> PartyKind -> List PartyAddress -> FullParty
But the type annotation on `fullPartyAssembler` says it should be:
FullParty
What would be the correct way to implement such a filter?