Magento 2: come restituire un oggetto JSON dall'API?


8

Sto cercando di restituire un oggetto JSON da uno dei miei modelli REST, qualcosa del genere:

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

Tuttavia, ciò che apparentemente sembra banale, non è così facile da implementare. Il problema è che non sono sicuro di quale dovrebbe essere il tipo restituito nell'interfaccia e nel modello.

<?php

namespace AppFactory\Core\Api;

/**
 * @api
 */

interface SettingsInterface
{


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

La classe oggetto verrà restituita

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

quando si chiama l'API. I tipi primitivi disponibili int, number e array non funzioneranno per me. Non voglio creare una classe anche per ogni tipo complesso che sta tornando. Come posso fare questo?

Grazie.


json data è una stringa per php, quindi
falla

@MohammadMujassam restituisce la stringa in docBlock farà in modo che Magento converta l'oggetto di output in stringa sfuggendo al "con barre rovesciate e circondando l'intero oggetto con". Ho esaminato questo articolo maxchadwick.xyz/blog/… e mi suggerisce che non ci sono altri modi per restituire un oggetto se non quello di creare un modello di dati per esso, ma voglio solo assicurarmi che questo sia l'unico modo e non ci sono altri modi.
Yehia A.Salam,

sì sicuramente, lo farà.
Mohammad Mujassam,

Risposte:


17

Suppongo che AppFactory\Core\Api\SettingInterface::get()sia un endpoint REST. In tal caso, nei commenti phpdoc devi definire cosa restituirà. Il gestore Magento REST prenderà quel valore e lo elaborerà per rimuovere tutti i dati non necessari. Ciò che rimane verrà codificato in JSON, quindi in javascript puoi recuperarlo come hash JS già corretto e non come stringa con codifica json.

Il trucco di questi endpoint è che è necessario definire con precisione cosa restituirai. Magento non sarà in grado di elaborare qualcosa di così generale come "array" in cui imposterai quello che ti piace.

Nel tuo caso, per non provare a giocare con una serie di stringhe, sarà più semplice creare un'interfaccia che restituirà il tuo endpoint.

 <?php

 namespace AppFactory\Core\Api;

 /**
  * @api
  */

