Membuat serial objek PHP ke JSON

101

Jadi saya berkeliling php.net untuk informasi tentang serialisasi objek PHP ke JSON, ketika saya menemukan Antarmuka JsonSerializable baru . Ini hanya PHP> = 5.4 , dan saya menjalankan di lingkungan 5.3.x.

Bagaimana fungsionalitas semacam ini mencapai PHP <5,4 ?

Saya belum banyak bekerja dengan JSON, tetapi saya mencoba mendukung lapisan API dalam sebuah aplikasi, dan membuang objek data ( yang jika tidak akan dikirim ke tampilan ) ke dalam JSON akan menjadi sempurna.

Jika saya mencoba untuk membuat serial objek secara langsung, ia mengembalikan string JSON kosong; yang karena saya berasumsi json_encode()tidak tahu apa yang harus dilakukan dengan objek tersebut. Haruskah saya rekursif mengurangi objek ke array, dan kemudian encode itu ?


Contoh

$data = new Mf_Data();
$data->foo->bar['hello'] = 'world';

echo json_encode($data) menghasilkan benda kosong:

{}

var_dump($data) namun, berfungsi seperti yang diharapkan:

object(Mf_Data)#1 (5) {
  ["_values":"Mf_Data":private]=>
  array(0) {
  }
  ["_children":"Mf_Data":private]=>
  array(1) {
    [0]=>
    array(1) {
      ["foo"]=>
      object(Mf_Data)#2 (5) {
        ["_values":"Mf_Data":private]=>
        array(0) {
        }
        ["_children":"Mf_Data":private]=>
        array(1) {
          [0]=>
          array(1) {
            ["bar"]=>
            object(Mf_Data)#3 (5) {
              ["_values":"Mf_Data":private]=>
              array(1) {
                [0]=>
                array(1) {
                  ["hello"]=>
                  string(5) "world"
                }
              }
              ["_children":"Mf_Data":private]=>
              array(0) {
              }
              ["_parent":"Mf_Data":private]=>
              *RECURSION*
              ["_key":"Mf_Data":private]=>
              string(3) "bar"
              ["_index":"Mf_Data":private]=>
              int(0)
            }
          }
        }
        ["_parent":"Mf_Data":private]=>
        *RECURSION*
        ["_key":"Mf_Data":private]=>
        string(3) "foo"
        ["_index":"Mf_Data":private]=>
        int(0)
      }
    }
  }
  ["_parent":"Mf_Data":private]=>
  NULL
  ["_key":"Mf_Data":private]=>
  NULL
  ["_index":"Mf_Data":private]=>
  int(0)
}

Tambahan

1)

Jadi ini adalah toArray()fungsi yang saya buat untuk Mf_Datakelas:

public function toArray()
{
    $array = (array) $this;
    array_walk_recursive($array, function (&$property) {
        if ($property instanceof Mf_Data) {
            $property = $property->toArray();
        }
    });
    return $array;
}

Namun karena Mf_Dataobjek juga memiliki referensi ke objek induknya ( berisi ), ini gagal dengan rekursi. Bekerja seperti pesona meskipun ketika saya menghapus _parentreferensi.

2)

Sebagai tindak lanjut, fungsi terakhir untuk mengubah objek simpul pohon kompleks yang saya gunakan adalah:

// class name - Mf_Data
// exlcuded properties - $_parent, $_index
public function toArray()
{
    $array = get_object_vars($this);
    unset($array['_parent'], $array['_index']);
    array_walk_recursive($array, function (&$property) {
        if (is_object($property) && method_exists($property, 'toArray')) {
            $property = $property->toArray();
        }
    });
    return $array;
}

3)

Saya menindaklanjuti lagi, dengan implementasi yang sedikit lebih bersih. Menggunakan antarmuka untuk instanceofpemeriksaan tampaknya jauh lebih bersih daripada method_exists()( namun demikian method_exists()juga pewarisan / implementasi lintas sektoral ).

Penggunaannya juga unset()terlihat agak berantakan, dan tampaknya logika harus direfraktorkan ke metode lain. Namun, implementasi ini menyalin array properti ( karenaarray_diff_key ), jadi sesuatu yang perlu dipertimbangkan.

interface ToMapInterface
{

    function toMap();

    function getToMapProperties();

}

class Node implements ToMapInterface
{

    private $index;
    private $parent;
    private $values = array();

    public function toMap()
    {
        $array = $this->getToMapProperties();
        array_walk_recursive($array, function (&$value) {
            if ($value instanceof ToMapInterface) {
                $value = $value->toMap();
            }
        });
        return $array;
    }

