Magento 2: Comment retourner un objet JSON depuis l'API?


8

J'essaie de renvoyer un objet JSON à partir de l'un de mes modèles REST, quelque chose comme ceci:

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

Cependant, ce qui semble banal n'est pas si simple à mettre en œuvre. Le problème est que je ne sais pas quel doit être le type de retour dans l'interface et le modèle.

<?php

namespace AppFactory\Core\Api;

/**
 * @api
 */

interface SettingsInterface
{


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

La classe d'objets retournera

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

lors de l'appel de l'API. Les types primitifs disponibles int, nombre et tableau ne fonctionneront pas pour moi. Je ne veux pas créer de classe pour chaque type complexe qui revient également. Comment puis-je faire ceci?

Merci.


les données json sont des chaînes pour php alors faites-en des chaînes
Mohammad Mujassam

@MohammadMujassam retourne la chaîne dans le docBlock, Magento convertira l'objet de sortie en chaîne échappant au "avec des barres obliques inverses et entourant l'objet entier avec". J'ai parcouru cet article maxchadwick.xyz/blog/… et il suggère qu'il n'y a pas d'autre moyen de retourner un objet que de créer un modèle de données pour celui-ci, mais je veux juste m'assurer que c'est la seule façon et qu'il n'y a pas d'autres moyens.
Yehia A.Salam

oui, certainement.
Mohammad Mujassam

Réponses:


17

Je suppose que AppFactory\Core\Api\SettingInterface::get()c'est un point de terminaison REST. Dans ce cas, dans les commentaires phpdoc, vous devez définir ce que cela renverra. Le gestionnaire Magento REST prendra cette valeur et la traitera pour supprimer toutes les données inutiles. Ce qui reste sera encodé en JSON, donc en javascript, vous pouvez le récupérer en tant que hachage JS déjà correct et non en chaîne encodée en json.

L'astuce à propos de ces points de terminaison est que vous devez définir très précisément ce que vous retournerez. Magento ne sera pas en mesure de traiter quelque chose d'aussi général que "array" où vous définirez ce que vous voulez.

Dans votre cas, afin de ne pas essayer de jouer avec un tableau de chaînes, il sera plus facile de créer une interface que votre endpoint retournera.

 <?php

 namespace AppFactory\Core\Api;

 /**
  * @api
  */

