Magento 2: ¿Cómo devolver un objeto JSON desde la API?

8

Estoy tratando de devolver un objeto JSON de uno de mis modelos REST, algo como esto:

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

Sin embargo, lo que parece trivial no es tan fácil de implementar. El problema es que no estoy seguro de cuál debería ser el tipo de retorno en la interfaz y el modelo.

<?php

namespace AppFactory\Core\Api;

/**
 * @api
 */

interface SettingsInterface
{


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

La clase de objeto devolverá

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

al llamar a la API. Los tipos primitivos disponibles int, number y array no funcionarán para mí. No quiero crear una clase para cada tipo complejo que también regresa. ¿Cómo puedo hacer esto?

Gracias.

Yehia A.Salam
fuente
json data es una cadena para php, así que
hágalo en
La cadena de retorno de @MohammadMujassam en el docBlock hará que Magento convierta el objeto de salida en cadena escapando de "con barras invertidas y rodeando todo el objeto con". Revisé este artículo maxchadwick.xyz/blog/… y sugiere que no hay otras formas de devolver un objeto que no sea crear un modelo de datos para él, pero solo quiero asegurarme de que esta sea la única forma y no haya otras maneras.
Yehia A.Salam
Sí, definitivamente lo hará.
Mohammad Mujassam

Respuestas:

17

Supongo que AppFactory\Core\Api\SettingInterface::get()es un punto final REST. En ese caso, en los comentarios de phpdoc debe definir qué devolverá esto. El manejador REST de Magento tomará ese valor y lo procesará para eliminar todos los datos que sean innecesarios. Lo que queda se codificará en JSON, por lo que en javascript puede recuperarlo como un hash JS adecuado y no una cadena codificada con json.

El truco sobre esos puntos finales es que debe definir con mucha precisión qué devolverá. Magento no podrá procesar algo tan general como "matriz" donde establecerá lo que quiera.

En su caso, para no intentar jugar con una variedad de cadenas, será más fácil crear una interfaz que devolverá su punto final.

 <?php

 namespace AppFactory\Core\Api;

 /**
  * @api
  */

