Dicas de desempenho

Este documento abrange algumas técnicas que podem ser usadas para melhorar o desempenho do seu aplicativo. Em alguns casos, exemplos de outras APIs ou de APIs genéricas são usados para ilustrar as ideias apresentadas. No entanto, os mesmos conceitos são aplicáveis à API Google Wallet.

Compactação com o gzip

Uma maneira fácil e prática de reduzir a largura de banda necessária a cada solicitação é ativar a compactação gzip. Embora isso exija mais tempo de CPU para descompactar os resultados, a redução dos custos de rede normalmente faz com que esse método valha muito a pena.

Para receber uma resposta codificada em gzip, é preciso definir um cabeçalho Accept-Encoding e modificar seu user agent para conter a string gzip. Veja um exemplo de cabeçalhos HTTP devidamente formados para permitir a compactação em gzip:

Accept-Encoding: gzip
User-Agent: my program (gzip)

Como trabalhar com recursos parciais

Outra maneira de melhorar o desempenho das chamadas da API é enviar e receber somente a parte dos dados que você quer. Assim, evita-se a transferência, a análise e o armazenamento de campos desnecessários no aplicativo para que recursos como rede, CPU e memória sejam usados de maneira mais eficiente.

Há dois tipos de solicitações parciais:

  • Resposta parcial: uma solicitação em que você especifica quais campos serão incluídos na resposta (use o parâmetro de solicitação fields).
  • Patch: uma solicitação de atualização em que você envia somente os campos que serão alterados (use o verbo HTTP PATCH).

Nas seções a seguir são fornecidos mais detalhes sobre como fazer solicitações parciais.

Resposta parcial

Por padrão, depois de processar as solicitações, o servidor envia de volta a representação completa de um recurso. Para melhorar o desempenho, solicite ao servidor o envio apenas dos campos realmente necessários para receber uma resposta parcial.

Para solicitar uma resposta parcial, use o parâmetro de solicitação fields para especificar os campos a serem retornados. Use esse parâmetro com qualquer solicitação que retorne dados de resposta.

Observe que o parâmetro fields afeta apenas os dados de resposta. Ele não afeta os dados que você precisa enviar, se houver. Para reduzir a quantidade de dados enviados durante a modificação de recursos, use uma solicitação de patch.

Exemplo

O exemplo a seguir mostra o uso do parâmetro fields com uma API "Demo" genérica (fictícia).

Solicitação simples: essa solicitação HTTP GET omite o parâmetro fields e retorna o recurso completo.

https://www.googleapis.com/demo/v1

Resposta de recursos completos: os dados de recursos completos incluem os campos a seguir, além de muitos outros omitidos para agilizar o processo.

{
  "kind": "demo",
  ...
  "items": [
  {
    "title": "First title",
    "comment": "First comment.",
    "characteristics": {
      "length": "short",
      "accuracy": "high",
      "followers": ["Jo", "Will"],
    },
    "status": "active",
    ...
  },
  {
    "title": "Second title",
    "comment": "Second comment.",
    "characteristics": {
      "length": "long",
      "accuracy": "medium"
      "followers": [ ],
    },
    "status": "pending",
    ...
  },
  ...
  ]
}

Solicitação de uma resposta parcial: na solicitação a seguir, para esse mesmo recurso, o parâmetro fields é usado para reduzir de modo significativo a quantidade de dados retornados.

https://www.googleapis.com/demo/v1?fields=kind,items(title,characteristics/length)

Resposta parcial: em reação à solicitação acima, o servidor envia de volta uma resposta que contém somente as informações de tipo, além de uma matriz de itens pareados com características de tamanho e título HTML em cada item.

200 OK
{
  "kind": "demo",
  "items": [{
    "title": "First title",
    "characteristics": {
      "length": "short"
    }
  }, {
    "title": "Second title",
    "characteristics": {
      "length": "long"
    }
  },
  ...
  ]
}

A resposta é um objeto JSON que contém apenas os campos selecionados e os respectivos objetos pais.

Os detalhes sobre como formatar o parâmetro fields são abordados a seguir. Depois há mais informações sobre os elementos retornados na resposta.

Resumo da sintaxe do parâmetro "fields"

