Десериализация из JSON в PHP, с приведением? - PullRequest
19 голосов
/ 18 марта 2009

Предположим, у меня есть класс User со свойствами 'name' и 'password' и метод 'save'. При сериализации объекта этого класса в JSON через json_encode метод должным образом пропускается, и я получаю что-то вроде {'name': 'testName', 'password': 'testPassword'}.

Однако при десериализации с помощью json_decode я получаю объект StdClass вместо объекта User, что имеет смысл, но это означает, что у объекта отсутствует метод 'save'. Есть ли способ привести результирующий объект как пользователь или предоставить некоторую подсказку json_decode относительно того, какой тип объекта я ожидаю?

Ответы [ 12 ]

13 голосов
/ 17 сентября 2015

Старый вопрос, но, возможно, кто-то найдет это полезным.
Я создал абстрактный класс со статическими функциями, которые вы можете наследовать в своем объекте, чтобы десериализовать любой JSON в экземпляр наследующего класса.

abstract class JsonDeserializer
{
    /**
     * @param string|array $json
     * @return $this
     */
    public static function Deserialize($json)
    {
        $className = get_called_class();
        $classInstance = new $className();
        if (is_string($json))
            $json = json_decode($json);

        foreach ($json as $key => $value) {
            if (!property_exists($classInstance, $key)) continue;

            $classInstance->{$key} = $value;
        }

        return $classInstance;
    }
    /**
     * @param string $json
     * @return $this[]
     */
    public static function DeserializeArray($json)
    {
        $json = json_decode($json);
        $items = [];
        foreach ($json as $item)
            $items[] = self::Deserialize($item);
        return $items;
    }
}

Вы используете его, унаследовав его от класса, значения которого будут иметь ваш JSON:

class MyObject extends JsonDeserializer
{
    /** @var string */
    public $property1;

    /** @var string */
    public $property2;

    /** @var string */
    public $property3;

    /** @var array */
    public $array1;
}

Пример использования:

$objectInstance = new MyObject();
$objectInstance->property1 = 'Value 1';
$objectInstance->property2 = 'Value 2';
$objectInstance->property3 = 'Value 3';
$objectInstance->array1 = ['Key 1' => 'Value 1', 'Key 2' => 'Value 2'];

$jsonSerialized = json_encode($objectInstance);

$deserializedInstance = MyObject::Deserialize($jsonSerialized);

Вы можете использовать метод ::DeserializeArray, если ваш JSON содержит массив целевого объекта.

Здесь - это работающий образец.

9 голосов
/ 18 марта 2009

Краткий ответ: Нет (не то, что я знаю *)

Длинный ответ: json_encode только сериализует открытые переменные. Как видно из спецификации JSON , тип данных "function" отсутствует. Это обе причины, по которым ваши методы не сериализуются в ваш объект JSON.

Райан Грэм прав: единственный способ воссоздать эти объекты как экземпляры не-stdClass - это воссоздать их после десериализации.

Пример

<?php

class Person
{
    public $firstName;
    public $lastName;

    public function __construct( $firstName, $lastName )
    {
        $this->firstName = $firstName;
        $this->lastName = $lastName;
    }

    public static function createFromJson( $jsonString )
    {
        $object = json_decode( $jsonString );
        return new self( $object->firstName, $object->lastName );
    }

    public function getName()
    {
        return $this->firstName . ' ' . $this->lastName;
    }
}

$p = new Person( 'Peter', 'Bailey' );
$jsonPerson = json_encode( $p );

$reconstructedPerson = Person::createFromJson( $jsonPerson );

echo $reconstructedPerson->getName();

В качестве альтернативы, если вам не нужны данные в виде JSON, вы можете просто использовать обычную сериализацию и использовать ловушки __ sleep () и __wakeup () для дополнительной настройки. *

* В предыдущем моем собственном вопросе было предложено, чтобы вы могли реализовать некоторые интерфейсы SPL для настройки ввода / вывода json_encode (), но мои тесты показали, что это дикие гусыни погонь.

9 голосов
/ 18 марта 2009

Я думаю, что лучший способ справиться с этим - через конструктор, напрямую или через фабрику:

class User
{
   public $username;
   public $nestedObj; //another class that has a constructor for handling json
   ...

   // This could be make private if the factories below are used exclusively
   // and then make more sane constructors like:
   //     __construct($username, $password)
   public function __construct($mixed)
   {
       if (is_object($mixed)) {
           if (isset($mixed->username))
               $this->username = $mixed->username;
           if (isset($mixed->nestedObj) && is_object($mixed->nestedObj))
               $this->nestedObj = new NestedObject($mixed->nestedObj);
           ...
       } else if (is_array($mixed)) {
           if (isset($mixed['username']))
               $this->username = $mixed['username'];
           if (isset($mixed['nestedObj']) && is_array($mixed['nestedObj']))
               $this->nestedObj = new NestedObj($mixed['nestedObj']);
           ...
       }
   }
   ...

   public static fromJSON_by_obj($json)
   {
       return new self(json_decode($json));
   }

   public static fromJSON_by_ary($json)
   {
       return new self(json_decode($json, TRUE)); 
   }
}
3 голосов
/ 18 марта 2009

Вы можете создать FactoryClass некоторого вида:

function create(array $data)  
{
    $user = new User();
    foreach($data as $k => $v) {
        $user->$k = $v;
    }
    return $user;
}

Это не то решение, которое вы хотели, но оно выполняет вашу работу.

2 голосов
/ 16 июля 2012

Посмотрите на этот класс, я написал:

