Magento 2: Wie kann ein JSON-Objekt von der API zurückgegeben werden?

8

Ich versuche, ein JSON-Objekt von einem meiner REST-Modelle zurückzugeben.

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

Was jedoch scheinbar trivial aussieht, ist nicht so einfach zu implementieren. Das Problem ist, dass ich nicht sicher bin, wie der Rückgabetyp in der Schnittstelle und im Modell aussehen soll.

<?php

namespace AppFactory\Core\Api;

/**
 * @api
 */

interface SettingsInterface
{


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

Die Objektklasse wird zurückgegeben

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

beim Aufruf der API. Die verfügbaren primitiven Typen int, number und array funktionieren bei mir nicht. Ich möchte nicht für jeden komplexen Typ eine Klasse erstellen, der ebenfalls zurückkehrt. Wie kann ich das machen?

Vielen Dank.

Yehia A. Salam
quelle
Json Daten sind Zeichenfolge für PHP, also machen Sie es Zeichenfolge
Mohammad Mujassam
Die @MohammadMujassam- Rückgabezeichenfolge im docBlock bewirkt, dass Magento das Ausgabeobjekt in eine Zeichenfolge konvertiert, die dem "mit umgekehrten Schrägstrichen und dem Umgeben des gesamten Objekts mit" entgeht. Ich habe diesen Artikel maxchadwick.xyz/blog/… durchgesehen und es wird vorgeschlagen, dass es keine andere Möglichkeit gibt, ein Objekt zurückzugeben, als ein Datenmodell dafür zu erstellen. Ich möchte jedoch nur sicherstellen, dass dies der einzige Weg ist und es keinen gibt andere Möglichkeiten.
Yehia A. Salam
Ja, auf jeden Fall wird es.
Mohammad Mujassam

Antworten:

17

Ich gehe davon aus, dass dies AppFactory\Core\Api\SettingInterface::get()ein REST-Endpunkt ist. In diesem Fall müssen Sie in phpdoc-Kommentaren definieren, was dies zurückgeben soll. Der Magento REST-Handler nimmt diesen Wert und verarbeitet ihn, um alle unnötigen Daten zu entfernen. Was übrig bleibt, wird in JSON codiert, sodass Sie es in Javascript als bereits richtigen JS-Hash und nicht als json-codierten String abrufen können.

Der Trick bei diesen Endpunkten besteht darin, dass Sie sehr genau definieren müssen, was Sie zurückgeben möchten. Magento ist nicht in der Lage, etwas so Allgemeines wie "Array" zu verarbeiten, in dem Sie festlegen können, was Sie möchten.

In Ihrem Fall ist es einfacher, eine Schnittstelle zu erstellen, die Ihr Endpunkt zurückgibt, um nicht mit einer Reihe von Zeichenfolgen zu spielen.

 <?php

 namespace AppFactory\Core\Api;

 /**
  * @api
  */