O formato do valor do parâmetro da solicitação fields baseia-se vagamente na sintaxe XPath. Veja abaixo um resumo da sintaxe compatível e outros exemplos.

  • Use uma lista separada por vírgulas para selecionar mais de um campo.
  • Use a/b para selecionar um campo b aninhado no campo a. Use a/b/c para selecionar um campo c aninhado em b.

    Exceção: para respostas da API que usam wrappers "data", em que a resposta é aninhada em um objeto data semelhante a data: { ... }, não inclua data na especificação fields. A inclusão do objeto de dados com uma especificação de campos como data/a/b causa um erro. Em vez disso, basta usar uma especificação fields como a/b.

  • Use um subseletor para solicitar um conjunto de subcampos específicos de matrizes ou objetos. Basta colocar expressões entre parênteses ( ).

    Por exemplo: fields=items(id,author/email) retorna apenas o código do item e o e-mail do autor para cada elemento na matriz de itens. Também é possível especificar um único subcampo, em que fields=items(id) é equivalente a fields=items/id.

  • Se necessário, use caracteres curinga em seleções de campo.

    Por exemplo: fields=items/pagemap/* seleciona todos os objetos em um pagemap.

Mais exemplos do uso do parâmetro "fields"

Os exemplos abaixo incluem descrições de como o valor do parâmetro fields afeta a resposta.

Observação: assim como com todos os valores de parâmetro de consulta, fields também precisa ter codificação de URL. Para facilitar a leitura, os exemplos neste documento estão sem a codificação.

Identifique os campos que quer retornar ou fazer seleções de campo.
O valor de parâmetro da solicitação fields é uma lista de campos separados por vírgulas e cada campo é especificado em relação à raiz da resposta. Portanto, se você estiver executando uma operação de lista, a resposta será uma coleção que, geralmente, inclui uma matriz de recursos. Se você estiver executando uma operação que retorne um único recurso, os campos serão especificados em relação a esse recurso. Se o campo selecionado for uma matriz, ou parte dela, o servidor retornará a parte selecionada de todos os elementos na matriz.

Veja alguns exemplos no nível de coleção:
Exemplos Efeito
items Retorna todos os elementos da matriz de itens, incluindo todos os campos em cada elemento, mas nenhum outro campo.
etag,items Retorna o campo etag e todos os elementos na matriz de itens.
items/title Retorna apenas o campo title para todos os elementos da matriz de itens.

Sempre que um campo aninhado for retornado, a resposta incluirá os respectivos objetos pais. Os campos pai não incluem outro campo filho, a menos que eles também sejam selecionados explicitamente.
context/facets/label Retorna apenas o campo label para todos os membros da matriz facets, que é aninhada sob o objeto context.
items/pagemap/*/title Para cada elemento na matriz de itens, retorna apenas o campo title, se presente, de todos os objetos filhos de pagemap.