    public function getToMapProperties()
    {
        return array_diff_key(get_object_vars($this), array_flip(array(
            'index', 'parent'
        )));
    }

}
Dan Lugg
sumber
4
+1 Pertanyaan bagus, belum mengetahui fitur ini.
ambil
@takeshin - Ya, edit tanggal di halaman doc adalah 4 hari yang lalu. Saya senang melihatnya!
Dan Lugg
2
Untuk referensi bagi orang lain yang melihat ini, json_encode dapat menangani objek dengan baik. Namun, itu hanya mengkodekan anggota publik dari objek itu. Jadi jika Anda memiliki variabel kelas terproteksi atau privat, maka Anda memerlukan salah satu metode yang diposting, atau JsonSerializable.
Matthew Herbst
@TwitAsahOtak. Pertanyaan lama sudah tua sekarang, dan <5,4 sebenarnya bukan pilihan lagi (atau setidaknya seharusnya tidak) PastiJsonSerializable
Dan Lugg

Jawaban:

45

edit : saat ini 2016-09-24, dan PHP 5.4 telah dirilis 2012-03-01, dan dukungan telah berakhir 2015-09-01. Namun, jawaban ini tampaknya mendapatkan suara positif. Jika Anda masih menggunakan PHP <5.4, Anda menimbulkan risiko keamanan dan membahayakan proyek Anda . Jika Anda tidak memiliki alasan kuat untuk tetap menggunakan <5.4, atau bahkan sudah menggunakan versi> = 5.4, jangan gunakan jawaban ini , dan cukup gunakan PHP> = 5.4 (atau, Anda tahu, yang terbaru) dan terapkan antarmuka JsonSerializable


Anda akan mendefinisikan sebuah fungsi, misalnya bernama getJsonData();, yang akan mengembalikan array, stdClassobjek, atau objek lain dengan parameter yang terlihat daripada yang privat / dilindungi, dan melakukan a json_encode($data->getJsonData());. Intinya, terapkan fungsi dari 5.4, tetapi panggil dengan tangan.

Sesuatu seperti ini akan berfungsi, seperti get_object_vars()yang dipanggil dari dalam kelas, memiliki akses ke variabel privat / dilindungi:

function getJsonData(){
    $var = get_object_vars($this);
    foreach ($var as &$value) {
        if (is_object($value) && method_exists($value,'getJsonData')) {
            $value = $value->getJsonData();
        }
    }
    return $var;
}
Wrikken
sumber
2
Terima kasih @Wrikken - Apakah ada pintasan untuk mengurangi objek, objek yang terkandung di dalamnya ( semua anggota terlepas dari visibilitas atau jenisnya ) ke array asosiatif, atau mengetikkannya ke stdClass? Saya berpikir ke arah Refleksi , tetapi jika tidak, saya hanya akan memikirkan sesuatu untuk melakukannya secara rekursif.
Dan Lugg
Refleksi akan berlangsung lama. Saat Anda berada di dalam kelas dalam getJsonData()fungsi Anda, Anda bisa memanggil get_object_vars(), dan mengulang hasil itu untuk mencari lebih banyak objek.
Wrikken
Aku hampir menyelesaikannya; masalahnya sekarang adalah rekursi. Setiap objek memiliki _parentproperti sehingga pohon tersebut dapat dilintasi ke root. Lihat hasil edit saya untuk pembaruan; mungkin saya harus mengajukan pertanyaan lain karena masalah ini sekarang disarikan dari aslinya.
Dan Lugg
Sederhana unset($array['_parent']);sebelum berjalan harus melakukan triknya.
Wrikken
Luar biasa, terima kasih @Wrikken - Saya mulai mencoba uji kesetaraan yang rumit, meneruskan objek konteks $parentsebagai data pengguna array_walk_recursive(). Sederhana itu indah! Juga, $array["\0class\0property"]karena polusi null-byte karena saya menggunakan casting. Saya pikir saya akan beralih ke get_object_vars().
Dan Lugg
91

Dalam kasus yang paling sederhana, jenis petunjuk seharusnya berfungsi:

$json = json_encode( (array)$object );
takeshin
sumber
7
Ini memberikan nama properti yang bertele-tele / jelek jika Anda bekerja dengan namespace dan pemuat otomatis.
BetaRide
inilah solusi terbaik, tepat dan ringkas!
Sujal Mandal
4
apakah ada cara untuk mendapatkan nama properti yang lebih bersih?
Christoffer
5
mengapa menambahkan \ u0000 * \ u0000 di awal nama prop?
Elia Weiss
1
Tidak berguna dengan properti pribadi. Anda semua harus mempelajari tentang en.wikipedia.org/wiki/Open/closed_principle .
Fabian Picone
19

