json_decode zur benutzerdefinierten Klasse

86

Ist es möglich, einen JSON-String in ein anderes Objekt als stdClass zu dekodieren?

Tom
quelle
3
Nichts Neues nach so vielen Jahren?
Victor
Ich habe eine Lösung gefunden, die für mich funktioniert stackoverflow.com/a/48838378/8138241
Jason Liu

Antworten:

96

Nicht automatisch. Aber Sie können es auf der altmodischen Route tun.

$data = json_decode($json, true);

$class = new Whatever();
foreach ($data as $key => $value) $class->{$key} = $value;

Alternativ können Sie dies auch automatischer gestalten:

class Whatever {
    public function set($data) {
        foreach ($data AS $key => $value) $this->{$key} = $value;
    }
}

$class = new Whatever();
$class->set($data);

Edit : etwas schicker werden:

class JSONObject {
    public function __construct($json = false) {
        if ($json) $this->set(json_decode($json, true));
    }

    public function set($data) {
        foreach ($data AS $key => $value) {
            if (is_array($value)) {
                $sub = new JSONObject;
                $sub->set($value);
                $value = $sub;
            }
            $this->{$key} = $value;
        }
    }
}

// These next steps aren't necessary. I'm just prepping test data.
$data = array(
    "this" => "that",
    "what" => "who",
    "how" => "dy",
    "multi" => array(
        "more" => "stuff"
    )
);
$jsonString = json_encode($data);

// Here's the sweetness.
$class = new JSONObject($jsonString);
print_r($class);
Michael McTiernan
quelle
1
Ich mag Ihre Vorschläge, nur um zu bemerken, dass es nicht mit verschachtelten Objekten (außer STDClass oder dem konvertierten Objekt)
funktioniert
33

Wir haben JsonMapper erstellt , um JSON-Objekte automatisch unseren eigenen Modellklassen zuzuordnen . Es funktioniert gut mit verschachtelten / untergeordneten Objekten.

Für die Zuordnung werden nur Informationen zum Docblock-Typ verwendet, die die meisten Klasseneigenschaften ohnehin haben:

<?php
$mapper = new JsonMapper();
$contactObject = $mapper->map(
    json_decode(file_get_contents('http://example.org/contact.json')),
    new Contact()
);
?>
cweiske
quelle
1
BEEINDRUCKEND! Das ist einfach unglaublich.
Vothaison
Können Sie die OSL3-Lizenz erklären? Muss ich den Quellcode dieser Website freigeben, wenn ich JsonMapper auf einer Website verwende? Wenn ich JsonMapper in Code auf einem von mir verkauften Gerät verwende, muss der gesamte Code dieses Geräts Open Source sein?
EricP
Nein, Sie müssen nur die Änderungen veröffentlichen, die Sie an JsonMapper selbst vornehmen.
Cweiske
29

Sie können es tun - es ist ein Kludge, aber absolut möglich. Wir mussten tun, als wir anfingen, Dinge auf der Couchbase zu lagern.

$stdobj = json_decode($json_encoded_myClassInstance);  //JSON to stdClass
$temp = serialize($stdobj);                   //stdClass to serialized

// Now we reach in and change the class of the serialized object
$temp = preg_replace('@^O:8:"stdClass":@','O:7:"MyClass":',$temp);

// Unserialize and walk away like nothing happend
$myClassInstance = unserialize($temp);   // Presto a php Class 

In unseren Benchmarks war dies viel schneller als der Versuch, alle Klassenvariablen zu durchlaufen.

Vorsichtsmaßnahme: Funktioniert nicht für andere verschachtelte Objekte als stdClass

Bearbeiten: Beachten Sie die Datenquelle. Es wird dringend empfohlen, dies nicht mit nicht vertrauenswürdigen Daten von Benutzern zu tun, ohne die Risiken sehr sorgfältig zu analysieren.

John Pettitt
quelle
1
Funktioniert dies mit gekapselten Unterklassen? ZB { "a": {"b":"c"} }, wo das Objekt in aeiner anderen Klasse ist und nicht nur ein assoziatives Array?
J-Rou
2
nein, json_decode erstellt stdclass-Objekte, einschließlich Unterobjekte. Wenn Sie möchten, dass sie etwas anderes sind, müssen Sie jedes Objekt wie oben beschrieben kludgen.
John Pettitt
Danke, das habe ich mir vorgestellt
J-Rou
Wie wäre es mit der Verwendung dieser Lösung für Objekte, für die der Konstruktor Parameter hat? Ich kann es nicht zum Laufen bringen. Ich hatte gehofft, jemand könnte mich in die richtige Richtung weisen, damit diese Lösung mit einem Objekt funktioniert, das einen benutzerdefinierten Konstruktor mit Parametern hat.
Marco
Ich ging voran und baute dies in eine Funktion ein. Beachten Sie, dass es mit Unterklassen immer noch nicht funktioniert. gist.github.com/sixpeteunder/2bec86208775f131ce686d42f18d8621
Peter Lenjo
15

