json_decode до власного класу


Відповіді:


96

Не автоматично. Але ви можете зробити це старомодним маршрутом.

$data = json_decode($json, true);

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

Або ж ви можете зробити це більш автоматичним:

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

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

Редагувати : отримати трохи захоплюючого:

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

1
Мені подобаються ваші пропозиції, просто зауважте, що він не буде працювати з вкладеними об’єктами (крім STDClass або перетвореного об’єкта)
javier_domenech

34

Ми створили JsonMapper для автоматичного відображення об'єктів JSON у наших власних класах моделей. Він чудово працює з вкладеними / дочірніми об'єктами.

Він покладається лише на інформацію типу docblock для зіставлення, яку так чи інакше має більшість властивостей класу:

<?php
$mapper = new JsonMapper();
$contactObject = $mapper->map(
    json_decode(file_get_contents('http://example.org/contact.json')),
    new Contact()
);
?>

1
ОЦЕ ТАК! Це просто дивно.
vothaison

Чи можете ви пояснити ліцензію OSL3? Якщо я використовую JsonMapper на веб-сайті, чи повинен я звільнити вихідний код цього веб-сайту? Якщо я використовую JsonMapper в коді на пристрої, який я продаю, чи повинен весь код цього пристрою бути відкритим?
EricP

Ні, вам потрібно лише опублікувати зміни, які ви робите в самому JsonMapper.
cweiske

29

Ви можете це зробити - це загроза, але цілком можлива. Нам довелося це зробити, коли ми почали зберігати речі на дивані.

$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 

У наших тестах це було набагато швидше, ніж спроба перебирати всі змінні класу.

Увага: не працюватиме для вкладених об'єктів, окрім stdClass

Редагувати: майте на увазі джерело даних, настійно рекомендується не робити цього з ненадійними даними користувачів без дуже ретельного аналізу ризиків.


1
Це працює з інкапсульованими підкласами. Наприклад { "a": {"b":"c"} }, де об’єкт у aмає інший клас, а не просто асоціативний масив?
J-Rou

2
ні, json_decode створює об’єкти stdclass, включаючи допоміжні об’єкти, якщо ви хочете, щоб вони були чимось іншим, вам доведеться класти кожен об’єкт, як зазначено вище.
Джон Петтітт,

Дякую, це те, що я собі уявляв
J-Rou

Як щодо використання цього рішення на об’єктах, де конструктор має параметри. Я не можу змусити його працювати. Я сподівався, що хтось може вказати мені в правильному напрямку, щоб це рішення працювало з об’єктом, який має власний конструктор з параметрами.
Марко

Я пішов і вбудував це у функцію. Зверніть увагу, що він все ще не працює з підкласами. gist.github.com/sixpeteunder/2bec86208775f131ce686d42f18d8621
Пітер Ленджо

17

Ви можете скористатися бібліотекою серіалізатора Йоганнеса Шмітта .

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

В останній версії серіалізатора JMS синтаксис:

$serializer = SerializerBuilder::create()->build();
$object = $serializer->deserialize($jsonData, MyObject::class, 'json');

2
Синтаксис не залежить від версії JMS Serializer, а швидше від версії PHP - починаючи з PHP5.5, ви можете використовувати ::classпозначення: php.net/manual/en/…
Іван Ярич

4

Ви можете зробити обгортку для свого об’єкта і зробити обгортку схожою на сам об’єкт. І це буде працювати з багаторівневими об’єктами.

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

3

Ні, це неможливо з PHP 5.5.1.

Єдине, що можливо, це json_decodeповернути асоційовані масиви замість об’єктів StdClass.


3

Ви можете зробити це нижче.

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

?>

Для отримання додаткової інформації відвідайте create-custom-class-in-php-from-json-or-array


3

Я здивований, що про це ще ніхто не згадував.

Використовуйте компонент Symfony Serializer: https://symfony.com/doc/current/components/serializer.html

Серіалізація від Object до 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

Десериалізація з JSON на Object: (у цьому прикладі використовується XML лише для демонстрації гнучкості форматів)

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

2

Використовуйте відображення :

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

1

Як каже Гордон, це неможливо. Але якщо ви шукаєте спосіб отримати рядок, який можна декодувати як екземпляр класу give, ви можете замість цього використовувати серіалізацію та десеріалізацію.

class Foo
{

    protected $bar = 'Hello World';

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

}

$string = serialize(new Foo);

$foo = unserialize($string);
echo $foo->getBar();

Здається, це не стосується питання. Якщо це так, вам слід надати деякі пояснення.
Фелікс Клінг

1

Одного разу я створив для цього абстрактний базовий клас. Назвемо це JsonConvertible. Це повинно серіалізувати та десеріалізувати публічних членів. Це можливо за допомогою Reflection та пізнього статичного прив'язки.

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

Тільки з пам’яті, тому, мабуть, не бездоганний. Вам також доведеться виключити статичні властивості і може дати похідним класам шанс зробити деякі властивості ігнорованими при серіалізації до / з json. Сподіваюся, тим не менше ви зрозуміли цю ідею.


0

JSON - це простий протокол для передачі даних між різними мовами програмування (і це також підмножина JavaScript), який підтримує лише певні типи: числа, рядки, масиви / списки, об'єкти / дикти. Об'єкти - це просто карти ключ = значення, а масиви - це впорядковані списки.

Отже, немає можливості виразити власні об’єкти загальним чином. Рішенням є визначення структури, за якою ваші програми знатимуть, що це власний об’єкт.

Ось приклад:

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

Це може бути використано для створення екземпляра MyClassта встановлення полів aта fooдо 123та "bar".


6
Це може бути правдою, але питання не задає питання про загальне представлення об’єктів. Схоже, у нього є певна сумка JSON, яка відповідає певному класу на одному або обох кінцях. Немає жодної причини, чому ви не можете використовувати JSON як явну серіалізацію не-загальних іменованих класів таким чином. Присвоєння йому імені, як ви робите, добре, якщо ви хочете загальне рішення, але також немає нічого поганого в тому, щоб мати погоджений контракт на структуру JSON.
DougW

Це може працювати, якщо ви реалізуєте Serializable на кінці кодування та маєте умови на кінці декодування. Навіть може працювати з підкласами, якщо правильно організовано.
Пітер Ленджо

0

Я пішов далі і застосував відповідь Джона Петі як функцію ( суть ):

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

Це чудово працювало для мого випадку використання. Однак відповідь Євгена Афанасьєва видається мені однаково багатообіцяючою. Можливо, можливо, щоб ваш клас мав додатковий "конструктор", наприклад:

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

Це також надихається цією відповіддю .


-1

Я думаю, що найпростіший спосіб:

function mapJSON($json, $class){
$decoded_object = json_decode($json);
   foreach ($decoded_object as $key => $value) {
            $class->$key = $value;
   }
   return $class;}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.