 interface SettingsInterface
 {


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

Wenn Sie nun eine Instanz eines Objekts zurückgeben, das diese Schnittstelle implementiert, liest Magento seine PHPDOCs und verarbeitet deren Rückgabewerte. Erstellen Sie nun eine Datei AppFactory\Core\Api\Data\SettingsInterfacewie folgt

<?php

namespace AppFactory\Core\Api\Data;

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

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

Wenn Sie nun eine tatsächliche Klasse erstellen, die diese AppFactory\Core\Api\SettingsInterface::get()beiden get-Methoden implementiert, und Sie sie zurückgeben, gibt magento so etwas wie zurück

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

Wenn Sie eine andere Ebene wünschen, müssen Sie eine andere Schnittstelle erstellen, die die settingsStruktur beibehält und als Rückgabewert für hinzufügt AppFactory\Core\Api\Data\SettingsInterface::getSettings().

Wenn Sie etwas benötigen, das dynamisch ist und Sie diese Strukturschnittstelle nicht vorbereiten möchten oder können, können Sie versuchen, eine json-codierte Zeichenfolge festzulegen und @return stringfür eines der Felder zu platzieren. Auf diese Weise müssen Sie jedoch sicherstellen, dass Sie diese Zeichenfolge nach Erhalt der Antwort manuell dekodieren, da Ihre Antwort dann folgendermaßen aussieht:

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

und um zu verwenden, müssen response.extra.testSie zuerst response.extra = JSON.parse(response.extra);manuell tun

Zefiryn
quelle
Vielen Dank für die ausführliche Erklärung. Das Dekodieren der Zeichenfolge auf der Clientseite fühlt sich nicht natürlich an. Das Schreiben aller Klassen zur Darstellung der einzelnen Elemente ist ein Albtraum. Gibt es eine vierte Option, um das JSON-Objekt einfach zurückzugeben, ohne die Klassen schreiben oder zurückgeben zu müssen? Zeichenfolge, vielleicht die gemischte Annotation return, obwohl ich es versucht habe, aber leider nicht funktioniert hat. Sieht aus wie ich das letzte json in einem Array zB Array umschließt ($ json_object) am Ende würde, wird dies den Trick tun, aber auch nicht natürlich anfühlen, mit dem ersten Elemente im Array von Client - Seite zu holen
Yehia A. Salam
Sie können eine reguläre Controller-Aktion erstellen, die einen JSON-String zurückgibt und den Header auf Text / JSON oder Anwendung / JSON setzt. Dieser wird auf der Browserseite dekodiert. Wenn Sie rest api verwenden möchten, habe ich nichts gefunden, was diese Nachbearbeitung umgehen könnte.
Zefiryn
Ja, es sieht so aus, Magento sollte dies in irgendeiner Weise unterstützen und sich lockern, ohne diese Art der Validierung für den Rückgabetyp zu erzwingen
Yehia A. Salam
@ YehiaA.Salam Ich habe etwas überprüft. Ich habe keine einfache Möglichkeit, dies zu testen, aber ich versuche, "gemischt" als Rendite für Methoden in zu verwenden AppFactory\Core\Api\DataSettingsInterface. Wenn dies funktioniert, müssen Sie nur die erste Ebene der Antwort ausführen.
Zefiryn
Sehr nützliche Antwort
Pandurang
5

Ich bin auch mit diesem Problem konfrontiert worden, und als Alternative zu der von @Zefiryn vorgeschlagenen Lösung habe ich es umgangen, indem ich die Rückgabedaten in ein Array (oder zwei) eingeschlossen habe. Bitte beachten Sie das folgende Beispiel.

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

Aufgrund der Tatsache, dass Magento 2 Arrays mit gemischtem Inhalt als Rückgabewerte zulässt, können komplexere Datenstrukturen in andere Arrays eingebettet werden. Das obige Beispiel liefert die folgende JSON-Antwort (aus Gründen der Lesbarkeit abgeschnitten).

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

Wenn Sie es in eine einzelne Ebene einschließen, werden die Schlüssel des Arrays entfernt, und ohne es in ein Array einzuschließen, führt dies zu einem Fehler.

Verständlicherweise ist nichts davon ideal, aber dieser Ansatz ermöglicht es mir, die Konsistenz in der zurückgegebenen Datenstruktur bis zu einem gewissen Grad (der erwarteten Datenstruktur und den erwarteten Datentypen) zu kontrollieren. Wenn Sie auch die Kontrolle über das Schreiben einer clientseitigen Bibliothek haben, kann ein Interceptor implementiert werden, um das äußere Array zu entfernen, bevor es an die Anwendung zurückgegeben wird.

Pawitk
quelle
1

Wenn Sie in Magento 2.3.1 die Array-Serialisierung umgehen müssen, können Sie diese Datei überprüfen, um die Kernlogik zu aktualisieren. Ich denke, es ist ein guter Einstiegspunkt. Auf diese Weise wird jedoch die Seifenkompatibilität mit Sicherheit beeinträchtigt.

Darüber hinaus tritt dieses Problem unter Magento 2.1.X nicht auf, wenn Sie anyType als Rückgabetyp angeben.

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

Änderungsreferenz festschreiben: https://github.com/magento/magento2/commit/6ba399cdaea5babb373a35e88131a8cbd041b0de#diff-53855cf24455a74e11a998ac1a871bb8

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

Und ersetzen durch:

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

Ich weiß, dass diese Frage ziemlich alt ist, aber es gibt eine ganz einfache Lösung dafür:

Sie müssen entweder den Json-Renderer ersetzen Magento\Framework\Webapi\Rest\Response\Renderer\Jsonoder ein Plugin dafür schreiben.

Hier ein kleines Beispiel für ein Plugin:

In deinem 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>

In deiner neuen Plugin-Klasse 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);
}

}

Was geschieht hier:

  • Wenn die Restroute "/ V1 / my / rest-route" ist, wird die neue Rendering-Methode verwendet, was einfach bedeutet, dass die Daten nicht codiert sind.
  • Eine zusätzliche Prüfmethode wird verwendet, um zu bewerten, ob die Zeichenfolge wirklich ein JSON-Objekt ist. Andernfalls (wenn die Antwort beispielsweise ein 401-Fehler ist, würde dies zu einem internen Fehler führen und einen falschen Statuscode zurückgeben).
  • Auf diese Weise können Sie in Ihrer Rest-Methode einen JSON-String zurückgeben, der nicht geändert wird.

Natürlich können Sie auch Ihren eigenen Renderer schreiben, der beispielsweise ein Array verarbeitet.

Codiga
quelle
0

Ich hatte das gleiche Problem und es dauerte eine Weile, bis ich das Problem herausgefunden hatte.

Magento macht etwas Seltsames in seinem Web-API-Service-Ausgabeprozessor, der sich unter Magento \ Framework \ Webapi \ ServiceOutputProcessor befindet. In dieser Klasse gibt es eine Methode namens convertValue (); Das ist der Grund für die [] Klammern.

Die beste Lösung für mich, um das Problem zu lösen, bestand darin, ein Around-Plugin zu erstellen, um diese if-Bedingung in convertValue () zu überwinden. Methode, bei der sie prüfen, ob $ data ein Array ist, und das seltsame Zeug damit machen.

Hier ist mein Beispielcode für das Plugin: Ich denke, jeder weiß, wie man ein Magento 2-Basismodul erstellt, daher poste ich hier nur den Code des Plugins.

  • Erstellen Sie einen Plugin-Ordner

  • Erstellen Sie eine Klasse 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);
    }
}
  • Erstellen Sie die Plugin-Deklaration 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>

Dies sollte das Problem der Array-JSON-Ausgabe in der Web-API lösen

Hoffe das hilft

Mage2Learn
quelle