Veja alguns exemplos no nível de recurso:
Exemplos Efeito
title Retorna o campo title do recurso solicitado.
author/uri Retorna o subcampo uri do objeto author no recurso solicitado.
links/*/href
Retorna o campo href de todos os objetos filhos de links.
Solicite apenas partes de campos específicos usando subseleções.
Por padrão, se houver campos especificados na solicitação, todos os objetos ou elementos da matriz serão retornados pelo servidor. É possível especificar uma resposta que inclua apenas alguns subcampos. Para fazer isso, use a sintaxe de subseleção ( ), como no exemplo abaixo.
Exemplo Efeito
items(title,author/uri) Retorna apenas os valores de title e de uri do autor para cada elemento na matriz de itens.

Como processar respostas parciais

Depois que o servidor processar uma solicitação válida que inclua o parâmetro de consulta fields, ele retornará um código de status HTTP 200 OK junto com os dados solicitados. Se o parâmetro de consulta fields tiver um erro ou for inválido, o servidor retornará um código de status HTTP 400 Bad Request com uma mensagem informando ao usuário o que havia de errado com a seleção de campos (por exemplo, "Invalid field selection a/b").

Veja o exemplo da resposta parcial apresentado na seção introdutória acima. A solicitação usa o parâmetro fields para especificar os campos que precisam ser retornados.

https://www.googleapis.com/demo/v1?fields=kind,items(title,characteristics/length)

A resposta parcial tem esta aparência:

200 OK
{
  "kind": "demo",
  "items": [{
    "title": "First title",
    "characteristics": {
      "length": "short"
    }
  }, {
    "title": "Second title",
    "characteristics": {
      "length": "long"
    }
  },
  ...
  ]
}

Observação: em APIs compatíveis com parâmetros de consulta para paginação de dados (por exemplo, maxResults e nextPageToken), use esses parâmetros para reduzir os resultados de cada consulta a um tamanho administrável. Caso contrário, os ganhos de desempenho possíveis com a resposta parcial não serão alcançados.

Patch (atualização parcial)

É possível também evitar o envio de dados desnecessários ao modificar recursos. Para enviar dados atualizados apenas nos campos que estiverem sendo alterados, use o verbo HTTP PATCH. A semântica do patch descrita neste documento é diferente e mais simples do que aquela da implementação de atualização parcial do GData, que é mais antiga.

Veja no exemplo abaixo como o uso do patch minimiza os dados necessários para fazer uma pequena atualização.

Exemplo

Este exemplo mostra uma solicitação de patch simples para atualizar somente o título de um recurso de uma API "Demo" genérica (fictícia). O recurso também tem um comentário, um conjunto de características, status e muitos outros campos, mas essa solicitação envia apenas o campo title, porque ele é o único que está sendo modificado:

PATCH https://www.googleapis.com/demo/v1/324
Authorization: Bearer your_auth_token
Content-Type: application/json

{
  "title": "New title"
}

Resposta:

200 OK
{
  "title": "New title",
  "comment": "First comment.",
  "characteristics": {
    "length": "short",
    "accuracy": "high",
    "followers": ["Jo", "Will"],
  },
  "status": "active",
  ...
}

O servidor retorna um código de status 200 OK e a representação completa do recurso atualizado. Como apenas o campo title foi incluído na solicitação de patch, esse é o único valor diferente de antes.

Observação: se você usar o parâmetro fields da resposta parcial em combinação com patch, será possível aumentar ainda mais a eficiência de suas solicitações de atualização. Uma solicitação de patch só reduz o tamanho da solicitação. Uma resposta parcial reduz o tamanho da resposta. Portanto, para reduzir a quantidade de dados enviados nas duas direções, use uma solicitação de patch com o parâmetro fields.

Semântica de uma solicitação de patch

O corpo da solicitação de patch inclui somente os campos de recurso que você quer modificar. Ao especificar um campo, é necessário adicionar todos os objetos pai incluídos, da forma como eles são retornados com uma resposta parcial. Os dados modificados que você enviar serão mesclados às informações do objeto pai, se houver um.

  • Adicionar: para adicionar um campo que ainda não existe, especifique o novo campo e o valor correspondente.
  • Modificar: para alterar o valor de um campo atual, especifique o campo e configure-o como o valor novo.
  • Excluir: para excluir um campo, especifique-o e configure-o como null. Por exemplo, "comment": null. Você também pode excluir um objeto inteiro (se for mutável) configurando-o como null. Se você estiver usando a biblioteca de cliente da API Java, use Data.NULL_STRING. Para mais detalhes, consulte JSON null.

Observação sobre matrizes: solicitações de patch que contêm matrizes substituem a matriz atual pela fornecida por você. Não é possível modificar, adicionar ou excluir parcialmente os itens em uma matriz.

Como usar patch em um ciclo de leitura-modificação-gravação

Pode ser útil começar com a recuperação de uma resposta parcial com os dados que quer modificar. Isso é especialmente importante para recursos que usam ETags, porque é preciso fornecer o valor de ETag atual no cabeçalho HTTP If-Match para atualizar o recurso com êxito. Depois de receber os dados, será possível modificar os valores que quer alterar e devolver a representação parcial modificada com uma solicitação de patch. Veja um exemplo que pressupõe que o recurso de demonstração usa ETags:

GET https://www.googleapis.com/demo/v1/324?fields=etag,title,comment,characteristics
Authorization: Bearer your_auth_token

Esta é a resposta parcial:

200 OK
{
  "etag": "ETagString"
  "title": "New title"
  "comment": "First comment.",
  "characteristics": {
    "length": "short",
    "level": "5",
    "followers": ["Jo", "Will"],
  }
}

A solicitação de patch a seguir é baseada nessa resposta. Como mostrado abaixo, ela também usa o parâmetro fields para limitar os dados retornados na resposta do patch:

PATCH https://www.googleapis.com/demo/v1/324?fields=etag,title,comment,characteristics
Authorization: Bearer your_auth_token
Content-Type: application/json
If-Match: "ETagString"
{
  "etag": "ETagString"
  "title": "",                  /* Clear the value of the title by setting it to the empty string. */
  "comment": null,              /* Delete the comment by replacing its value with null. */
  "characteristics": {
    "length": "short",
    "level": "10",              /* Modify the level value. */
    "followers": ["Jo", "Liz"], /* Replace the followers array to delete Will and add Liz. */
    "accuracy": "high"          /* Add a new characteristic. */
  },
}