https://github.com/mindplay-dk/jsonfreeze/blob/master/mindplay/jsonfreeze/JsonSerializer.php

Он резервирует свойство объекта JSON с именем '#type' для хранения имени класса, и имеет некоторые ограничения, которые описаны здесь:

Сериализация / десериализация объекта-графа PHP в JSON

1 голос
/ 04 октября 2016

Может быть, поможет шаблон hydration.

По сути, вы создаете новый пустой объект (new User()), а затем заполняете свойства значениями из объекта StdClass. Например, у вас может быть метод hydrate в User.

Если возможно, в вашем случае вы можете заставить User constructor принимать необязательный параметр типа StdClass и принимать значения при создании экземпляра.

1 голос
/ 16 августа 2016

Немного поздно, но другой вариант - использовать сериализатор symfony для десериализации xml, json, чего угодно, до Object.

вот документация: http://symfony.com/doc/current/components/serializer.html#deserializing-in-an-existing-object

1 голос
/ 18 марта 2009

Мне известно, что JSON не поддерживает сериализацию функций, что вполне приемлемо и даже желательно. Мои классы в настоящее время используются как объекты-значения при взаимодействии с JavaScript, и функции не будут иметь никакого значения (а обычные функции сериализации не могут использоваться).

Однако, поскольку функциональность, относящаяся к этим классам, возрастает, инкапсуляция их служебных функций (таких как save () в данном случае пользователя) внутри реального класса имеет смысл для меня. Это означает, что они больше не строго оценивают объекты, и именно здесь я сталкиваюсь с моей вышеупомянутой проблемой.

У меня была идея, что имя класса указывалось бы внутри строки JSON, и, вероятно, получилось бы так:

$string = '{"name": "testUser", "password": "testPassword", "class": "User"}';
$object = json_decode ($string);
$user = ($user->class) $object;

И наоборот будет установка свойства класса во время / после json_encode. Я знаю, немного запутанно, но я просто пытаюсь сохранить связанный код вместе. Я, вероятно, в конечном итоге заберу свои вспомогательные функции из классов снова; модифицированный подход конструктора кажется немного непрозрачным и наталкивается на проблемы с вложенными объектами.

Я ценю это и любые будущие отзывы, однако.

0 голосов
/ 01 июня 2019

Старый вопрос, новый ответ.

А как насчет создания собственного интерфейса для соответствия JsonSerializable ? ;)

/**
 * Interface JsonUnseriablizable
 */
interface JsonUnseriablizable {
    /**
     * @param array $json
     */
    public function jsonUnserialize(array $json);
}

пример:

/**
 * Class Person
 */
class Person implements JsonUnseriablizable {

    public $name;
    public $dateOfBirth;

    /**
     * @param string $json
     */
    public function jsonUnserialize(array $json)
    {
        $this->name = $json['name'] ?? $this->name;
        $this->dateOfBirth = $json['date_of_birth'] ?? $this->dateOfBirth;
    }
}

$json = '{"name":"Bob","date_of_birth":"1970-01-01"}';

$person = new Person();
if($person instanceof JsonUnseriablizable){
    $person->jsonUnserialize(json_decode($json, true, 512, JSON_THROW_ON_ERROR));
}

var_dump($person->name);
0 голосов
/ 15 ноября 2018

Ниже приведен пример использования как статического (то есть вы знаете тип класса в коде), так и динамического (то есть вы знаете только тип класса во время выполнения) для десериализации JSON обратно в объект PHP:

код

<?php

class Car
{
    private $brand;
    private $model;
    private $year;

    public function __construct($brand, $model, $year)
    {
        $this->brand = $brand;
        $this->model = $model;
        $this->year = $year;
    }

    public function toJson()
    {
        $arr = array(
            'brand' => $this->brand,
            'model' => $this->model,
            'year' => $this->year,
        );

        return json_encode($arr);
    }

    public static function fromJson($json)
    {
        $arr = json_decode($json, true);

        return new self(
            $arr['brand'],
            $arr['model'],
            $arr['year']
        );
    }
}

// original object
echo 'car1: ';
$car1 = new Car('Hyundai', 'Tucson', 2010);
var_dump($car1);

// serialize
echo 'car1class: ';
$car1class = get_class($car1); // need the class name for the dynamic case below. this would need to be bundled with the JSON to know what kind of class to recreate.
var_dump($car1class);

echo 'car1json: ';
$car1Json = $car1->toJson();
var_dump($car1Json);

// static recreation with direct invocation. can only do this if you know the class name in code.
echo 'car2: ';
$car2 = Car::fromJson($car1Json);
var_dump($car2);

// dynamic recreation with reflection. can do this when you only know the class name at runtime as a string.
echo 'car3: ';
$car3 = (new ReflectionMethod($car1class, 'fromJson'))->invoke(null, $car1Json);
var_dump($car3);

выход

car1: object(Car)#1 (3) {
  ["brand":"Car":private]=>
  string(7) "Hyundai"
  ["model":"Car":private]=>
  string(6) "Tucson"
  ["year":"Car":private]=>
  int(2010)
}
car1class: string(3) "Car"
car1json: string(48) "{"brand":"Hyundai","model":"Tucson","year":2010}"
car2: object(Car)#2 (3) {
  ["brand":"Car":private]=>
  string(7) "Hyundai"
  ["model":"Car":private]=>
  string(6) "Tucson"
  ["year":"Car":private]=>
  int(2010)
}
car3: object(Car)#4 (3) {
  ["brand":"Car":private]=>
  string(7) "Hyundai"
  ["model":"Car":private]=>
  string(6) "Tucson"
  ["year":"Car":private]=>
  int(2010)
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...