Jak zapytać o wszystkie pola typu GraphQL bez pisania długiego zapytania?

132

Załóżmy, że masz typ GraphQL i zawiera on wiele pól. Jak sprawdzić wszystkie pola bez zapisywania długiego zapytania zawierającego nazwy wszystkich pól?

Na przykład, jeśli mam te pola:

 public function fields()
    {
        return [
            'id' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'The id of the user'
            ],
            'username' => [
                'type' => Type::string(),
                'description' => 'The email of user'
            ], 
             'count' => [
                'type' => Type::int(),
                'description' => 'login count for the user'
            ]

        ];
    }

Aby zapytać o wszystkie pola, zwykle zapytanie wygląda mniej więcej tak:

FetchUsers{users(id:"2"){id,username,count}}

Ale chcę mieć takie same wyniki bez wpisywania wszystkich pól, coś takiego:

FetchUsers{users(id:"2"){*}}
//or
FetchUsers{users(id:"2")}

Czy jest na to sposób w GraphQL?

Używam Folkloreatelier / laravel-graphql bibliotekę.

BlackSigma
źródło
4
Pytasz, jak zrobić coś, czego GraphQL z założenia nie obsługuje.
Travis Webb
12
Po prostu wpisz te 40 pól coś i miej nadzieję, że nie popełnisz błędu :)
Ska
33
Wow, dopiero zaczynam w GraphQL, a to jest poważny WTF.
user949300
1
To ma sens, że nie jest obsługiwane. Wyobraź sobie, że masz obiekty Ucznia i Klasy, uczeń ma „klasy”, które zawierają listę wszystkich zajęć, do których uczęszcza, a klasa ma pole „uczniowie”, które zawiera listę wszystkich uczniów uczęszczających na te zajęcia. To jest cykliczna struktura. Teraz, jeśli poprosisz o wszystkich uczniów ze wszystkimi dziedzinami, czy obejmie to również wszystkie zwrócone pola zajęć? A te klasy mają uczniów, czy ich kierunki też byłyby uwzględnione? A uczniowie mają zajęcia, ...
Buksy
Miałem to pytanie i było tak, że mogłem zobaczyć, co było w ogóle do wyciągnięcia. Wiele klientów GraphQL (np. GraphiQL, patrz gatsbyjs.org/docs/running-queries-with-graphiql ) ma eksplorator schematów, który wykorzystuje introspekcję, aby przedstawić ci, co możesz wyciągnąć, jeśli to jest powód, dla którego chcesz uzyskać „wszystko” ”.
James

Odpowiedzi:

122

Niestety to, co chciałbyś zrobić, nie jest możliwe. GraphQL wymaga, abyś wyraźnie określił, które pola mają być zwracane z zapytania.

Peter Horne
źródło
5
Ok, a jeśli zażądam od zaplecza jakiegoś obiektu o nieznanym formularzu, który mam proxy lub odesłać?
meandre
19
@meandre, cała idea graphql polega na tym, że nie ma czegoś takiego jak „nieznana forma”.
s.meijer
2
@meandre, moja odpowiedź poniżej może być dla Ciebie przydatna?
Tyrone Wilson
Czy to nie jest cała idea większości języków i protokołów zapytań API ?, @meandre
Clijsters
ciekawe, to naprawdę inne nastawienie podczas korzystania z graphql
andy mccullough
91

Tak, możesz to zrobić za pomocą introspekcji . Utwórz zapytanie GraphQL, takie jak (dla typu UserType )

{
   __type(name:"UserType") {
      fields {
         name
         description
      }  
   }
}

a otrzymasz odpowiedź taką jak (rzeczywiste nazwy pól będą zależeć od aktualnej definicji schematu / typu)

{
  "data": {
    "__type": {
      "fields": [
        {
          "name": "id",
          "description": ""
        },
        {
          "name": "username",
          "description": "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
        },
        {
          "name": "firstName",
          "description": ""
        },
        {
          "name": "lastName",
          "description": ""
        },
        {
         "name": "email",
          "description": ""
        },
        ( etc. etc. ...)
      ]
    }
  }
}

Następnie możesz przeczytać tę listę pól w swoim kliencie i dynamicznie utworzyć drugie zapytanie GraphQL, aby uzyskać wszystkie te pola.

Zależy to od tego, czy znasz nazwę typu, dla którego chcesz uzyskać pola - jeśli nie znasz typu, możesz zebrać wszystkie typy i pola razem za pomocą introspekcji, np.

{
  __schema {
    types {
      name
      fields {
        name
        description
      }
    }
  }
}

UWAGA: to są dane GraphQL over-the-wire - jesteś sam, aby dowiedzieć się, jak czytać i pisać z rzeczywistym klientem. Twoja biblioteka GraphQL javascript może już w pewnym stopniu wykorzystywać introspekcję, na przykład polecenie apollo codegen używa introspekcji do generowania typów.