O servidor responde com um código de status HTTP "200 OK" e a representação parcial do recurso atualizado:

200 OK
{
  "etag": "newETagString"
  "title": "",                 /* Title is cleared; deleted comment field is missing. */
  "characteristics": {
    "length": "short",
    "level": "10",             /* Value is updated.*/
    "followers": ["Jo" "Liz"], /* New follower Liz is present; deleted Will is missing. */
    "accuracy": "high"         /* New characteristic is present. */
  }
}

Como criar uma solicitação de patch diretamente

Algumas solicitações de patch precisam ser baseadas nos dados recuperados anteriormente. Por exemplo, para adicionar um item a uma matriz sem perder elementos dela, primeiro extraia os dados atuais. Da mesma maneira, se uma API usa ETags, é preciso enviar o valor anterior da ETag com sua solicitação para atualizar o recurso corretamente.

Observação: use um cabeçalho HTTP "If-Match: *" para forçar a passagem de um patch quando as ETags estiverem sendo usadas.  Se fizer isso, não será necessário fazer a leitura antes da gravação.

Porém, em outras situações, é possível construir a solicitação de patch diretamente, sem recuperar os dados atuais antes. Por exemplo, é fácil configurar uma solicitação de patch que atualiza um campo com um novo valor ou adiciona um novo campo. Veja um exemplo:

PATCH https://www.googleapis.com/demo/v1/324?fields=comment,characteristics
Authorization: Bearer your_auth_token
Content-Type: application/json

{
  "comment": "A new comment",
  "characteristics": {
    "volume": "loud",
    "accuracy": null
  }
}

Se o campo estiver vazio, essa solicitação definirá um valor. Caso contrário, ela substituirá o valor novo. Da mesma maneira, se houvesse uma característica de volume, o valor seria substituído. Caso contrário, um novo seria criado. O campo "accuracy", se definido, seria removido.

Como processar a resposta para um patch

Ao processar uma solicitação de patch válida, a API retorna um código de resposta HTTP 200 OK com a representação completa do recurso modificado. Caso a API use ETags, o servidor vai atualizar os valores delas durante o processamento de uma solicitação de patch, como acontece com PUT.

A solicitação de patch retorna toda a representação do recurso, a menos que você use o parâmetro fields para reduzir a quantidade de dados retornados.

Se uma solicitação de correção resultar em um novo estado de recurso sintática ou semanticamente inválido, o servidor retornará um código de status HTTP 400 Bad Request ou 422 Unprocessable Entity e o estado do recurso permanecerá inalterado. Por exemplo, ao tentar excluir o valor de um campo obrigatório, o servidor retorna um erro.

Notação alternativa quando não houver suporte para o verbo HTTP PATCH

Caso o firewall não permita solicitações de HTTP PATCH, faça uma solicitação HTTP POST e configure o cabeçalho de substituição como PATCH. Veja o exemplo abaixo:

POST https://www.googleapis.com/...
X-HTTP-Method-Override: PATCH
...

Diferença entre patch e atualização

Na prática, ao enviar dados para uma solicitação de atualização que usa o verbo HTTP PUT, basta enviar os campos obrigatórios ou opcionais. Os valores enviados para campos definidos pelo servidor serão ignorados. Isso pode parecer outra maneira de fazer uma atualização parcial, mas essa abordagem tem algumas limitações. Com atualizações que usam o verbo HTTP PUT, a solicitação falha quando não são fornecidos parâmetros obrigatórios e remove dados já definidos quando parâmetros opcionais não são informados.

É muito mais seguro usar o patch por esse motivo. Você fornece somente os dados dos campos que quer alterar. Os campos omitidos não são apagados. A única exceção a essa regra ocorre com matrizes ou elementos repetidos. Se forem omitidos, eles ficarão como estão. Se você fornecer parte deles, o conjunto inteiro será substituído pelo conjunto que você inseriu.

Limite de taxa de solicitação recomendado

Podemos limitar a taxa com que você pode chamar a API Google Wallet. Recomendamos que você limite as solicitações a, no máximo, 20 por segundo.

Solicitações em lote para a Carteira do Google

A API Google Wallet suporta chamadas de API em lote para reduzir o número de conexões que um cliente precisa fazer. Para mais informações sobre solicitação de lote e estrutura de resposta, consulte Detalhes do lote.

Veja as solicitações em lote na amostra de código a seguir. Os exemplos em Java e PHP usam as bibliotecas da Carteira do Google para simplificar a criação de classes e objetos.

Java

