json_decode do klasy niestandardowej

87

Czy można zdekodować ciąg json do obiektu innego niż stdClass?

Tomek
źródło
3
Nie ma w tym nic nowego po tylu latach?
Victor
Znalazłem rozwiązanie, które u mnie działa stackoverflow.com/a/48838378/8138241
Jason Liu

Odpowiedzi:

96

Nie automatycznie. Ale możesz to zrobić staromodną trasą.

$data = json_decode($json, true);

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

Lub możesz zrobić to bardziej automatycznie:

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

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

Edycja : trochę bardziej wyszukana:

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
źródło
1
Lubię cię sugestii, żeby zauważyć, że to nie będzie działać z obiektów zagnieżdżonych (innych niż stdClass lub przekształconego obiektu)
javier_domenech
34

Zbudowaliśmy JsonMapper, aby automatycznie mapować obiekty JSON na nasze własne klasy modelu. Działa dobrze z obiektami zagnieżdżonymi / podrzędnymi.

Opiera się tylko na informacjach o typie docblock do mapowania, które i tak ma większość właściwości klas:

<?php
$mapper = new JsonMapper();
$contactObject = $mapper->map(
    json_decode(file_get_contents('http://example.org/contact.json')),
    new Contact()
);
?>
cweiske
źródło
1
ŁAŁ! To po prostu niesamowite.
vothaison
Czy możesz wyjaśnić licencję OSL3? Jeśli używam JsonMapper w witrynie internetowej, czy muszę udostępniać kod źródłowy tej witryny? Jeśli używam JsonMapper w kodzie na sprzedawanym przeze mnie urządzeniu, czy cały kod tego urządzenia musi być open source?
EricP
Nie, musisz tylko opublikować zmiany, które robisz, w samym JsonMapper.
cweiske
29

Możesz to zrobić - to niewyraźne, ale całkowicie możliwe. Musieliśmy to zrobić, kiedy zaczęliśmy przechowywać rzeczy na kanapie.

$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 

W naszych testach porównawczych było to znacznie szybsze niż próba iteracji przez wszystkie zmienne klas.

Uwaga: nie będzie działać w przypadku obiektów zagnieżdżonych innych niż stdClass

Edycja: pamiętaj o źródle danych, zdecydowanie zaleca się, aby nie robić tego z niezaufanymi danymi od użytkowników bez bardzo dokładnej analizy ryzyka.

John Pettitt
źródło
1
Czy to działa z hermetyzowanymi podklasami. Np. { "a": {"b":"c"} }Gdzie obiekt anależy do innej klasy, a nie tylko do tablicy asocjacyjnej?
J-Rou
2
nie, json_decode tworzy obiekty stdclass, w tym obiekty podrzędne, jeśli chcesz, aby były czymkolwiek innym, musisz skleić każdy obiekt jak powyżej.
John Pettitt
Dziękuję, tak sobie wyobrażałem
J-Rou
Co powiesz na użycie tego rozwiązania na obiektach, w których konstruktor ma parametry. Nie mogę zmusić tego do pracy. Miałem nadzieję, że ktoś wskaże mi właściwy kierunek, aby to rozwiązanie działało z obiektem, który ma niestandardowego konstruktora z parametrami.
Marco
Poszedłem do przodu i wbudowałem to w funkcję. Zauważ, że nadal nie działa z podklasami. gist.github.com/sixpeteunder/2bec86208775f131ce686d42f18d8621
Peter Lenjo
17

Możesz użyć biblioteki Serializer J ohannesa Schmitta .

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

W najnowszej wersji serializatora JMS składnia jest następująca:

$serializer = SerializerBuilder::create()->build();
$object = $serializer->deserialize($jsonData, MyObject::class, 'json');
Malachiasz
źródło
2
Składnia nie zależy od wersji JMS Serializer, ale raczej od wersji PHP - zaczynając od PHP5.5 możesz użyć ::classnotacji: php.net/manual/en/ ...
Ivan Yarych
4

Możesz utworzyć opakowanie dla swojego obiektu i sprawić, że będzie wyglądać tak, jakby był samym obiektem. I będzie działać z obiektami wielopoziomowymi.

<?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
Yevgeniy Afanasyev
źródło
3