Sie können die Serializer-Bibliothek von J ohannes Schmitt verwenden .

$serializer = JMS\Serializer\SerializerBuilder::create()->build();
$object = $serializer->deserialize($jsonData, 'MyNamespace\MyObject', 'json');

In der neuesten Version des JMS-Serializers lautet die Syntax:

$serializer = SerializerBuilder::create()->build();
$object = $serializer->deserialize($jsonData, MyObject::class, 'json');
Malachi
quelle
2
Die Syntax ist nicht abhängig von der JMS Serializer-Version, sondern von der PHP-Version - ab PHP5.5 können Sie die ::classNotation verwenden: php.net/manual/en/…
Ivan Yarych
4

Sie können einen Wrapper für Ihr Objekt erstellen und den Wrapper so aussehen lassen, als wäre er das Objekt selbst. Und es funktioniert mit mehrstufigen Objekten.

<?php
class Obj
{
    public $slave;

    public function __get($key) {
        return property_exists ( $this->slave ,  $key ) ? $this->slave->{$key} : null;
    }

    public function __construct(stdClass $slave)
    {
        $this->slave = $slave;
    }
}

$std = json_decode('{"s3":{"s2":{"s1":777}}}');

$o = new Obj($std);

echo $o->s3->s2->s1; // you will have 777
Jewgenij Afanasjew
quelle
3

Nein, dies ist ab PHP 5.5.1 nicht möglich.

Das einzige, was möglich ist, ist, json_decodeassoziierte Arrays anstelle der StdClass-Objekte zurückzugeben.

Gordon
quelle
3

Sie können es auf folgende Weise tun ..

<?php
class CatalogProduct
{
    public $product_id;
    public $sku;
    public $name;
    public $set;
    public $type;
    public $category_ids;
    public $website_ids;

    function __construct(array $data) 
    {
        foreach($data as $key => $val)
        {
            if(property_exists(__CLASS__,$key))
            {
                $this->$key =  $val;
            }
        }
    }
}

?>

Weitere Informationen finden Sie unter create-custom-class-in-php-from-json-or-array

Jigarshahindia
quelle
1

Wie Gordon sagt, ist das nicht möglich. Wenn Sie jedoch nach einer Möglichkeit suchen, eine Zeichenfolge zu erhalten, die als Instanz einer give-Klasse dekodiert werden kann, können Sie stattdessen serialize und unserialize verwenden.

class Foo
{

    protected $bar = 'Hello World';

    function getBar() {
        return $this->bar;
    }

}

$string = serialize(new Foo);

$foo = unserialize($string);
echo $foo->getBar();
Francesco Terenzani
quelle
Dies scheint die Frage nicht zu beantworten. Wenn dies der Fall ist, müssen Sie eine Erklärung abgeben.
Felix Kling
1

Zu diesem Zweck habe ich einmal eine abstrakte Basisklasse erstellt. Nennen wir es JsonConvertible. Es sollte die öffentlichen Mitglieder serialisieren und deserialisieren. Dies ist durch Reflexion und späte statische Bindung möglich.