/**
 * Batch create Google Wallet objects from an existing class.
 *
 * @param issuerId The issuer ID being used for this request.
 * @param classSuffix Developer-defined unique ID for this pass class.
 */
public void batchCreateObjects(String issuerId, String classSuffix) throws IOException {
  // Create the batch request client
  BatchRequest batch = service.batch(new HttpCredentialsAdapter(credentials));

  // The callback will be invoked for each request in the batch
  JsonBatchCallback<GiftCardObject> callback =
      new JsonBatchCallback<GiftCardObject>() {
        // Invoked if the request was successful
        public void onSuccess(GiftCardObject response, HttpHeaders responseHeaders) {
          System.out.println("Batch insert response");
          System.out.println(response.toString());
        }

        // Invoked if the request failed
        public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) {
          System.out.println("Error Message: " + e.getMessage());
        }
      };

  // Example: Generate three new pass objects
  for (int i = 0; i < 3; i++) {
    // Generate a random object suffix
    String objectSuffix = UUID.randomUUID().toString().replaceAll("[^\\w.-]", "_");

    // See link below for more information on required properties
    // https://developers.google.com/wallet/retail/gift-cards/rest/v1/giftcardobject
    GiftCardObject batchObject =
        new GiftCardObject()
            .setId(String.format("%s.%s", issuerId, objectSuffix))
            .setClassId(String.format("%s.%s", issuerId, classSuffix))
            .setState("ACTIVE")
            .setHeroImage(
                new Image()
                    .setSourceUri(
                        new ImageUri()
                            .setUri(
                                "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"))
                    .setContentDescription(
                        new LocalizedString()
                            .setDefaultValue(
                                new TranslatedString()
                                    .setLanguage("en-US")
                                    .setValue("Hero image description"))))
            .setTextModulesData(
                    List.of(
                            new TextModuleData()
                                    .setHeader("Text module header")
                                    .setBody("Text module body")
                                    .setId("TEXT_MODULE_ID")))
            .setLinksModuleData(
                new LinksModuleData()
                    .setUris(
                        Arrays.asList(
                            new Uri()
                                .setUri("http://maps.google.com/")
                                .setDescription("Link module URI description")
                                .setId("LINK_MODULE_URI_ID"),
                            new Uri()
                                .setUri("tel:6505555555")
                                .setDescription("Link module tel description")
                                .setId("LINK_MODULE_TEL_ID"))))
            .setImageModulesData(
                    List.of(
                            new ImageModuleData()
                                    .setMainImage(
                                            new Image()
                                                    .setSourceUri(
                                                            new ImageUri()
                                                                    .setUri(
                                                                            "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"))
                                                    .setContentDescription(
                                                            new LocalizedString()
                                                                    .setDefaultValue(
                                                                            new TranslatedString()
                                                                                    .setLanguage("en-US")
                                                                                    .setValue("Image module description"))))
                                    .setId("IMAGE_MODULE_ID")))
            .setBarcode(new Barcode().setType("QR_CODE").setValue("QR code value"))
            .setLocations(
                    List.of(
                            new LatLongPoint()
                                    .setLatitude(37.424015499999996)
                                    .setLongitude(-122.09259560000001)))
            .setCardNumber("Card number")
            .setPin("1234")
            .setBalance(new Money().setMicros(20000000L).setCurrencyCode("USD"))
            .setBalanceUpdateTime(new DateTime().setDate("2020-04-12T16:20:50.52-04:00"));

    service.giftcardobject().insert(batchObject).queue(batch, callback);
  }

  // Invoke the batch API calls
  batch.execute();
}

PHP

/**
 * Batch create Google Wallet objects from an existing class.
 *
 * @param string $issuerId The issuer ID being used for this request.
 * @param string $classSuffix Developer-defined unique ID for the pass class.
 */
