json_decode to custom class
Asked Answered
B

16

110

Is it possible to decode a json string to an object other than stdClass?

Bently answered 22/3, 2011 at 20:59 Comment(2)
Nothing new on this after so many years?Pod
I found a solution that works for me https://mcmap.net/q/196380/-convert-json-string-into-object-of-custom-class-instead-of-stdclassOrlina
S
116

Not automatically. But you can do it the old fashioned route.

$data = json_decode($json, true);

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

Or alternatively, you could make that more automatic:

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

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

Edit: getting a little fancier:

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);
Synsepalous answered 22/3, 2011 at 21:57 Comment(1)
I like you suggestions, just to remark that it won't work with nested objects (other than STDClass or the converted object)Theona
O
50

We built JsonMapper to map JSON objects onto our own model classes automatically. It works fine with nested/child objects.

It only relies on docblock type information for mapping, which most class properties have anyway:

<?php
$mapper = new JsonMapper();
$contactObject = $mapper->map(
    json_decode(file_get_contents('http://example.org/contact.json')),
    new Contact()
);
?>
Olfactory answered 27/1, 2014 at 14:49 Comment(3)
WOW! That's just amazing.Switchman
Can you explain the OSL3 license? If I use JsonMapper on a website, must I release the source code of that website? If I use JsonMapper in code on a device I sell, must all of that device's code be open source?Appetency
No, you only have to publish the changes you do to JsonMapper itself.Olfactory
F
30

You can do it - it's a kludge but totally possible. We had to do when we started storing things in couchbase.

$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 our benchmarks this was way faster than trying to iterate through all the class variables.

Caveat: Won't work for nested objects other than stdClass

Edit: keep in mind the data source, it's strongly recommended that you don't do this withe untrusted data from users without a very carful analysis of the risks.

Foolscap answered 18/12, 2013 at 9:30 Comment(5)
Does this work with encapsulated subclasses. E.g. { "a": {"b":"c"} }, where the object in a is of another class and not just an associative array?Cotta
no, json_decode creates stdclass objects, including sub objects, if you want to have them be anything else you have to kludge each object as above.Foolscap
How about usig this solution on objects where the constructor has parameters. I can't get it to work. I was hoping someone could point me in the right direction to make this solution work with an object that has a custom constructor with parameters.Romance
I went ahead and built this into a function. Note that it still doesn't work with subclasses. gist.github.com/sixpeteunder/2bec86208775f131ce686d42f18d8621Balough
That is such a hack! I love it, made me laugh and laugh. PHP typing (or the lack thereof) is my biggest issue with the language.Vermis
U
19

You could use Johannes Schmitt's Serializer library.

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

In the latest version of the JMS serializer the syntax is:

$serializer = SerializerBuilder::create()->build();
$object = $serializer->deserialize($jsonData, MyObject::class, 'json');
Unthinkable answered 19/1, 2015 at 16:58 Comment(1)
The syntax is not dependent on JMS Serializer version, but rather on PHP version - starting from PHP5.5 you can use ::class notation: php.net/manual/en/…Bashful
G
10

I'm surprised no one mentioned this, yet.

Use the Symfony Serializer component: https://symfony.com/doc/current/components/serializer.html

Serializing from Object to 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

Deserializing from JSON to Object: (this example uses XML just to demonstrate the flexibility of formats)

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');
Gamesmanship answered 24/6, 2020 at 21:56 Comment(1)
My colleague came to ask me about deserializing, and we found this very answer, wondering afterwards why did we not look up symfony docs first.Nerynesbit
S
6

You can do it in below way ..

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

?>

For more details visit create-custom-class-in-php-from-json-or-array

Solitta answered 18/8, 2016 at 14:24 Comment(0)
G
5

You can make a wrapper for your object and make the wrapper look like it is the object itself. And it will work with multilevel objects.

<?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
Gretchengrete answered 24/8, 2016 at 7:26 Comment(0)
T
4

Use Reflection:

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;
}
Threepiece answered 22/7, 2020 at 10:31 Comment(0)
D
3

No, this is not possible as of PHP 5.5.1.

The only thing possible is to have json_decode return associate arrays instead of the StdClass objects.

Decapolis answered 22/3, 2011 at 20:59 Comment(0)
A
1

As Gordon says is not possible. But if you are looking for a way to obtain a string that can be decoded as an instance of a give class you can use serialize and unserialize instead.

class Foo
{

    protected $bar = 'Hello World';

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

}

$string = serialize(new Foo);

$foo = unserialize($string);
echo $foo->getBar();
Aquamarine answered 22/3, 2011 at 21:54 Comment(1)
This does not seem to address the question. If it does, you have to provide some explanation.Birkett
D
1

I once created an abstract base class for this purpose. Let's call it JsonConvertible. It should serialize and deserialize the public members. This is possible using Reflection and late static binding.

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