 interface SettingsInterface
 {


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

Maintenant, lorsque vous retournez une instance d'un objet implémentant cette interface, Magento lira ses phpdocs et traitera leurs valeurs de retour. Maintenant, créez un fichier AppFactory\Core\Api\Data\SettingsInterfacecomme suit

<?php

namespace AppFactory\Core\Api\Data;

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

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

Maintenant, lorsque vous créez une classe réelle qui implémentera ces 2 méthodes get et que vous la retournerez, AppFactory\Core\Api\SettingsInterface::get()magento renverra quelque chose comme

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

Si vous voulez un autre niveau, vous devez créer une autre interface qui gardera la settingsstructure et l'ajoutera comme valeur de retour pour AppFactory\Core\Api\Data\SettingsInterface::getSettings().

Si vous avez besoin d'avoir quelque chose qui sera dynamique et que vous ne voulez pas ou ne pouvez pas préparer cette interface de structure, vous pouvez essayer de définir une chaîne codée json et de la placer @return stringpour l'un des champs. De cette façon, cependant, vous devrez vous assurer de décoder manuellement cette chaîne après avoir reçu la réponse, car votre réponse ressemblera à ceci:

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

et pour l'utiliser, response.extra.testvous devrez d'abord le faire response.extra = JSON.parse(response.extra);manuellement


merci pour l'explication détaillée, décoder la chaîne côté client ne semble pas naturel, et écrire toutes les classes pour représenter chaque élément est un cauchemar, existe-t-il une quatrième option pour simplement renvoyer l'objet json sans avoir à écrire les classes ou à retourner chaîne, peut-être l'annotation mixte de retour, bien que je l'aie essayé mais n'a pas fonctionné malheureusement. On dirait que je finirai par enfermer le json final dans un tableau, par exemple un tableau ($ json_object), cela fera l'affaire, mais ne semble pas naturel aussi, devoir ramasser le premier élément du tableau du côté client
Yehia A.Salam

Vous pouvez créer une action de contrôleur régulière qui renverra la chaîne json et définira l'en-tête sur text / json ou application / json et elle sera décodée du côté du navigateur. Si vous souhaitez utiliser l'api de repos, je n'ai rien trouvé qui pourrait contourner ce post-traitement.
Zefiryn

oui, magento devrait prendre en charge cela d'une certaine manière et se détendre sans appliquer ce type de validation sur le type de retour
Yehia A.Salam

@ YehiaA.Salam Je vérifiais quelque chose. Je n'ai pas de moyen facile de tester cela, mais essayez d'utiliser "mixte" comme retour sur les méthodes dans AppFactory\Core\Api\DataSettingsInterface. Si cela fonctionne, il vous suffit de faire le premier niveau de la réponse.
Zefiryn

Réponse très utile
Pandurang

5

J'ai également fait face à ce problème, et comme alternative à la solution proposée par @Zefiryn, j'ai travaillé autour de lui en enfermant les données de retour dans un tableau (ou deux). Veuillez considérer l'exemple ci-dessous.

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

En raison de la façon dont Magento 2 autorise les tableaux de contenu mixte en tant que valeurs de retour, des structures de données plus complexes peuvent être intégrées dans d'autres tableaux. L'exemple ci-dessus donne la réponse JSON suivante (tronquée pour plus de lisibilité).

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

L'enfermer dans une seule couche supprime les clés du tableau, et sans l'enfermer dans aucun tableau entraîne une erreur.

Naturellement, rien de tout cela n'est idéal, mais cette approche me permet de contrôler la cohérence de la structure de données retournée dans une certaine mesure (la structure et les types de données attendus). Si vous contrôlez également l'écriture d'une bibliothèque côté client, un intercepteur peut être implémenté pour supprimer le tableau externe avant de le renvoyer à l'application.


1

Pour Magento 2.3.1, si vous devez contourner la sérialisation de la baie, vous pouvez vérifier ce fichier pour mettre à jour la logique principale. Je pense que c'est un bon point d'entrée. Mais en faisant cela, vous casserez la compatibilité Soap à coup sûr.

De plus sur Magento 2.1.X, vous n'avez pas ce problème si vous mettez anyType comme type de retour.

Référence Github: https://github.com/magento/magento2/blob/2.3-develop/lib/internal/Magento/Framework/Reflection/TypeCaster.php

Validation de la référence de changement: https://github.com/magento/magento2/commit/6ba399cdaea5babb373a35e88131a8cbd041b0de#diff-53855cf24455a74e11a998ac1a871bb8

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

Et remplacez par:

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

Je sais que cette question est assez ancienne, mais il existe une solution assez simple à cela:

Vous devez soit remplacer Json-Renderer, Magento\Framework\Webapi\Rest\Response\Renderer\Jsonsoit écrire un plugin pour celui-ci.

Voici un petit exemple de plugin:

Dans ton 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>

Dans votre nouvelle classe de plugins 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);
}

}

Que se passe t-il ici:

  • Si la route de repos est "/ V1 / ma / route de repos", la nouvelle méthode de rendu est utilisée, ce qui signifie simplement que les données ne sont pas codées.
  • Une méthode de vérification supplémentaire est utilisée pour évaluer si la chaîne est vraiment un objet json. Sinon (par exemple, si la réponse est une erreur 401, cela entraînerait une erreur interne et donnerait un mauvais code d'état)
  • De cette façon, dans votre méthode de repos, vous pouvez rendre une chaîne json, qui ne sera pas modifiée.

Bien sûr, vous pouvez également écrire votre propre moteur de rendu, qui traite un tableau par exemple.


0

J'ai fait face au même problème et il m'a fallu un certain temps pour comprendre le problème.

Magento fait quelque chose de bizarre dans le processeur de sortie du service API Web qui se trouve sous Magento \ Framework \ Webapi \ ServiceOutputProcessor. Dans cette classe, il existe une méthode nommée convertValue (); ce qui est la raison des accolades [].

La meilleure solution pour moi de résoudre le problème était de créer un plugin around pour surmonter cette condition if dans convertValue (); méthode où ils vérifient si $ data est un tableau et font ce truc bizarre avec.

Voici mon exemple de code du plugin: je pense que tout le monde sait comment créer un module de base Magento 2, donc je ne poste que le code du plugin lui-même ici.

  • Créer un dossier de plugin

  • Créez une 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);
    }
}
  • Créez la déclaration du plugin dans 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>

Cela devrait résoudre le problème de sortie du tableau json dans l'API Web

J'espère que cela t'aides

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.