public function batchCreateObjects(string $issuerId, string $classSuffix)
{
  // Update the client to enable batch requests
  $this->client->setUseBatch(true);
  $batch = $this->service->createBatch();

  // Example: Generate three new pass objects
  for ($i = 0; $i < 3; $i++) {
    // Generate a random object suffix
    $objectSuffix = preg_replace('/[^\w.-]/i', '_', uniqid());

    // See link below for more information on required properties
    // https://developers.google.com/wallet/retail/gift-cards/rest/v1/giftcardobject
    $batchObject = new Google_Service_Walletobjects_GiftCardObject([
      'id' => "{$issuerId}.{$objectSuffix}",
      'classId' => "{$issuerId}.{$classSuffix}",
      'state' => 'ACTIVE',
      'heroImage' => new Google_Service_Walletobjects_Image([
        'sourceUri' => new Google_Service_Walletobjects_ImageUri([
          'uri' => 'https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg'
        ]),
        'contentDescription' => new Google_Service_Walletobjects_LocalizedString([
          'defaultValue' => new Google_Service_Walletobjects_TranslatedString([
            'language' => 'en-US',
            'value' => 'Hero image description'
          ])
        ])
      ]),
      'textModulesData' => [
        new Google_Service_Walletobjects_TextModuleData([
          'header' => 'Text module header',
          'body' => 'Text module body',
          'id' => 'TEXT_MODULE_ID'
        ])
      ],
      'linksModuleData' => new Google_Service_Walletobjects_LinksModuleData([
        'uris' => [
          new Google_Service_Walletobjects_Uri([
            'uri' => 'http://maps.google.com/',
            'description' => 'Link module URI description',
            'id' => 'LINK_MODULE_URI_ID'
          ]),
          new Google_Service_Walletobjects_Uri([
            'uri' => 'tel:6505555555',
            'description' => 'Link module tel description',
            'id' => 'LINK_MODULE_TEL_ID'
          ])
        ]
      ]),
      'imageModulesData' => [
        new Google_Service_Walletobjects_ImageModuleData([
          'mainImage' => new Google_Service_Walletobjects_Image([
            'sourceUri' => new Google_Service_Walletobjects_ImageUri([
              'uri' => 'http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg'
            ]),
            'contentDescription' => new Google_Service_Walletobjects_LocalizedString([
              'defaultValue' => new Google_Service_Walletobjects_TranslatedString([
                'language' => 'en-US',
                'value' => 'Image module description'
              ])
            ])
          ]),
          'id' => 'IMAGE_MODULE_ID'
        ])
      ],
      'barcode' => new Google_Service_Walletobjects_Barcode([
        'type' => 'QR_CODE',
        'value' => 'QR code value'
      ]),
      'locations' => [
        new Google_Service_Walletobjects_LatLongPoint([
          'latitude' => 37.424015499999996,
          'longitude' =>  -122.09259560000001
        ])
      ],
      'cardNumber' => 'Card number',
      'pin' => '1234',
      'balance' => new Google_Service_Walletobjects_Money([
        'micros' => 20000000,
        'currencyCode' => 'USD'
      ]),
      'balanceUpdateTime' => new Google_Service_Walletobjects_DateTime([
        'date' => '2020-04-12T16:20:50.52-04:00'
      ])
    ]);

    $batch->add($this->service->giftcardobject->insert($batchObject));
  }

  // Make the batch request
  $batchResponse = $batch->execute();

  print "Batch insert response\n";
  foreach ($batchResponse as $key => $value) {
    if ($value instanceof Google_Service_Exception) {
      print_r($value->getErrors());
      continue;
    }
    print "{$value->getId()}\n";
  }
}

Python

def batch_create_objects(self, issuer_id: str, class_suffix: str):
    """Batch create Google Wallet objects from an existing class.

    The request body will be a multiline string. See below for information.

    https://cloud.google.com/compute/docs/api/how-tos/batch#example

    Args:
        issuer_id (str): The issuer ID being used for this request.
        class_suffix (str): Developer-defined unique ID for this pass class.
    """
    data = ''

    # Example: Generate three new pass objects
    for _ in range(3):
        # Generate a random object suffix
        object_suffix = str(uuid.uuid4()).replace('[^\\w.-]', '_')

        # See link below for more information on required properties
        # https://developers.google.com/wallet/retail/gift-cards/rest/v1/giftcardobject
        batch_object = {
            'id': f'{issuer_id}.{object_suffix}',
            'classId': f'{issuer_id}.{class_suffix}',
            'state': 'ACTIVE',
            'heroImage': {
                'sourceUri': {
                    'uri':
                        'https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg'
                },
                'contentDescription': {
                    'defaultValue': {
                        'language': 'en-US',
                        'value': 'Hero image description'
                    }
                }
            },
            'textModulesData': [{
                'header': 'Text module header',
                'body': 'Text module body',
                'id': 'TEXT_MODULE_ID'
            }],
            'linksModuleData': {
                'uris': [{
                    'uri': 'http://maps.google.com/',
                    'description': 'Link module URI description',
                    'id': 'LINK_MODULE_URI_ID'
                }, {
                    'uri': 'tel:6505555555',
                    'description': 'Link module tel description',
                    'id': 'LINK_MODULE_TEL_ID'
                }]
            },
            'imageModulesData': [{
                'mainImage': {
                    'sourceUri': {
                        'uri':
                            'http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg'
                    },
                    'contentDescription': {
                        'defaultValue': {
                            'language': 'en-US',
                            'value': 'Image module description'
                        }
                    }
                },
                'id': 'IMAGE_MODULE_ID'
            }],
            'barcode': {
                'type': 'QR_CODE',
                'value': 'QR code'
            },
            'locations': [{
                'latitude': 37.424015499999996,
                'longitude': -122.09259560000001
            }],
            'cardNumber': 'Card number',
            'pin': '1234',
            'balance': {
                'micros': 20000000,
                'currencyCode': 'USD'
            },
            'balanceUpdateTime': {
                'date': '2020-04-12T16:20:50.52-04:00'
            }
        }

        data += '--batch_createobjectbatch\n'
        data += 'Content-Type: application/json\n\n'
        data += 'POST /walletobjects/v1/giftCardObject/\n\n'

        data += json.dumps(batch_object) + '\n\n'

    data += '--batch_createobjectbatch--'

    # Invoke the batch API calls
    response = self.http_client.post(
        url='https://walletobjects.googleapis.com/batch',
        data=data,
        headers={
            # `boundary` is the delimiter between API calls in the batch request
            'Content-Type':
                'multipart/mixed; boundary=batch_createobjectbatch'
        })

    print('Batch insert response')
    print(response.content.decode('UTF-8'))

