hi everyone so far so good with graphql and Absinthe. Its getting bit tricky with some data structs
here is the example of schema
object :uploads do
field :links, list_of(:links_types)
end
the links_types looks like
object :links_types do
field :url, :string
field :format, :string
end
so the data structure that is fetched from postgresql looks this %{id:1, links: %{data: [ %{"url" => "xyz.com", "format" => "format1" }, %{ "url": "abc.com", format => "format2"]}}
i managed to remove data key from links map , so now links: is list of maps as we described in graphql schema still i get null result
sample query
id,
links{
url
}
}
result is
id:1,
links: [ {url: null}, {url: null}]
}
I think i am making some mistake here or there is something better way to define schema. thanks in advance
Can you elaborate a bit on your data? Normally if you use a jsonb column the keys are strings not atoms. I’m also confused by the single quotes, are you intentionally storing char lists instead of strings?
You’ll likely just need a small inline resolution function on your links field, but in order to suggest one I need to understand the shape of your data better.
@benwilson512 thanks for quick reply. I think the problem is my data structure was map with arrow x = %{"a" => "xyz"}
I thing in the absinthe library we access map as x.a which gave null i think. idn i maybe wrong.
i was thinking to convert arrow map into atoms is there better way to solve this. like some config to access with the arrow maps as well? x["a"]
First off, some elixir terminology. All elixir maps are “arrow maps”. %{foo: 1} is exactly the same as %{:foo => 1}. If the key is an atom you can write it in the %{foo: 1} form, but it’s just a visual change.
%{:foo => 1} is a map with the atom key :foo. %{"foo" => 1} is a map with the string key "foo".
What you’re trying to figure out is how to get values out of a map with string keys. The answer does not need to be converting them to atoms.
You haven’t really supplied enough of your code for me to tell you exactly where to put stuff. For example you just said you created a helper, but you didn’t say where you put that code or what values you gave it (what is list?) so it doesn’t help us. Help us to help you, give us the full context.
The basic way to use string keys is as follows:
query do
field :thing, :thing_with_string_keys do
{:ok, %{"name" => "ben", "age" => 25}}
end
end
object :thing_with_string_keys do
field :name, :string, resolve: key("name")
field :age, :integer, resolve: key("name")
end
def key(key_name) do
fn thing, _, _ ->
{:ok, Map.get(thing, key_name}
end
end
object :permissions do
field :add, list_of(:string) do
resolve(fn(%{"add" => add},_,_) ->
{:ok, add}
end)
end
field :exclude, list_of(:string) do
resolve(fn(%{"exclude" => exclude },_,_) ->
{:ok, exclude}
end)
end
field :roles, list_of(:string) do
resolve(fn(%{"roles" => roles},_,_) ->
{:ok, roles}
end)
end
end
This isn’t what you’re asking, but it is relevant to the title of the thread, so for the benefit of others visiting it, here’s how I handle JSONB fields in Absinthe where the fields are unknown ahead of time (you’ll notice some custom handling for GeoJSON included):
@desc "A JSON document containing more info beyond specified fields"
field(:extra_info, :json)
which relies on this scalar type:
defmodule MyAPI.GraphQL.JSON do
@moduledoc """
The Json scalar type allows arbitrary JSON values to be passed in and out.
Requires `{ :jason, "~> 1.1" }` package: https://github.com/michalmuskala/jason
"""
defstruct data: nil, normalized: nil, raw: nil, errors: [], flags: %{}, open_ended: false
alias Absinthe.Blueprint.Input
use Absinthe.Schema.Notation
require Protocol
scalar :json, name: "Json" do
description("Arbitrary json stored as a string")
serialize(&encode/1)
parse(&decode/1)
end
@spec decode(Input.String.t()) :: {:ok, term} | {:error, term}
@spec decode(Input.Null.t()) :: {:ok, nil}
defp decode(%Input.String{value: value}) do
try do
{:ok, Jason.decode!(value)}
rescue e ->
:error
end
end
defp decode(%Input.Null{}), do: {:ok, nil}
defp decode(_), do: :error
defp encode(%Geo.Point{} = geo) do
with {:ok, geo_json} <- Geo.JSON.encode(geo) do
geo_json
end
end
defp encode(value) when is_struct(value) do
value
end
defp encode(value), do: value
end
Edit: Re-reading it now, I see several ways it can be improved, but it’s worked pretty well for us so far.