abstract class JsonConvertible {
   static function fromJson($json) {
       $result = new static();
       $objJson = json_decode($json);
       $class = new \ReflectionClass($result);
       $publicProps = $class->getProperties(\ReflectionProperty::IS_PUBLIC);
       foreach ($publicProps as $prop) {
            $propName = $prop->name;
            if (isset($objJson->$propName) {
                $prop->setValue($result, $objJson->$propName);
            }
            else {
                $prop->setValue($result, null);
            }
       }
       return $result;
   }
   function toJson() {
      return json_encode($this);
   }
} 

class MyClass extends JsonConvertible {
   public $name;
   public $whatever;
}
$mine = MyClass::fromJson('{"name": "My Name", "whatever": "Whatever"}');
echo $mine->toJson();

Nur aus dem Gedächtnis, also wahrscheinlich nicht fehlerfrei. Sie müssen auch statische Eigenschaften ausschließen und können abgeleiteten Klassen die Möglichkeit geben, einige Eigenschaften bei der Serialisierung zu / von json zu ignorieren. Ich hoffe, Sie haben trotzdem die Idee.

klawipo
quelle
1

Verwenden Reflexion :

function json_decode_object(string $json, string $class)
{
    $reflection = new ReflectionClass($class);
    $instance = $reflection->newInstanceWithoutConstructor();
    $json = json_decode($json, true);
    $properties = $reflection->getProperties();
    foreach ($properties as $key => $property) {
        $property->setAccessible(true);
        $property->setValue($instance, $json[$property->getName()]);
    }
    return $instance;
}
luke23489
quelle
0

JSON ist ein einfaches Protokoll zum Übertragen von Daten zwischen verschiedenen Programmiersprachen (und es ist auch eine Teilmenge von JavaScript), das nur bestimmte Typen unterstützt: Zahlen, Zeichenfolgen, Arrays / Listen, Objekte / Dikte. Objekte sind nur Schlüssel = Wert-Maps und Arrays sind geordnete Listen.

Es gibt also keine Möglichkeit, benutzerdefinierte Objekte generisch auszudrücken. Die Lösung besteht darin, eine Struktur zu definieren, in der Ihre Programme wissen, dass es sich um ein benutzerdefiniertes Objekt handelt.

Hier ist ein Beispiel:

{ "cls": "MyClass", fields: { "a": 123, "foo": "bar" } }

Dies könnte verwendet werden, um eine Instanz von zu erstellen MyClassund die Felder aund fooauf 123und zu setzen "bar".

DiebMaster
quelle
6
Dies mag zutreffen, aber es geht nicht darum, Objekte generisch darzustellen. Es hört sich so an, als hätte er eine bestimmte JSON-Tasche, die an einem oder beiden Enden einer bestimmten Klasse zugeordnet ist. Es gibt keinen Grund, warum Sie JSON auf diese Weise nicht als explizite Serialisierung nicht generischer benannter Klassen verwenden können. Es ist in Ordnung, es so zu benennen, wie Sie es tun, wenn Sie eine generische Lösung wünschen, aber es ist auch nichts Falsches daran, einen vereinbarten Vertrag über die JSON-Struktur zu haben.
DougW
Dies könnte funktionieren, wenn Sie Serializable auf der Codierungsseite implementieren und Bedingungen auf der Decodierungsseite haben. Könnte sogar mit Unterklassen arbeiten, wenn sie richtig organisiert sind.
Peter Lenjo
0

Ich ging voran und implementierte John Petits Antwort als Funktion ( Kern ):

function json_decode_to(string $json, string $class = stdClass::class, int $depth = 512, int $options = 0)
{
    $stdObj = json_decode($json, false, $depth, $options);
    if ($class === stdClass::class) return $stdObj;

    $count = strlen($class);
    $temp = serialize($stdObj);
    $temp = preg_replace("@^O:8:\"stdClass\":@", "O:$count:\"$class\":", $temp);
    return unserialize($temp);  
}

Dies funktionierte perfekt für meinen Anwendungsfall. Die Antwort von Jewgenij Afanasjew erscheint mir jedoch ebenso vielversprechend. Es könnte möglich sein, dass Ihre Klasse einen zusätzlichen "Konstruktor" hat, wie folgt:

public static function withJson(string $json) {
    $instance = new static();
    // Do your thing
    return $instance;
}

Dies ist auch von dieser Antwort inspiriert .

Peter Lenjo
quelle
0

Ich bin überrascht, dass dies noch niemand erwähnt hat.

Verwenden Sie die Symfony Serializer-Komponente: https://symfony.com/doc/current/components/serializer.html

Serialisierung von Objekt zu JSON:

use App\Model\Person;

$person = new Person();
$person->setName('foo');
$person->setAge(99);
$person->setSportsperson(false);

$jsonContent = $serializer->serialize($person, 'json');

// $jsonContent contains {"name":"foo","age":99,"sportsperson":false,"createdAt":null}

echo $jsonContent; // or return it in a Response

Deserialisierung von JSON zu Object: (In diesem Beispiel wird XML verwendet, um die Flexibilität von Formaten zu demonstrieren.)

use App\Model\Person;

$data = <<<EOF
<person>
    <name>foo</name>
    <age>99</age>
    <sportsperson>false</sportsperson>
</person>
EOF;

$person = $serializer->deserialize($data, Person::class, 'xml');
Lucas Bustamante
quelle
-2

Ich denke, der einfachste Weg ist:

function mapJSON($json, $class){
$decoded_object = json_decode($json);
   foreach ($decoded_object as $key => $value) {
            $class->$key = $value;
   }
   return $class;}
JCoreX
quelle