Formerly ryannhg/graphql. Search finds this package by either name.
When I first got started with JSON decoders, I had a hard time visualizing what was going on.
For that reason, this module comes with a test function to help you
easily verify your code is behaving the way you expect.
This is inspired by the elm/json package's decodeString
function, which is great for testing JSON decoders when you get stuck!
This function is exposed so you can quickly check if your GraphQL Decoder works as you expect
with a raw JSON string.
import GraphQL.Decode
testResult : Result Http.Error Int
testResult =
GraphQL.Decode.test
{ decoder = GraphQL.Decode.int
, json = """ 1000 """"
}A decoder for the built-in String scalar.
import GraphQL.Decode
GraphQL.Decode.test
{ decoder = GraphQL.Decode.string
, json = """ "Hello, world!" """
}
== Ok "Hello, world!"A decoder for the built-in Float scalar.
import GraphQL.Decode
GraphQL.Decode.test
{ decoder = GraphQL.Decode.float
, json = """ 1.25 """
}
== Ok 1.25A decoder for the built-in Int scalar.
import GraphQL.Decode
GraphQL.Decode.test
{ decoder = GraphQL.Decode.int
, json = """ 9000 """
}
== Ok 9000A decoder for the built-in Boolean scalar.
import GraphQL.Decode
GraphQL.Decode.test
{ decoder = GraphQL.Decode.bool
, json = """ true """
}
== Ok TrueA decoder for the built-in ID scalar.
Uses GraphQL.Scalar.Id as a way to prevent
mixing up String and Id values.
import GraphQL.Decode
-- Works with JSON strings
GraphQL.Decode.test
{ decoder = GraphQL.Decode.id
, json = """ "abc" """
}
== Ok (Id "abc")
-- Works with JSON numbers
test
{ decoder = id
, json = """ 12345 """
}
== Ok (Id "12345")GraphQL allows APIs to define custom scalar values, like Date or Currency. This function
allows you to work with those custom values.
Here's an example of using this function to work with a DateTime type. We recommend defining each custom scalar
in a separate Elm module, like GraphQL.Scalar.DateTime:
-- module GraphQL.Scalar.DateTime exposing
-- ( DateTime
-- , decoder, encode
-- )
import GraphQL.Decode exposing (Decoder)
import Iso8601
import Time
type alias DateTime =
Time.Posix
decoder : Decoder DateTime
decoder =
GraphQL.Decode.scalar Iso8601.decoderThe snippet above uses elm/time and rtfeldman/elm-iso8601-date-strings
to convert a String value from our API into a Time.Posix value.
import GraphQL.Decode
import GraphQL.Scalar.DateTime
GraphQL.Decode.test
{ decoder = GraphQL.Scalar.DateTime.decoder
, value = """ "2022-12-26T01:45:56.520Z" """
}
== Ok (Posix 1672019156520)A decoder for handling enum values. It allows you to specify a list of allowed enums that can come back with a certain request.
To prevent code duplication, we recommend defining one decoder for each enum in a separate Elm module,
like GraphQL.Enum.Episode. Here's how you can use this function to define a decoder for
the Episode enum type:
import GraphQL.Decode exposing (Decoder)
type Episode
= NewHope
| EmpireStrikesBack
| ReturnOfTheJedi
decoder : Decoder Episode
decoder =
GraphQL.Decode.enum
[ ( "NEWHOPE", NewHope )
, ( "EMPIRE", EmpireStrikesBack )
, ( "RETURN", ReturnOfTheJedi )
]Note: If an enum is missing from that list, this package will return a JSON decoding error letting you know which enum variant was missing.
A decoder for working with object types.
This function works in conjunction with field to allow you to safely decode one field at a time.
import GraphQL.Decode exposing (Decoder)
type alias Person =
{ id : Id
, fullName : String
}
decoder : Decoder Person
decoder =
GraphQL.Decode.object Person
|> GraphQL.Decode.field
{ name = "id"
, decoder = GraphQL.Decode.id
}
|> GraphQL.Decode.field
{ name = "fullName"
, decoder = GraphQL.Decode.string
}When used with object, this function allows you to
add fields to an object.
Note: The order of the fields in the type alias need to match
the order you add the fields in your decoder.
import GraphQL.Decode exposing (Decoder)
import GraphQL.Enum.Episode
type alias Jedi =
{ name : String
, appearsIn : List GraphQL.Enum.Episode
}
decoder : Decoder Person
decoder =
GraphQL.Decode.object Person
|> GraphQL.Decode.field
{ name = "name"
, decoder = GraphQL.Decode.string
}
|> GraphQL.Decode.field
{ name = "appearsIn"
, decoder =
GraphQL.Decode.list
GraphQL.Enum.Episode.decoder
}Use this function to tell an existing decoder that we expect a nullable value, which might not guaranteed to be available:
import GraphQL.Decode exposing (Decoder)
stringDecoder : Decoder String
stringDecoder =
GraphQL.Decoder.string
nullableStringDecoder : Decoder (Maybe String)
nullableStringDecoder =
GraphQL.Decoder.maybe GraphQL.Decoder.stringUse this function to tell an existing decoder that we expect a list of values, rather than a single value:
import GraphQL.Decode exposing (Decoder)
stringDecoder : Decoder String
stringDecoder =
GraphQL.Decoder.string
listOfStringDecoder : Decoder (List String)
listOfStringDecoder =
GraphQL.Decoder.list GraphQL.Decoder.stringInterfaces and union type decoders require the __typename field in the selection. Without it,
Elm won't be able to match on the typename provided.
Here's an example from the official GraphQL documentation:
{
search(text: "an") {
__typename
... on Human {
name
height
}
... on Droid {
name
primaryFunction
}
... on Starship {
name
length
}
}
}Because the hero query returns an interface, you should always include __typename in your selection set.
This will allow your Elm frontend to know which types came back:
{
"data": {
"search": [
{
"__typename": "Human",
"name": "Han Solo",
"height": 1.8
},
{
"__typename": "Human",
"name": "Leia Organa",
"height": 1.5
},
{
"__typename": "Starship",
"name": "TIE Advanced x1",
"length": 9.2
}
]
}
}Decoder for a union type. Use this with the variant function to select variants by typename:
import GraphQL.Decode exposing (Decoder)
type SearchUnion
= On_Human Human
| On_Droid Droid
| On_Starship Starship
decoder : Decoder SearchUnion
decoder =
GraphQL.Decode.union
[ GraphQL.Decode.variant
{ typename = "Human"
, onVariant = On_Human
, decoder = humanDecoder
}
, GraphQL.Decode.variant
{ typename = "Droid"
, onVariant = On_Droid
, decoder = droidDecoder
}
, GraphQL.Decode.variant
{ typename = "Starship"
, onVariant = On_Starship
, decoder = starshipDecoder
}
]Each variant will expect a decoder for the data you expect. This will involve using
the object and field functions in this module:
type alias Human =
{ name : String
, height : Int
}
humanDecoder : Decoder Human
humanDecoder =
GraphQL.Decode.object Human
|> GraphQL.Decode.field
{ name = "name"
, decoder = GraphQL.Decode.string
}
|> GraphQL.Decode.field
{ name = "height"
, decoder = GraphQL.Decode.int
}
type alias Droid =
{ name : String
, primaryFunction : String
}
droidDecoder : Decoder Droid
droidDecoder =
GraphQL.Decode.object Droid
|> GraphQL.Decode.field
{ name = "name"
, decoder = GraphQL.Decode.string
}
|> GraphQL.Decode.field
{ name = "primaryFunction"
, decoder = GraphQL.Decode.string
}
type alias Starship =
{ name : String
, length : Int
}
starshipDecoder : Decoder Starship
starshipDecoder =
GraphQL.Decode.object Starship
|> GraphQL.Decode.field
{ name = "name"
, decoder = GraphQL.Decode.string
}
|> GraphQL.Decode.field
{ name = "length"
, decoder = GraphQL.Decode.int
}❗️ See the important note about using __typename to prevent issues
Decoder for interfaces that allow you to select extra fields. This works alongside the variant
function to define which variants you are interested in tracking.
import GraphQL.Decode exposing (Decoder)
type HeroInterface
= On_Human Human
| On_Droid Droid
decoder : Decoder SearchUnion
decoder =
GraphQL.Decode.interface
[ GraphQL.Decode.variant
{ typename = "Human"
, onVariant = On_Human
, decoder = humanDecoder
}
, GraphQL.Decode.variant
{ typename = "Droid"
, onVariant = On_Droid
, decoder = droidDecoder
}
]You can see the union example to understand how to
make humanDecoder or droidDecoder. Their implementations are identical!
❗️ See the important note about using __typename to prevent issues
A function intended to be used with union or interface for
selecting specific custom type variants by typename.
import GraphQL.Decode exposing (Variant)
type HeroInterface
= On_Human Human
| On_Droid Droid
humanVariant : Variant HeroInterface
humanVariant =
GraphQL.Decode.variant
{ typename = "Human"
, onVariant = On_Human
, decoder = humanDecoder
}
droidVariant : Variant HeroInterface
droidVariant =
GraphQL.Decode.variant
{ typename = "Droid"
, onVariant = On_Droid
, decoder = droidDecoder
}These functions are used internally by GraphQL.Http, and you won't need them in your projects.
When you make a GraphQL request, you'll get a JSON response like this one:
This
Decodertype represents a value that knows how to convert a raw JSON response into standard Elm values that can be used in your Elm application.