C#

/// <summary>
/// Batch create Google Wallet objects from an existing class.
/// </summary>
/// <param name="issuerId">The issuer ID being used for this request.</param>
/// <param name="classSuffix">Developer-defined unique ID for this pass class.</param>
public async void BatchCreateObjects(string issuerId, string classSuffix)
{
  // The request body will be a multiline string
  // See below for more information
  // https://cloud.google.com/compute/docs/api/how-tos/batch//example
  string data = "";

  HttpClient httpClient = new HttpClient();
  httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Bearer",
    credentials.GetAccessTokenForRequestAsync().Result
  );

  // Example: Generate three new pass objects
  for (int i = 0; i < 3; i++)
  {
    // Generate a random object suffix
    string objectSuffix = Regex.Replace(Guid.NewGuid().ToString(), "[^\\w.-]", "_");

    // See link below for more information on required properties
    // https://developers.google.com/wallet/retail/gift-cards/rest/v1/giftcardobject
    GiftCardObject batchObject = new GiftCardObject
    {
      Id = $"{issuerId}.{objectSuffix}",
      ClassId = $"{issuerId}.{classSuffix}",
      State = "ACTIVE",
      HeroImage = new Image
      {
        SourceUri = new ImageUri
        {
          Uri = "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
        },
        ContentDescription = new LocalizedString
        {
          DefaultValue = new TranslatedString
          {
            Language = "en-US",
            Value = "Hero image description"
          }
        }
      },
      TextModulesData = new List<TextModuleData>
      {
        new TextModuleData
        {
          Header = "Text module header",
          Body = "Text module body",
          Id = "TEXT_MODULE_ID"
        }
      },
      LinksModuleData = new LinksModuleData
      {
        Uris = new List<Google.Apis.Walletobjects.v1.Data.Uri>
        {
          new Google.Apis.Walletobjects.v1.Data.Uri
          {
            UriValue = "http://maps.google.com/",
            Description = "Link module URI description",
            Id = "LINK_MODULE_URI_ID"
          },
          new Google.Apis.Walletobjects.v1.Data.Uri
          {
            UriValue = "tel:6505555555",
            Description = "Link module tel description",
            Id = "LINK_MODULE_TEL_ID"
          }
        }
      },
      ImageModulesData = new List<ImageModuleData>
      {
        new ImageModuleData
        {
          MainImage = new Image
          {
            SourceUri = new ImageUri
            {
              Uri = "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
            },
            ContentDescription = new LocalizedString
            {
              DefaultValue = new TranslatedString
              {
                Language = "en-US",
                Value = "Image module description"
              }
            }
          },
          Id = "IMAGE_MODULE_ID"
        }
      },
      Barcode = new Barcode
      {
        Type = "QR_CODE",
        Value = "QR code"
      },
      Locations = new List<LatLongPoint>
      {
        new LatLongPoint
        {
          Latitude = 37.424015499999996,
          Longitude = -122.09259560000001
        }
      },
      CardNumber = "Card number",
      Pin = "1234",
      Balance = new Money
      {
        Micros = 20000000,
        CurrencyCode = "USD"
      },
      BalanceUpdateTime = new Google.Apis.Walletobjects.v1.Data.DateTime
      {
        Date = "2020-04-12T16:20:50.52-04:00"
      }
    };

    data += "--batch_createobjectbatch\n";
    data += "Content-Type: application/json\n\n";
    data += "POST /walletobjects/v1/giftCardObject/\n\n";

    data += JsonConvert.SerializeObject(batchObject) + "\n\n";
  }
  data += "--batch_createobjectbatch--";

  // Invoke the batch API calls
  HttpRequestMessage batchObjectRequest = new HttpRequestMessage(
      HttpMethod.Post,
      "https://walletobjects.googleapis.com/batch");

  batchObjectRequest.Content = new StringContent(data);
  batchObjectRequest.Content.Headers.ContentType = new MediaTypeHeaderValue(
      "multipart/mixed");
  // `boundary` is the delimiter between API calls in the batch request
  batchObjectRequest.Content.Headers.ContentType.Parameters.Add(
      new NameValueHeaderValue("boundary", "batch_createobjectbatch"));

  HttpResponseMessage batchObjectResponse = httpClient.Send(
      batchObjectRequest);

  string batchObjectContent = await batchObjectResponse
      .Content
      .ReadAsStringAsync();

  Console.WriteLine("Batch insert response");
  Console.WriteLine(batchObjectContent);
}