 interface SettingsInterface
 {


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

Ora quando restituisci un'istanza di un oggetto che implementa quell'interfaccia Magento leggerà i suoi phpdocs ed elaborerà i loro valori di ritorno. Ora crea un file AppFactory\Core\Api\Data\SettingsInterfacecome segue

<?php

namespace AppFactory\Core\Api\Data;

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

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

Ora quando crei una classe reale che implementerà quei 2 metodi get e la restituirai, AppFactory\Core\Api\SettingsInterface::get()Magento restituirà qualcosa del genere

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

Se vuoi un altro livello devi creare un'altra interfaccia che manterrà la settingsstruttura e la aggiungerà come valore di ritorno per AppFactory\Core\Api\Data\SettingsInterface::getSettings().

Se devi avere qualcosa che sarà dinamico e non vuoi o non puoi preparare questa interfaccia di struttura, puoi provare a impostare una stringa con codifica json e posizionarla @return stringper uno qualsiasi dei campi. In questo modo, tuttavia, dovrai assicurarti di decodificare manualmente quella stringa dopo aver ricevuto la risposta poiché la tua risposta sarà simile a questa:

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

e response.extra.testper poterlo utilizzare devi prima fare response.extra = JSON.parse(response.extra);manualmente


grazie per la spiegazione dettagliata, decodificare la stringa sul lato client non sembra naturale e scrivere tutte le classi per rappresentare ogni elemento è un incubo, c'è una quarta opzione per restituire semplicemente l'oggetto json senza dover scrivere le classi o restituire stringa, forse l'annotazione mista di ritorno, anche se l'ho provato ma purtroppo non ha funzionato. Sembra che finirò per racchiudere il json finale in un array ad esempio array ($ json_object), questo farà il trucco, ma non sembra anche naturale, dovendo raccogliere il primo elemento dell'array dal lato client
Yehia A.Salam il

È possibile creare una normale azione del controller che restituirà una stringa json e imposterà l'intestazione su text / json o application / json e verrà decodificata sul lato browser. Se vuoi usare rest api, allora non ho trovato nulla che potesse bypassare quella post elaborazione.
Zefiryn,

sì, il magento dovrebbe sostenerlo in qualche modo e sciogliersi senza imporre questo tipo di validazione sul tipo di ritorno
Yehia A.Salam

@ YehiaA.Salam Stavo controllando qualcosa. Non ho un modo semplice per testarlo, ma provo a usare "misto" come ritorno sui metodi in AppFactory\Core\Api\DataSettingsInterface. Se funziona, devi solo fare il primo livello della risposta.
Zefiryn,

Risposta molto utile
Pandurang,

5

Ho anche affrontato questo problema e, in alternativa alla soluzione proposta da @Zefiryn, ho risolto il problema racchiudendo i dati di ritorno in un array (o due). Si prega di considerare l'esempio seguente.

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

A causa di come Magento 2 consente matrici di contenuto misto come valori di ritorno, strutture di dati più complesse possono essere incorporate all'interno di altre matrici. L'esempio sopra fornisce la seguente risposta JSON (troncata per leggibilità).

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

Racchiuderlo in un singolo livello rimuove le chiavi dell'array e senza racchiuderlo in alcun array si ottiene un errore.

Comprensibilmente nulla di tutto ciò è l'ideale, ma questo approccio mi consente di controllare la coerenza nella struttura dei dati restituiti in una certa misura (struttura e tipi di dati previsti). Se si ha anche il controllo della scrittura di una libreria sul lato client, è possibile implementare un intercettore per rimuovere l'array esterno prima di restituirlo all'applicazione.


1

Per Magento 2.3.1, se è necessario ignorare la serializzazione dell'array, è possibile controllare questo file per aggiornare la logica principale. Penso che sia un buon punto di ingresso. In questo modo, si romperà sicuramente la compatibilità del sapone.

Inoltre su Magento 2.1.X, questo problema non si presenta se si inserisce anyType come tipo di ritorno.

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

Conferma riferimento modifica: https://github.com/magento/magento2/commit/6ba399cdaea5babb373a35e88131a8cbd041b0de#diff-53855cf24455a74e11a998ac1a871bb8

vendor / magento / quadro / riflessione / 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 sostituisci con:

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

1

So che questa domanda è piuttosto vecchia, ma esiste una soluzione abbastanza semplice per questo:

Devi sostituire Json-Renderer Magento\Framework\Webapi\Rest\Response\Renderer\Jsono scrivere un plugin per esso.

Ecco un piccolo esempio di plugin:

Nella 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>

Nella tua nuova classe plug-in 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);
}

}

Che succede qui:

  • Se la rest-route è "/ V1 / my / rest-route", viene utilizzato il nuovo metodo di rendering, il che significa semplicemente che i dati non sono codificati.
  • Un metodo di controllo aggiuntivo viene utilizzato per valutare se la stringa è realmente un oggetto json. Altrimenti (ad esempio, se la risposta è un errore 401, si tradurrebbe in un errore interno e restituire un codice di stato errato)
  • In questo modo, nel tuo metodo di riposo, puoi restituire una json-string, che non verrà modificata.

Naturalmente puoi anche scrivere il tuo Renderer, che ad esempio elabora un array.


0

Ho affrontato lo stesso problema e mi ci è voluto un po 'per capire il problema.

Magento fa qualcosa di strano in questo processore di output del servizio API Web che si trova in Magento \ Framework \ Webapi \ ServiceOutputProcessor In questa classe c'è un metodo chiamato convertValue (); quale è la ragione per le parentesi graffe [].

La migliore soluzione per me per risolvere il problema è stata quella di creare un plugin around per ovviare a questa condizione if in convertValue (); metodo in cui controllano se $ data è un array e fanno quelle cose strane con esso.

Ecco il mio esempio di codice del plugin: penso che tutti sappiano come creare un modulo base Magento 2, quindi inserisco qui solo il codice del plugin stesso.

  • Crea una cartella Plugin

  • Crea una 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);
    }
}
  • Crea la dichiarazione del plugin in 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>

Ciò dovrebbe risolvere il problema di output dell'array json nell'API Web

Spero che sia di aiuto

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.