Magento 2: Como retornar um objeto JSON da API?

8

Estou tentando retornar um objeto JSON de um dos meus modelos REST, algo como isto:

{
    "settings": {
        "set1" : 2,
        "set2" : "key1" 
    },
    "extra": {
        "e1'" : {
            "e2'": true 
        }
    }
}

No entanto, o que parece trivial, não é tão fácil de implementar. O problema é que não tenho certeza de qual deve ser o tipo de retorno na interface e no modelo.

<?php

namespace AppFactory\Core\Api;

/**
 * @api
 */

interface SettingsInterface
{


    /**
     * @return object
     */
    public function get();
}

A classe de objeto retornará

{
  "message": "Class object does not exist",

ao chamar a API. Os tipos primitivos disponíveis int, number e array não funcionarão para mim. Eu não quero criar uma classe para cada tipo complexo que está retornando também. Como posso fazer isso?

Obrigado.

Yehia A.Salam
fonte
dados json é string para php para torná-lo corda
Mohammad Mujassam
A string de retorno @MohammadMujassam no docBlock fará com que o Magento converta o objeto de saída em string, escapando do "com barras invertidas e envolvendo o objeto inteiro". Analisei este artigo maxchadwick.xyz/blog/… e sugere que não há outras maneiras de retornar um objeto além de criar um modelo de dados para ele, mas só quero garantir que esse seja o único caminho e que não haja outras maneiras.
precisa saber é o seguinte
Sim, definitivamente, será.
Mohammad Mujassam

Respostas:

17

Estou assumindo que AppFactory\Core\Api\SettingInterface::get()é um ponto de extremidade REST. Nesse caso, nos comentários do phpdoc, você precisa definir o que isso retornará. O manipulador REST Magento pega esse valor e o processa para remover todos os dados desnecessários. O que resta será codificado em JSON, portanto, em javascript, você pode recuperá-lo como hash JS já adequado e não como uma string codificada por json.

O truque sobre esses pontos de extremidade é que você precisa definir com muita precisão o que retornará. O Magento não será capaz de processar algo tão geral como "array", onde você definirá o que quiser.

No seu caso, para não tentar jogar com uma série de cadeias, será mais fácil criar uma interface que seu endpoint retornará.

 <?php

 namespace AppFactory\Core\Api;

 /**
  * @api
  */

 interface SettingsInterface
 {


     /**
      * @return Data\SettingsInterface
      */
     public function get();
 }

Agora, quando você retornar uma instância de um objeto implementando essa interface, o Magento lerá seus phpdocs e processará seus valores de retorno. Agora crie um arquivo da AppFactory\Core\Api\Data\SettingsInterfaceseguinte maneira

<?php

namespace AppFactory\Core\Api\Data;

interface SettingsInterface
{
    /**
    * @return int[]
    **/
    public function getSettings();

    /**
    * @return string[]
    **/
    public function getExtra();
}

Agora, quando você cria uma classe real que implementará esses 2 métodos get e você a retornará, o AppFactory\Core\Api\SettingsInterface::get()magento retornará algo como

{
    "settings": [1, 2, 5],
    "extra": ["my","array","of","strings"]
}

Se você quiser outro nível, precisará criar outra interface que mantenha a settingsestrutura e a adicione como valor de retorno AppFactory\Core\Api\Data\SettingsInterface::getSettings().

Se você precisar de algo que seja dinâmico e não desejar ou não puder preparar essa interface de estrutura, tente definir a string codificada por json e o local @return stringpara qualquer um dos campos. Dessa forma, porém, você precisará decodificar manualmente essa sequência após receber a resposta, pois sua resposta será semelhante a esta:

{
    "settings": [1, 2, 5],
    "extra": "{\"test\":\"string\",\"value\":8}"
}

e para usar response.extra.testvocê terá que primeiro fazer response.extra = JSON.parse(response.extra);manualmente

Zefiryn
fonte
obrigado pela explicação detalhada, decodificar a string no lado do cliente não parece natural e escrever todas as classes para representar cada item é um pesadelo; existe uma quarta opção para retornar o objeto json sem precisar escrever as classes ou retornar string, talvez o retorno anotação mista, embora eu tentei, mas não funcionou infelizmente. Parece que vou acabar encerrando o json final em uma matriz, por exemplo, matriz ($ json_object), isso fará o truque, mas também não parece natural, tendo que pegar o primeiro item da matriz no lado do cliente
Yehia A.Salam
Você pode criar uma ação regular do controlador que retornará json string e defina o cabeçalho como text / json ou application / json, que será decodificado no lado do navegador. Se você quiser usar o resto da API, não encontrei nada que pudesse ignorar esse pós-processamento.
Zefiryn 07/07
sim parece que, magento deve apoiar esta de alguma forma e relaxar sem impor este tipo de validação no tipo de retorno
Yehia A.Salam
@ YehiaA.Salam eu estava verificando algo. Não tenho uma maneira fácil de testar isso, mas tente usar "misto" como retorno dos métodos no AppFactory\Core\Api\DataSettingsInterface. Se isso funcionar, você precisa apenas executar o primeiro nível da resposta.
Zefiryn
Resposta muito útil
Pandurang
5

Também enfrentei esse problema e, como alternativa à solução proposta por Zefiryn, resolvi-o colocando os dados de retorno em uma matriz (ou duas). Por favor, considere o exemplo abaixo.

/**
 * My function
 *
 * @return
 */
public function myFunction()
{
  $searchCriteria = $this->_searchCriteriaBuilder->addFilter('is_filterable_in_grid',true,'eq')->create();
  $productAttributes = $this->_productAttributeRepository->getList($searchCriteria)->getItems();

  $productAttributesArray = [];
  foreach ($productAttributes as $attribute) {
    $productAttributesArray[$attribute->getAttributeCode()] = $this->convertAttributeToArray($attribute);
  }

  return [[
          "attributes"=>$productAttributesArray,
          "another_thing"=>["another_thing_2"=>"two"]
        ]];
}

private function convertAttributeToArray($attribute) {
  return [
    "id" => $attribute->getAttributeId(),
    "code" => $attribute->getAttributeCode(),
    "type" => $attribute->getBackendType(),
    "name" => $attribute->getStoreLabel(),
    "options" => $attribute->getSource()->getAllOptions(false)
  ];
}

Devido à forma como o Magento 2 permite matrizes de conteúdo misto como valores de retorno, estruturas de dados mais complexas podem ser incorporadas dentro de outras matrizes. A amostra acima produz a seguinte resposta JSON (truncada para facilitar a leitura).

[
{
    "attributes": {
        "special_price": {
            "id": "78",
            "code": "special_price",
            "type": "decimal",
            "name": "Special Price",
            "options": []
        },
        "cost": {
            "id": "81",
            "code": "cost",
            "type": "decimal",
            "name": "Cost",
            "options": []
        },
    "another_thing": {
        "another_thing_2": "two"
    }
}
]

Colocá-lo em uma única camada remove as chaves da matriz e, sem incluí-la em nenhuma matriz, resulta em um erro.

Compreensivelmente, nada disso é ideal, mas essa abordagem me permite controlar a consistência na estrutura de dados retornada até um certo grau (a estrutura e os tipos de dados esperados). Se você também estiver no controle da gravação de uma biblioteca do lado do cliente, um interceptador poderá ser implementado para remover a matriz externa antes de devolvê-la ao aplicativo.

pawitk
fonte
1

Para o Magento 2.3.1, se você precisar ignorar a serialização do array, poderá verificar este arquivo para atualizar a lógica principal. Eu acho que é um bom ponto de entrada. Mas, ao fazer isso, você quebrará a compatibilidade do Soap com certeza.

Além disso, no Magento 2.1.X, você não tem esse problema se colocar anyType como tipo de retorno.

Referência do Github: https://github.com/magento/magento2/blob/2.3-develop/lib/internal/Magento/Framework/Reflection/TypeCaster.php

Confirme a referência de alteração: https://github.com/magento/magento2/commit/6ba399cdaea5babb373a35e88131a8cbd041b0de#diff-53855cf24455a74e11a998ac1a871bb8

fornecedor / magento / framework / Reflexão / TypeCaster.php: 42

     /**
     * Type caster does not complicated arrays according to restrictions in JSON/SOAP API
     * but interface and class implementations should be processed as is.
     * Function `class_exists()` is called to do not break code which return an array instead
     * interface implementation.
     */
    if (is_array($value) && !interface_exists($type) && !class_exists($type)) {
        return $this->serializer->serialize($value);
    }

E substitua por:

     /**
     * Type caster does not complicated arrays according to restrictions in JSON/SOAP API
     * but interface and class implementations should be processed as is.
     * Function `class_exists()` is called to do not break code which return an array instead
     * interface implementation.
     */
    if (is_array($value) && !interface_exists($type) && !class_exists($type)) {
        return $value;
    }
Franck Garnier
fonte
1

Eu sei que essa pergunta é bastante antiga, mas há uma solução bastante simples para isso:

Você precisa substituir o Json-Renderer Magento\Framework\Webapi\Rest\Response\Renderer\Jsonou escrever um plug-in para ele.

Aqui está um pequeno exemplo de um plugin:

Na tua di.xml

<type name="Magento\Framework\Webapi\Rest\Response\Renderer\Json">
    <plugin name="namespace_module_renderer_json_plugin" type="Namespace\Module\Plugin\Webapi\RestResponse\JsonPlugin" sortOrder="100" disabled="false" />
</type>

Na sua nova classe de plug-ins Namespace\Module\Plugin\Webapi\RestResponse\JsonPlugin

<?php
namespace Namespace\Module\Plugin\Webapi\RestResponse;

use Magento\Framework\Webapi\Rest\Request;
use Magento\Framework\Webapi\Rest\Response\Renderer\Json;

class JsonPlugin
{

    /** @var Request */
    private $request;

    /**
     * JsonPlugin constructor.
     * @param Request $request
     */
    public function __construct(
        Request $request
    )
    {
        $this->request = $request;
    }

    /**
     * @param Json $jsonRenderer
     * @param callable $proceed
     * @param $data
     * @return mixed
     */
    public function aroundRender(Json $jsonRenderer, callable $proceed, $data)
    {
        if ($this->request->getPathInfo() == "/V1/my/rest-route" && $this->isJson($data)) {
            return $data;
        }
        return $proceed($data);
    }

    /**
    * @param $data
    * @return bool
    */
    private function isJson($data)
    {
       if (!is_string($data)) {
       return false;
    }
    json_decode($data);
    return (json_last_error() == JSON_ERROR_NONE);
}

}

O que acontece aqui:

  • Se a rota restante for "/ V1 / my / rest-route", o novo método de renderização será usado, o que significa simplesmente que os dados não estão codificados.
  • Um método de verificação adicional é usado para avaliar se a string é realmente um objeto json. Caso contrário (por exemplo, se a resposta for um erro 401, isso resultaria em um erro interno e retornaria um código de status errado)
  • Dessa forma, no seu método rest, você pode devolver uma string json, que não será alterada.

Claro que você também pode escrever seu próprio Renderer, que processa uma matriz, por exemplo.

codiga
fonte
0

Enfrentei o mesmo problema e demorei um pouco para descobrir o problema.

O Magento faz algo estranho no processador de saída de serviço de API da Web, localizado em Magento \ Framework \ Webapi \ ServiceOutputProcessor Nesta classe, existe um método chamado convertValue (); qual é o motivo dos chavetas [].

A melhor solução para resolver o problema foi criar um plugin around para superar essa condição if no convertValue (); método em que eles verificam se $ data é uma matriz e fazem coisas estranhas com ela.

Aqui está o meu exemplo de código do plug-in: Eu acho que todo mundo sabe como criar um módulo básico do Magento 2, então eu só publico o código do próprio plug-in aqui.

  • Crie uma pasta Plugin

  • Crie uma classe Vendor \ ModuleName \ Plugin \ ServiceOutputProcessorPlugin.php

<?php

namespace Vendor\ModuleName\Plugin;

use Magento\Framework\Webapi\ServiceOutputProcessor;

class ServiceOutputProcessorPlugin
{
    public function aroundConvertValue(ServiceOutputProcessor $subject, callable $proceed, $data, $type)
    {
        if ($type == 'array') {
            return $data;
        }
        return $proceed($data, $type);
    }
}
  • Crie a declaração de plug-in em Vendor \ ModuleName \ etc \ di.xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\Webapi\ServiceOutputProcessor">
        <plugin name="vendor_modulenameplugin" type="Vendor\ModuleName\Plugin\ServiceOutputProcessorPlugin"/>
    </type>
</config>

Isso deve resolver o problema de saída do json da matriz na API da Web

Espero que isto ajude

Mage2Learn
fonte