 interface SettingsInterface
 {


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

Ahora, cuando devuelve una instancia de un objeto que implementa esa interfaz, Magento leerá sus phpdocs y procesará sus valores de retorno. Ahora cree un archivo de la AppFactory\Core\Api\Data\SettingsInterfacesiguiente manera

<?php

namespace AppFactory\Core\Api\Data;

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

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

Ahora, cuando crea una clase real que implementará esos 2 métodos get y la devolverá, AppFactory\Core\Api\SettingsInterface::get()entonces magento devolverá algo como

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

Si desea otro nivel, debe crear otra interfaz que mantenga la settingsestructura y la agregue como valor de retorno AppFactory\Core\Api\Data\SettingsInterface::getSettings().

Si necesita tener algo que sea dinámico y no desea o no puede preparar esta interfaz de estructura, puede intentar configurar una cadena codificada con json y colocarla @return stringen cualquiera de los campos. De esta manera, sin embargo, deberá asegurarse de decodificar manualmente esa cadena después de recibir la respuesta, ya que su respuesta se verá así:

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

y para usarlo response.extra.testprimero response.extra = JSON.parse(response.extra);deberá hacerlo manualmente

Zefiryn
fuente
gracias por la explicación detallada, decodificar la cadena en el lado del cliente no parece natural, y escribir todas las clases para representar cada elemento es una pesadilla, ¿hay alguna cuarta opción para devolver el objeto json sin tener que escribir las clases o devolver cadena, tal vez la anotación mixta de retorno, aunque lo intenté pero desafortunadamente no funcionó. Parece que terminaré encerrando el json final en una matriz, por ejemplo, matriz ($ json_object), esto hará el truco, pero no se siente natural también, tener que recoger el primer elemento de la matriz desde el lado del cliente
Yehia A.Salam
Puede crear una acción de controlador regular que devolverá la cadena json y establecerá el encabezado en text / json o application / json y se decodificará en el lado del navegador. Si desea utilizar la API de descanso, entonces no encontré nada que pudiera evitar ese procesamiento posterior.
Zefiryn
sí lo parece, magento debería soportar esto de alguna manera y aflojarse sin forzar este tipo de validación en el tipo de retorno
Yehia A.Salam
@ YehiaA.Salam Estaba revisando algo. No tengo una manera fácil de probar esto, pero trato de usar "mixto" como un retorno de los métodos en AppFactory\Core\Api\DataSettingsInterface. Si esto funciona, solo necesita hacer el primer nivel de la respuesta.
Zefiryn
Respuesta muy útil
Pandurang
5

También me he enfrentado a este problema y, como alternativa a la solución propuesta por @Zefiryn, he solucionado el problema al encerrar los datos de retorno en una matriz (o dos). Por favor considere el siguiente ejemplo.

/**
 * 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)
  ];
}

Debido a cómo Magento 2 permite matrices de contenido mixto como valores de retorno, se pueden incrustar estructuras de datos más complejas dentro de otras matrices. La muestra anterior produce la siguiente respuesta JSON (truncada para facilitar la lectura).

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

Al encerrarlo en una sola capa se eliminan las claves de la matriz, y sin encerrarlo en ninguna matriz se produce un error.

Es comprensible que nada de esto sea ideal, pero este enfoque me permite controlar la consistencia en la estructura de datos devuelta en cierto grado (la estructura y los tipos de datos esperados). Si también tiene el control de escribir una biblioteca del lado del cliente, se puede implementar un interceptor para eliminar la matriz externa antes de devolverla a la aplicación.

pawitk
fuente
1

Para Magento 2.3.1, si necesita omitir la serialización de la matriz, puede verificar este archivo para actualizar la lógica central. Creo que es un buen punto de entrada. Pero al hacer esto, seguramente romperá la compatibilidad de Soap.

Además, en Magento 2.1.X, no tiene este problema si coloca anyType como tipo de retorno.

Referencia de Github: https://github.com/magento/magento2/blob/2.3-develop/lib/internal/Magento/Framework/Reflection/TypeCaster.php

Confirmar referencia de cambio: https://github.com/magento/magento2/commit/6ba399cdaea5babb373a35e88131a8cbd041b0de#diff-53855cf24455a74e11a998ac1a871bb8

vendor / magento / framework / Reflection / 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);
    }

Y reemplazar 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
fuente
1

Sé que esta pregunta es bastante antigua, pero hay una solución bastante simple para esto:

Es necesario reemplazar el Json-Renderer Magento\Framework\Webapi\Rest\Response\Renderer\Jsono escribir un complemento para él.

Aquí un pequeño ejemplo de un complemento:

En tus 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>

En tu nueva clase de plugin 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);
}

}

Qué pasa aquí:

  • Si la ruta de descanso es "/ V1 / my / ruta de descanso", se utiliza el nuevo método de representación, lo que significa simplemente que los datos no están codificados.
  • Se utiliza un método de verificación adicional para evaluar si la cadena es realmente un objeto json. De lo contrario (por ejemplo, si la respuesta es un error 401, se produciría un error interno y se devolvería un código de estado incorrecto)
  • De esta manera, en su método rest, puede devolver una cadena json, que no se cambiará.

Por supuesto, también puede escribir su propio Renderer, que procesa una matriz, por ejemplo.

codiga
fuente
0

Me enfrenté al mismo problema y me tomó un tiempo resolverlo.

Magento hace algo extraño en el procesador de salida del servicio de API web que se encuentra en Magento \ Framework \ Webapi \ ServiceOutputProcessor. En esta clase hay un método llamado convertValue (); cuál es la razón de las llaves [].

La mejor solución para resolver el problema fue crear un complemento alrededor para superar esto si la condición en el convertValue (); método en el que comprueban si $ data es una matriz y hacen cosas raras con él.

Aquí está mi código de ejemplo del complemento: creo que todos saben cómo crear un módulo básico de Magento 2, por lo que solo publico el código del complemento aquí.

  • Crear una carpeta de complementos

  • Cree una clase 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);
    }
}
  • Cree la declaración del complemento en 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>

Esto debería resolver el problema de salida de array json en la API web

Espero que esto ayude

Mage2Learn
fuente