json_decode เป็นคลาสที่กำหนดเอง


91

เป็นไปได้ไหมที่จะถอดรหัสสตริง json ไปยังวัตถุอื่นที่ไม่ใช่ stdClass


4
ไม่มีอะไรใหม่หลังจากผ่านไปหลายปี?
วิกเตอร์

ฉันพบวิธีแก้ปัญหาที่เหมาะกับฉันstackoverflow.com/a/48838378/8138241
Jason Liu

คำตอบ:


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

36

เราสร้าง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

1
ไม่คุณต้องเผยแพร่การเปลี่ยนแปลงที่คุณทำกับ JsonMapper เท่านั้น
cweiske

29

คุณทำได้ - มันเป็นกากตะกอน แต่เป็นไปได้ทั้งหมด เราต้องทำเมื่อเราเริ่มจัดเก็บสิ่งต่างๆใน 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 

ในเกณฑ์มาตรฐานของเราวิธีนี้เร็วกว่าการพยายามวนซ้ำผ่านตัวแปรคลาสทั้งหมด

Caveat: จะไม่ทำงานกับวัตถุที่ซ้อนกันนอกเหนือจาก stdClass

แก้ไข: โปรดจำไว้ว่าแหล่งข้อมูลขอแนะนำอย่างยิ่งว่าอย่าทำเช่นนี้กับข้อมูลที่ไม่น่าเชื่อถือจากผู้ใช้โดยไม่มีการวิเคราะห์ความเสี่ยงอย่างรอบคอบ


1
สิ่งนี้ใช้ได้กับคลาสย่อยที่ห่อหุ้มไว้หรือไม่ เช่น{ "a": {"b":"c"} }ที่ออบเจ็กต์อยู่ในaคลาสอื่นไม่ใช่แค่อาร์เรย์ที่เชื่อมโยงกัน?
J-Rou

2
ไม่ json_decode สร้างออบเจ็กต์ stdclass รวมถึงอ็อบเจ็กต์ย่อยหากคุณต้องการให้มันเป็นอย่างอื่นคุณต้องแยกแต่ละอ็อบเจ็กต์ตามด้านบน
John Pettitt

ขอบคุณนั่นคือสิ่งที่ฉันจินตนาการ
J-Rou

วิธีใช้วิธีแก้ปัญหานี้บนวัตถุที่ตัวสร้างมีพารามิเตอร์ ฉันไม่สามารถทำให้มันทำงานได้ ฉันหวังว่าจะมีใครบางคนชี้ฉันไปในทิศทางที่ถูกต้องเพื่อให้โซลูชันนี้ทำงานกับออบเจ็กต์ที่มีตัวสร้างแบบกำหนดเองพร้อมพารามิเตอร์
Marco

ฉันดำเนินการต่อและสร้างสิ่งนี้ให้เป็นฟังก์ชัน โปรดทราบว่ายังใช้ไม่ได้กับคลาสย่อย gist.github.com/sixpeteunder/2bec86208775f131ce686d42f18d8621
Peter Lenjo

17

คุณสามารถใช้เจohannes ห้องสมุด

$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/…
Ivan 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

4

ฉันแปลกใจที่ไม่มีใครพูดถึงเรื่องนี้

ใช้ส่วนประกอบ 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

Deserializing จาก 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');


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


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 ได้คุณสามารถใช้serializeและ Unserialize แทนได้

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 ควรทำให้เป็นอนุกรมและ deserialize สมาชิกสาธารณะ สามารถทำได้โดยใช้ 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 ที่ส่วนท้ายของการเข้ารหัสและมีเงื่อนไขในการสิ้นสุดการถอดรหัส สามารถทำงานร่วมกับคลาสย่อยได้หากจัดอย่างถูกต้อง
Peter Lenjo

0

ฉันดำเนินการต่อและใช้คำตอบของ John Petitเป็นฟังก์ชัน ( ส่วนสำคัญ ):

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

นี่เป็นแรงบันดาลใจจากคำตอบนี้เช่นกัน

แก้ไข: ฉันใช้karriereat / json-decoderมาระยะหนึ่งแล้วและฉันก็ไม่มีปัญหากับมันเลย มีน้ำหนักเบาและขยายได้ง่ายมาก นี่คือตัวอย่างของการผูกที่ฉันเขียนเพื่อแยก JSON ลงในวัตถุ Carbon / CarbonImmutable

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.