Mark Chackerian
źródło
Wygląda na to, że należy zwracać uwagę na typy rekurencyjne. Jeśli zszedłeś w dół drzewa i natknąłeś się na typ, który zawiera siebie w jakiejś formie (lista, pojedyncza lub inna ...), możesz liczyć na nieskończoną rekursję.
Milos Grujic
W rzeczywistości tak się nie dzieje w moim doświadczeniu z tym konkretnym zapytaniem - samo zapytanie definiuje głębokość rozwiązania.
Mark Chackerian
Powyższa odpowiedź pozwala jedynie na zapytanie o typy pól dostępne w zapytaniu. Nie zwraca wszystkich „wartości” pól obiektu, o co chodzi w pierwotnym pytaniu.
quantdaddy
4
Zgodnie z odpowiedzią musisz dynamicznie zbudować drugie zapytanie na podstawie wyników pierwszego zapytania - zostawiłem to jako ćwiczenie dla czytelnika.
Mark Chackerian
39

Myślę, że jedynym sposobem na to jest wykorzystanie fragmentów wielokrotnego użytku:

fragment UserFragment on Users {
    id
    username
    count
} 

FetchUsers {
    users(id: "2") {
        ...UserFragment
    }
}
tommy
źródło
19
Jeśli to zrobiłem, to i tak muszę pisać nazwę każdego pola „przynajmniej we fragmencie”, czego starałem się uniknąć, wygląda na to, że GraphQL zmusza nas do wyrażenia się wprost.
BlackSigma
jak to dodać w zapytaniu POSTMan? lub jQuery / UI framwork, aby utworzyć stringified JSON. Ten GraphiQL wydaje się bezużyteczny w rzeczywistym celu programistycznym.
mfaisalhyder
Służy to wyłącznie do ponownego wykorzystania.
Henok Tesfaye
@BlackSigma Biorąc pod uwagę dokumentację GraphQL , powinna to być akceptowana jako najlepsza odpowiedź
JP Ventura
4
@JPVentura: Nie, przyjacielu, istnieje różnica między możliwością ponownego wykorzystania a symbolami wieloznacznymi, zarówno w koncepcji, jak i zastosowaniu. Cel fragmentu jest jasny w dokumentacji „GraphQL zawiera jednostki wielokrotnego użytku zwane fragmentami”. Użycie fragmentu jest przydatne, ale nie jest odpowiedzią na pytanie.
BlackSigma
11

Napotkałem ten sam problem, gdy musiałem załadować dane o lokalizacji, które zserializowałem do bazy danych z Google Places API. Generalnie chciałbym mieć całość, aby działała z mapami, ale nie chciałem za każdym razem określać wszystkich pól.

Pracowałem w Rubim, więc nie mogę dać Ci implementacji PHP, ale zasada powinna być taka sama.

Zdefiniowałem niestandardowy typ skalarny o nazwie JSON, który po prostu zwraca dosłowny obiekt JSON.

Implementacja ruby ​​była taka (używając graphql-ruby)

module Graph
  module Types
    JsonType = GraphQL::ScalarType.define do
      name "JSON"
      coerce_input -> (x) { x }
      coerce_result -> (x) { x }
    end
  end
end

Potem użyłem go do naszych obiektów

field :location, Types::JsonType

Używałbym tego jednak bardzo oszczędnie, używając go tylko wtedy, gdy wiesz, że zawsze potrzebujesz całego obiektu JSON (tak jak w moim przypadku). W przeciwnym razie, mówiąc bardziej ogólnie, pokonuje obiekt GraphQL.

Tyrone Wilson
źródło
1
Właśnie tego potrzebowałem, dziękuję. Mój przypadek użycia jest taki, że w całym systemie mam ciągi do tłumaczenia przez użytkownika i są one przechowywane jako json w podobnej bazie danych {"en": "Hello", "es": "Hola"}. A ponieważ każdy użytkownik może zaimplementować własny podzbiór języków dla swojego przypadku użycia, nie ma sensu, aby interfejs użytkownika sprawdzał każdy możliwy podzbiór. Twój przykład działa doskonale.
Luke Ehresman
2

Format zapytań GraphQL został zaprojektowany, aby umożliwić:

  1. Kształt zapytania i wyniku jest dokładnie taki sam .
  2. Serwer dokładnie zna wymagane pola, dlatego klient pobiera tylko niezbędne dane.

Jednak zgodnie z dokumentacją GraphQL , możesz tworzyć fragmenty , aby ułatwić wielokrotne użycie zestawów wskazań :

# Only most used selection properties

fragment UserDetails on User {
    id,
    username
} 

Następnie możesz zapytać o wszystkie szczegóły użytkownika poprzez:

FetchUsers {
    users() {
        ...UserDetails
    }
}

Możesz również dodać dodatkowe pola obok swojego fragmentu :

FetchUserById($id: ID!) {
    users(id: $id) {
        ...UserDetails
        count
    }
}
JP Ventura
źródło