Just from memory, so probably not flawless. You will also have to exclude static properties and may give derived classes the chance to make some properties ignored when serialized to/from json. I hope you get the idea, nonetheless.

Doyle answered 3/12, 2015 at 11:53 Comment(0)
C
0

JSON is a simple protocol to transfer data between various programming languages (and it's also a subset of JavaScript) which supports just certain types: numbers, strings, arrays/lists, objects/dicts. Objects are just key=value maps and Arrays are ordered lists.

So there is no way to express custom objects in a generic way. The solution is defining a structure where your program(s) will know that it's a custom object.

Here's an example:

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

This could be used to create an instance of MyClass and set the fields a and foo to 123 and "bar".

Canaliculus answered 22/3, 2011 at 22:39 Comment(2)
This may be true, but the question isn't asking about representing objects in a generic way. It sounds like he's got a specific JSON bag that maps to a specific class on one or both ends. There's no reason you can't use JSON as an explicit serialization of non-generic named classes in this way. Naming it as you're doing is fine if you want a generic solution, but there's also nothing wrong with having an agreed upon contract on the JSON structure.Dichroic
This could work if you implement Serializable on the encoding end, and have conditionals on the decoding end. Could even work with subclasses if organized properly.Balough
B
0

I went ahead and implemented John Petit's answer, as a function(gist):

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

This worked perfectly for my use case. However Yevgeniy Afanasyev's response seems equally promising to me. It could be possible to have your class have an extra "constructor", like so:

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

This is also inspired by this answer.

EDIT: I have been using karriereat/json-decoder for some time now, and I have had absolutely no trouble with it. It is lightweight and very easily extensible. Here's an example of a binding I wrote to deserialize JSON into a Carbon/CarbonImmutable object.

Balough answered 1/2, 2020 at 8:31 Comment(0)
D
0

All this here inspired me to a generic function:

function loadJSON($Obj, $json)
{
     $dcod = json_decode($json);
     $prop = get_object_vars ( $dcod );
     foreach($prop as $key => $lock)
     {
        if(property_exists ( $Obj ,  $key ))
        {
            if(is_object($dcod->$key))
            {
                loadJSON($Obj->$key, json_encode($dcod->$key));
            }
            else
            {
                $Obj->$key = $dcod->$key;
            }
        }
    }
    return $Obj;
}

to be called in class declaration:

class Bar{public $bar = " Boss";}
class Bas
{
    public $ber ;
    public $bas=" Boven"; 
    public function __construct() 
        {$this->ber = new Bar;}
}
class Baz
{
    public $bes ;
    public $baz=" Baaz";  
    public function __construct() 
        {$this->bes = new Bas;}
}

$Bazjson = '{"bes":{"ber":{"bar":"Baas"}}}';

$Bazobj = new Baz;

loadJSON($Bazobj, $Bazjson);
var_dump($Bazobj);
Delimitate answered 22/4, 2021 at 10:41 Comment(0)
S
0

This worked for me, especially for if you don't have setters or named properties in the target class

function cast($jsonstring, $class)
{
   //$class is a string like 'User'

    $json= json_decode($jsonstring,true);  //array
   
    $reflection = new ReflectionClass($class);
    $instance = $reflection->newInstanceWithoutConstructor();
    $keys = array_keys($json);

    foreach ($keys  as $key => $property) {
        $instance->{$property} =$json[$property];
    }

   // print_r($instance);

    return $instance;
}

Sartin answered 30/4, 2022 at 8:43 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Lal
S
0

Not directly, but if the class has a constructor with parameter names that match the keys in the JSON object, you can simply decode the JSON into an associative array and pass it to the constructor via the '...' (argument unpacking) operator:

<?php
class MyClass {
    public function __construct(
        public int $id,
        public string $name,
        public array $attributes,
    ){}
}
$json = '{"name":"foo","id":42,"attributes":{"color":"red"}}';
$object = new MyClass(...json_decode($json, true));
print_r($object);

Output:

MyClass Object
(
    [id] => 42
    [name] => foo
    [attributes] => Array
        (
            [color] => red
        )

)

However, in practice, there is often some additional mapping to do, especially sub-objects that need to be recursively decoded too. So usually it is better to have a static fromArray function in each class that pre-processes the json-decoded array before passing the result to the constructor:

class Part {
    public function __construct(public float $weight){}
    public static function fromArray(array $data): self {
        return new self(...$data);
    }
}
class System {
    public function __construct(
        public string $name,
        public Part $mainPart,
        public array $otherParts,
    ){}
    public static function fromArray(array $data): self {
        $data['mainPart'] = Part::fromArray($data['mainPart']);
        $data['otherParts'] = array_map(Part::fromArray(...), $data['otherParts']); // php 8.1
        return new self(...$data);
    }
}
$json = '{"name":"foo","mainPart":{"weight":2},"otherParts":[{"weight":1}, {"weight":0.5}]}';
$object = System::fromArray(json_decode($json, true));
Shandy answered 15/1, 2023 at 18:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.