Nie, od wersji PHP 5.5.1 nie jest to możliwe.

Jedyną możliwą rzeczą jest json_decodezwrócenie tablic skojarzonych zamiast obiektów StdClass.

Gordon
źródło
3

Możesz to zrobić poniżej.

<?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;
            }
        }
    }
}

?>

Aby uzyskać więcej informacji, odwiedź stronę create-custom-class-in-php-from-json-or-array

jigarshahindia
źródło
3

Dziwię się, że nikt jeszcze o tym nie wspomniał.

Użyj komponentu Symfony Serializer: https://symfony.com/doc/current/components/serializer.html

Serializacja z obiektu do 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

Deserializacja z JSON do obiektu: (w tym przykładzie użyto XML tylko po to, aby zademonstrować elastyczność formatów)

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
źródło
2

Użyj odbicia :

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
źródło
1

Jak mówi Gordon, nie jest to możliwe. Ale jeśli szukasz sposobu na uzyskanie ciągu, który można zdekodować jako wystąpienie klasy podającej , możesz zamiast tego użyć serializacji i odserializacji.

class Foo
{

    protected $bar = 'Hello World';

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

}

$string = serialize(new Foo);

$foo = unserialize($string);
echo $foo->getBar();
Francesco Terenzani
źródło
To nie wydaje się odpowiadać na pytanie. Jeśli tak, musisz podać jakieś wyjaśnienie.
Felix Kling
1

W tym celu stworzyłem kiedyś abstrakcyjną klasę bazową. Nazwijmy to JsonConvertible. Powinien serializować i deserializować członków publicznych. Jest to możliwe dzięki zastosowaniu Reflection i późnego wiązania statycznego.

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();

Tylko z pamięci, więc prawdopodobnie nie bez wad. Będziesz także musiał wykluczyć właściwości statyczne i może dać klasom pochodnym szansę na zignorowanie niektórych właściwości podczas serializacji do / z json. Mam nadzieję, że mimo wszystko wpadniesz na pomysł.

klawipo
źródło
0

JSON to prosty protokół do przesyłania danych między różnymi językami programowania (a także podzbiorem JavaScript), który obsługuje tylko niektóre typy: liczby, ciągi znaków, tablice / listy, obiekty / dykty. Obiekty to po prostu mapy klucz = wartość, a tablice to uporządkowane listy.

Dlatego nie ma możliwości wyrażenia obiektów niestandardowych w sposób ogólny. Rozwiązaniem jest zdefiniowanie struktury, w której program (y) będzie wiedział, że jest to obiekt niestandardowy.

Oto przykład:

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

Można to wykorzystać do utworzenia wystąpienia MyClassi ustawienia pól aoraz foodo 123i "bar".

ThiefMaster
źródło
6
Może to prawda, ale pytanie nie dotyczy przedstawiania obiektów w ogólny sposób. Wygląda na to, że ma określoną torbę JSON, która mapuje do określonej klasy na jednym lub obu końcach. Nie ma powodu, dla którego nie można w ten sposób używać formatu JSON jako jawnej serializacji nieogólnych nazwanych klas. Nazwanie go tak, jak to robisz, jest w porządku, jeśli chcesz uzyskać ogólne rozwiązanie, ale nie ma też nic złego w posiadaniu uzgodnionej umowy dotyczącej struktury JSON.
DougW
Może to zadziałać, jeśli zaimplementujesz Serializable na końcu kodowania i masz warunki warunkowe na końcu dekodowania. Może nawet działać z podklasami, jeśli są odpowiednio zorganizowane.
Peter Lenjo
0

Poszedłem dalej i zaimplementowałem odpowiedź Johna Petita jako funkcję ( sedno ):

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

To działało idealnie w moim przypadku użycia. Jednak odpowiedź Jewgienija Afanasjewa wydaje mi się równie obiecująca. Możliwe, że Twoja klasa będzie miała dodatkowego „konstruktora”, na przykład:

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

To również jest inspirowane tą odpowiedzią .

Peter Lenjo
źródło
-1

Myślę, że najprostszy sposób to:

function mapJSON($json, $class){
$decoded_object = json_decode($json);
   foreach ($decoded_object as $key => $value) {
            $class->$key = $value;
   }
   return $class;}
JCoreX
źródło