json_encode()hanya akan menyandikan variabel anggota publik. jadi jika Anda ingin memasukkan pribadi setelah Anda harus melakukannya sendiri (seperti yang disarankan orang lain)

jfried
sumber
8

Kode berikut melakukan pekerjaan menggunakan refleksi. Ini mengasumsikan Anda memiliki getter untuk properti yang ingin Anda serialisasi

    <?php

    /**
     * Serialize a simple PHP object into json
     * Should be used for POPO that has getter methods for the relevant properties to serialize
     * A property can be simple or by itself another POPO object
     *
     * Class CleanJsonSerializer
     */
    class CleanJsonSerializer {

    /**
     * Local cache of a property getters per class - optimize reflection code if the same object appears several times
     * @var array
     */
    private $classPropertyGetters = array();

    /**
     * @param mixed $object
     * @return string|false
     */
    public function serialize($object)
    {
        return json_encode($this->serializeInternal($object));
    }

    /**
     * @param $object
     * @return array
     */
    private function serializeInternal($object)
    {
        if (is_array($object)) {
            $result = $this->serializeArray($object);
        } elseif (is_object($object)) {
            $result = $this->serializeObject($object);
        } else {
            $result = $object;
        }
        return $result;
    }

    /**
     * @param $object
     * @return \ReflectionClass
     */
    private function getClassPropertyGetters($object)
    {
        $className = get_class($object);
        if (!isset($this->classPropertyGetters[$className])) {
            $reflector = new \ReflectionClass($className);
            $properties = $reflector->getProperties();
            $getters = array();
            foreach ($properties as $property)
            {
                $name = $property->getName();
                $getter = "get" . ucfirst($name);
                try {
                    $reflector->getMethod($getter);
                    $getters[$name] = $getter;
                } catch (\Exception $e) {
                    // if no getter for a specific property - ignore it
                }
            }
            $this->classPropertyGetters[$className] = $getters;
        }
        return $this->classPropertyGetters[$className];
    }

    /**
     * @param $object
     * @return array
     */
    private function serializeObject($object) {
        $properties = $this->getClassPropertyGetters($object);
        $data = array();
        foreach ($properties as $name => $property)
        {
            $data[$name] = $this->serializeInternal($object->$property());
        }
        return $data;
    }

    /**
     * @param $array
     * @return array
     */
    private function serializeArray($array)
    {
        $result = array();
        foreach ($array as $key => $value) {
            $result[$key] = $this->serializeInternal($value);
        }
        return $result;
    }  
} 
Danny Yeshurun
sumber
1
Aku sangat mencintaimu sekarang! Saya akan mengirimkan Anda beberapa bacon atau bir atau cupcake bagaimana dengan cupcake?
Jonathan dos Santos
ini adalah Kelas yang hebat! itu juga bekerja dengan item objek yang dilindungi.
Roelof Berkepeis
2

Karena tipe objek Anda adalah kustom, saya cenderung setuju dengan solusi Anda - memecahnya menjadi segmen yang lebih kecil menggunakan metode pengkodean (seperti JSON atau membuat serial konten), dan di sisi lain memiliki kode yang sesuai untuk membangun kembali objek.

barfoon
sumber
2

Versi saya:

json_encode(self::toArray($ob))

Penerapan:

private static function toArray($object) {
    $reflectionClass = new \ReflectionClass($object);

    $properties = $reflectionClass->getProperties();

    $array = [];
    foreach ($properties as $property) {
        $property->setAccessible(true);
        $value = $property->getValue($object);
        if (is_object($value)) {
            $array[$property->getName()] = self::toArray($value);
        } else {
            $array[$property->getName()] = $value;
        }
    }
    return $array;
}

JsonUtils: GitHub

Suku John
sumber
Persis apa yang saya cari. Memecahkan masalah dengan kemaluan. Sederhana dan kecil.
Fabian Picone
1

Coba gunakan ini, ini bekerja dengan baik untuk saya.

json_encode(unserialize(serialize($array)));
Navaneeth Mohan
sumber
1

Ubah ke jenis variabel Anda privatemenjadipublic

Ini sederhana dan lebih mudah dibaca.

Sebagai contoh

Tidak Bekerja;

class A{
   private $var1="valuevar1";
   private $var2="valuevar2";
   public function tojson(){
    return json_encode($this)
   }
}

Ini bekerja;

class A{
   public $var1="valuevar1";
   public $var2="valuevar2";
   public function tojson(){
    return json_encode($this)
   }
}
Ferhat KOÇER
sumber
itu sangat aneh. tapi itu benar.
Abilogos
0

