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 с нашими собственными классами модели. Он отлично работает с вложенными / дочерними объектами.

Он полагается только на информацию о типе докблока для сопоставления, которая в любом случае есть у большинства свойств класса:

<?php
$mapper = new JsonMapper();
$contactObject = $mapper->map(
    json_decode(file_get_contents('http://example.org/contact.json')),
    new Contact()
);
?>
Cweiske
источник
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

Сериализация от объекта к 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;
}
luke23489
источник
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".

ThiefMaster
источник
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;}
JCoreX
источник