Node.js

/**
 * Batch create Google Wallet objects from an existing class.
 *
 * @param {string} issuerId The issuer ID being used for this request.
 * @param {string} classSuffix Developer-defined unique ID for this pass class.
 */
async batchCreateObjects(issuerId, classSuffix) {
  // See below for more information
  // https://cloud.google.com/compute/docs/api/how-tos/batch#example
  let data = '';
  let batchObject;
  let objectSuffix;

  // Example: Generate three new pass objects
  for (let i = 0; i < 3; i++) {
    // Generate a random object suffix
    objectSuffix = uuidv4().replace('[^\w.-]', '_');

    // See link below for more information on required properties
    // https://developers.google.com/wallet/retail/gift-cards/rest/v1/giftcardobject
    batchObject = {
      'id': `${issuerId}.${objectSuffix}`,
      'classId': `${issuerId}.${classSuffix}`,
      'state': 'ACTIVE',
      'heroImage': {
        'sourceUri': {
          'uri': 'https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg'
        },
        'contentDescription': {
          'defaultValue': {
            'language': 'en-US',
            'value': 'Hero image description'
          }
        }
      },
      'textModulesData': [
        {
          'header': 'Text module header',
          'body': 'Text module body',
          'id': 'TEXT_MODULE_ID'
        }
      ],
      'linksModuleData': {
        'uris': [
          {
            'uri': 'http://maps.google.com/',
            'description': 'Link module URI description',
            'id': 'LINK_MODULE_URI_ID'
          },
          {
            'uri': 'tel:6505555555',
            'description': 'Link module tel description',
            'id': 'LINK_MODULE_TEL_ID'
          }
        ]
      },
      'imageModulesData': [
        {
          'mainImage': {
            'sourceUri': {
              'uri': 'http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg'
            },
            'contentDescription': {
              'defaultValue': {
                'language': 'en-US',
                'value': 'Image module description'
              }
            }
          },
          'id': 'IMAGE_MODULE_ID'
        }
      ],
      'barcode': {
        'type': 'QR_CODE',
        'value': 'QR code'
      },
      'locations': [
        {
          'latitude': 37.424015499999996,
          'longitude': -122.09259560000001
        }
      ],
      'cardNumber': 'Card number',
      'pin': '1234',
      'balance': {
        'micros': 20000000,
        'currencyCode': 'USD'
      },
      'balanceUpdateTime': {
        'date': '2020-04-12T16:20:50.52-04:00'
      }
    };

    data += '--batch_createobjectbatch\n';
    data += 'Content-Type: application/json\n\n';
    data += 'POST /walletobjects/v1/giftCardObject\n\n';

    data += JSON.stringify(batchObject) + '\n\n';
  }
  data += '--batch_createobjectbatch--';

  // Invoke the batch API calls
  let response = await this.httpClient.request({
    url: this.batchUrl,
    method: 'POST',
    data: data,
    headers: {
      // `boundary` is the delimiter between API calls in the batch request
      'Content-Type': 'multipart/mixed; boundary=batch_createobjectbatch'
    }
  });

  console.log('Batch insert response');
  console.log(response);
}