Saya membuat kelas penolong bagus yang mengubah objek dengan metode get menjadi array. Itu tidak bergantung pada properti, hanya metode.

Jadi saya memiliki objek ulasan berikut yang berisi dua metode:

Ulasan

  • getAmountReviews: int
  • getReviews: serangkaian komentar

Komentar

  • getSubject
  • getDescription

Skrip yang saya tulis akan mengubahnya menjadi array dengan properti yang terlihat seperti ini:

    {
      amount_reviews: 21,
      reviews: [
        {
          subject: "In een woord top 1!",
          description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque laoreet lacus quis eros venenatis, sed tincidunt mi rhoncus. Aliquam ut pharetra diam, nec lobortis dolor."
        },
        {
          subject: "En een zwembad 2!",
          description: "Maecenas et aliquet mi, a interdum mauris. Donec in egestas sem. Sed feugiat commodo maximus. Pellentesque porta consectetur commodo. Duis at finibus urna."
        },
        {
          subject: "In een woord top 3!",
          description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque laoreet lacus quis eros venenatis, sed tincidunt mi rhoncus. Aliquam ut pharetra diam, nec lobortis dolor."
        },
        {
          subject: "En een zwembad 4!",
          description: "Maecenas et aliquet mi, a interdum mauris. Donec in egestas sem. Sed feugiat commodo maximus. Pellentesque porta consectetur commodo. Duis at finibus urna."
       },
       {
          subject: "In een woord top 5!",
          description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque laoreet lacus quis eros venenatis, sed tincidunt mi rhoncus. Aliquam ut pharetra diam, nec lobortis dolor."
    }
]}

Sumber: PHP Serializer yang mengubah objek menjadi array yang dapat dikodekan ke JSON.

Yang harus Anda lakukan adalah membungkus json_encode di sekitar output.

Beberapa informasi tentang skrip:

  • Hanya metode yang dimulai dengan get yang ditambahkan
  • Metode pribadi diabaikan
  • Pembuat diabaikan
  • Karakter kapital dalam nama metode akan diganti dengan karakter garis bawah dan huruf kecil
Jamie
sumber
-7

Saya menghabiskan beberapa jam untuk masalah yang sama. Objek saya untuk dikonversi berisi banyak objek lain yang definisinya tidak boleh saya sentuh (API), jadi saya telah menemukan solusi yang bisa jadi lambat, tetapi saya menggunakannya untuk tujuan pengembangan.

Yang ini mengubah objek apa pun menjadi array

function objToArr($o) {
$s = '<?php
class base {
    public static function __set_state($array) {
        return $array;
    }
}
function __autoload($class) {
    eval("class $class extends base {}");
}
$a = '.var_export($o,true).';
var_export($a);
';
$f = './tmp_'.uniqid().'.php';
file_put_contents($f,$s);
chmod($f,0755);
$r = eval('return '.shell_exec('php -f '.$f).';');
unlink($f);
return $r;
}

Ini mengubah objek apa pun menjadi stdClass

class base {
    public static function __set_state($array) {
        return (object)$array;
    }
}
function objToStd($o) {
$s = '<?php
class base {
    public static function __set_state($array) {
        $o = new self;
        foreach($array as $k => $v) $o->$k = $v;
        return $o;
    }
}
function __autoload($class) {
    eval("class $class extends base {}");
}
$a = '.var_export($o,true).';
var_export($a);
';
$f = './tmp_'.uniqid().'.php';
file_put_contents($f,$s);
chmod($f,0755);
$r = eval('return '.shell_exec('php -f '.$f).';');
unlink($f);
return $r;
}
SharkyDog
sumber
Ada jawaban bagus dan akurat lainnya, sudah diterima. Apakah jawaban Anda menambahkan sesuatu yang sangat berbeda, lebih efisien atau ringkas? Saya kira tidak
Yaroslav
Saya akan jujur; Saya tidak berpikir ini menjawab pertanyaan itu sama sekali.
Dan Lugg
5
Sudah sekitar 6 bulan; Saya secara berkala kembali ke sini karena mendapat suara positif, dan untuk membuat beberapa pengeditan untuk pengunjung di masa mendatang; Saya masih tidak tahu apa yang harus dilakukan ini.
Dan Lugg
unlink($thisAnswer);
Dan Lugg
orang cenderung meremehkan apa yang tidak mereka pahami. Ini mungkin bukan solusi pasti tetapi itu adalah sesuatu yang harus diperhatikan. Dalam kasus seperti itu, Anda meminta klarifikasi sebelum memberikan suara negatif.
Gimali