json_decode到自定义类


87

是否可以将json字符串解码为stdClass以外的对象?


3
这么多年后,这没有什么新鲜的?
维克多

我找到了一个对我有用
Jason Liu

Answers:


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-扣肉

如何在构造函数具有参数的对象上使用此解决方案。我无法正常工作。我希望有人可以指出正确的方向,以使此解决方案与具有自定义构造函数和参数的对象一起使用。
马可(Marco)

我继续将其构建为一个函数。请注意,它仍然不适用于子类。gist.github.com/sixpeteunder/2bec86208775f131ce686d42f18d8621
Peter Lenjo

16

您可以使用J ohannes Schmitt的Serializer库

$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串行版本,而是基于PHP的版本-从PHP5.5开始,你可以使用::class符号:php.net/manual/en/...
伊万Yarych

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

?>

有关更多详细信息,请访问 php-from-json-or-array中的create-custom-class-in-php


3

我很惊讶,现在还没有人提到。

使用Symfony序列化器组件: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反序列化为对象:(此示例使用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

正如戈登所说,这是不可能的。但是,如果您正在寻找一种方法来获取可以作为给定类的实例进行解码的字符串,则可以使用序列化和反序列化。

class Foo
{

    protected $bar = 'Hello World';

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

}

$string = serialize(new Foo);

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

这似乎没有解决这个问题。如果是这样,则必须提供一些解释。
Felix Kling

1

为此,我曾经创建了一个抽象基类。我们称之为JsonConvertible。它应该对公众成员进行序列化和反序列化。使用反射和后期静态绑定是可能的。

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 /从json序列化时忽略某些属性。尽管如此,我希望您能明白。


0

JSON是在各种编程语言(也是JavaScript的子集)之间传输数据的简单协议,仅支持某些类型:数字,字符串,数组/列表,对象/字典。对象只是键=值映射,而数组是有序列表。

因此,无法以通用方式表达自定义对象。解决方案是定义一个结构,您的程序将在该结构中知道这是一个自定义对象。

这是一个例子:

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

这可用于创建和的实例MyClass并将字段aand设置foo123"bar"


6
这可能是正确的,但问题不是要以通用方式表示对象。听起来他有一个特定的JSON包,该包在一端或两端都映射到特定的类。没有理由不能以这种方式将JSON用作非泛型命名类的显式序列化。如果您想要一个通用的解决方案,那么在进行操作时将其命名是很好的,但是在JSON结构上达成一致的约定也没有错。
DougW

如果您在编码端实现了Serializable,并且在解码端具有了条件,那么这可能会起作用。如果组织得当,甚至可以使用子类。
Peter Lenjo

0

我继续将John Petit的答案实现为一个功能(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);  
}

这对于我的用例来说是完美的。但是,叶夫根尼·阿凡纳西耶夫(Yevgeniy Afanasyev)的回应对我来说同样有希望。可能会使您的班级有一个额外的“构造函数”,如下所示:

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;}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.