Как запросить все поля типа GraphQL без написания длинного запроса?

132

Предположим, у вас есть тип GraphQL, который включает много полей. Как запросить все поля, не записывая длинный запрос, включающий имена всех полей?

Например, если у меня есть эти поля:

 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'
            ]

        ];
    }

Чтобы запросить все поля, обычно запрос выглядит примерно так:

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

Но мне нужен способ получить те же результаты без записи всех полей, примерно так:

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

Есть ли способ сделать это в GraphQL ??

Я использую библиотеку Folkloreatelier / laravel-graphql .

BlackSigma
источник
4
Вы спрашиваете, как сделать что-то, что GraphQL по своей задумке не поддерживает.
Трэвис Уэбб,
12
Просто введите эти 40 полей и надейтесь, что вы не сделаете опечатку :)
Ska
33
Вау, я только начинаю работать с GraphQL, и это серьезная чушь.
user949300 08
1
Имеет смысл, что он не поддерживается, представьте, что у вас есть объекты Student и Class, у студента есть поле «classes», в котором перечислены все классы, которые он посещает, у класса есть поле «student», в котором перечислены все ученики, которые посещают этот класс. Это циклическая структура. Теперь, если вы запросите всех студентов со всеми полями, будут ли это также включать все возвращенные поля классов? И в этих классах есть ученики, будут ли включены их поля? А у студентов уроки, ...
Буксы 06
У меня был этот вопрос, и я хотел увидеть, что вообще можно было потянуть. Многие клиенты GraphQL (например, GraphiQL, см. Gatsbyjs.org/docs/running-queries-with-graphiql ) имеют обозреватель схемы, который использует интроспекцию, чтобы представить вам то, что вы можете сделать, если это причина желания получить "все". ».
Джеймс

Ответы:

122

К сожалению, то, что вы хотели бы сделать, невозможно. GraphQL требует, чтобы вы четко указали, какие поля вы хотите вернуть из вашего запроса.

Питер Хорн
источник
5
Хорошо, а если я запрошу какой-то объект неизвестной формы из бэкэнда, который я должен проксировать или отправить обратно?
меандре
19
@meandre, вся идея graphql в том, что не существует такой вещи, как "неизвестная форма".
s.meijer
2
@meandre, мой ответ ниже может быть вам полезен?
Тайрон Уилсон
Разве это не вся идея большинства языков запросов и протоколов API ?, @meandre
Clijsters
интересно, это действительно другое мышление при использовании graphql
Энди Маккалоу
91

Да, вы можете сделать это с помощью самоанализа . Сделайте запрос GraphQL, например (для типа UserType )

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

и вы получите ответ вроде (фактические имена полей будут зависеть от вашего фактического определения схемы / типа)

{
  "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. ...)
      ]
    }
  }
}

Затем вы можете прочитать этот список полей в своем клиенте и динамически построить второй запрос GraphQL, чтобы получить все эти поля.

Это зависит от того, знаете ли вы имя типа, для которого хотите получить поля - если вы не знаете тип, вы можете собрать все типы и поля вместе, используя самоанализ, например

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

ПРИМЕЧАНИЕ: это данные GraphQL, передаваемые по сети - вы сами решаете, как читать и писать с помощью вашего реального клиента. Ваша библиотека javascript graphQL может уже использовать интроспекцию в некоторой степени, например, команда apollo codegen использует интроспекцию для генерации типов.

Марк Чакериан
источник
Похоже, следует позаботиться о рекурсивных типах. Если вы спуститесь вниз по дереву и натолкнетесь на тип, который содержит себя в той или иной форме (список, одиночный или другой ...), вы можете столкнуться с бесконечной рекурсией.
Милош Груич
По моему опыту с этим конкретным запросом, на самом деле этого не происходит - сам запрос определяет глубину разрешения.
Марк Чакериан,
Приведенный выше ответ позволяет запрашивать только типы полей, доступные в запросе. Он не возвращает все «значения» полей объекта, о чем и идет речь в исходном вопросе.
quantdaddy
4
В соответствии с ответом вам необходимо динамически построить второй запрос на основе результатов первого запроса - я оставил это в качестве упражнения для читателя.
Марк Чакериан
39

Думаю, единственный способ сделать это - использовать многократно используемые фрагменты:

fragment UserFragment on Users {
    id
    username
    count
} 

FetchUsers {
    users(id: "2") {
        ...UserFragment
    }
}
Томми
источник
19
Если бы я сделал это, то мне все равно пришлось бы писать каждое имя поля «по крайней мере во фрагменте», хотя я пытался избежать того, чего я пытался избежать, похоже, что GraphQL вынуждает нас быть явными.
BlackSigma
как добавить это в запрос POSTMan? или jquery / UI для создания строкового JSON. Этот graphiQL кажется бесполезным для реальных целей разработки.
mfaisalhyder
Это сделано исключительно для повторного использования.
Хенок Тесфайе
@BlackSigma Учитывая документацию GraphQL , это должно быть принято как лучший ответ
JP Ventura
4
@JPVentura: Нет, друг мой, есть разница между возможностью повторного использования и шаблоном как в концепции, так и в применении. Назначение фрагментов ясно указано в документации: «GraphQL включает многоразовые блоки, называемые фрагментами». Использование фрагмента полезно, но не является ответом на вопрос.
BlackSigma
11

Я столкнулся с той же проблемой, когда мне нужно было загрузить данные о местоположении, которые я сериализовал в базу данных из API Google Places. Как правило, я бы хотел, чтобы все это работало с картами, но мне не хотелось каждый раз указывать все поля.

Я работал с Ruby, поэтому не могу дать вам реализацию PHP, но принцип должен быть таким же.

Я определил настраиваемый скалярный тип JSON, который просто возвращает буквальный объект JSON.

Реализация ruby ​​была такой (с использованием graphql-ruby)

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

Затем я использовал его для наших объектов вот так

field :location, Types::JsonType

Я бы использовал это очень экономно, используя его только там, где вы знаете, что вам всегда нужен весь объект JSON (как я сделал в моем случае). В противном случае это побеждает объект GraphQL в более общем смысле.

Тайрон Уилсон
источник
1
Это именно то, что мне было нужно, спасибо. Мой вариант использования: у меня есть переводимые пользователем строки по всей системе, и они хранятся как json в базе данных {"en": "Hello", "es": "Hola"}. А поскольку каждый пользователь может реализовать собственное подмножество языков для своего варианта использования, пользовательский интерфейс не имеет смысла запрашивать каждое возможное подмножество. Ваш пример отлично работает.
Люк Эресман
2

Формат запроса GraphQL был разработан для того, чтобы позволить:

  1. Форма запроса и результата должна быть одинаковой. .
  2. Сервер точно знает запрошенные поля, поэтому клиент загружает только важные данные.

Однако, согласно документации GraphQL , вы можете создавать фрагменты , чтобы сделать наборы выбора более повторно используемыми:

# Only most used selection properties

fragment UserDetails on User {
    id,
    username
} 

Затем вы можете запросить все сведения о пользователе:

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

Вы также можете добавить дополнительные поля рядом с вашим фрагментом :

FetchUserById($id: ID!) {
    users(id: $id) {
        ...UserDetails
        count
    }
}
JP Ventura
источник