Tuần tự hóa đối tượng PHP thành JSON


101

Vì vậy, tôi đã lang thang trên php.net để tìm thông tin về việc tuần tự hóa các đối tượng PHP thành JSON, khi tôi tình cờ tìm thấy Giao diện JsonSerializable mới . Tuy nhiên, nó chỉ có PHP> = 5.4 và tôi đang chạy trong môi trường 5.3.x.

Làm thế nào để loại chức năng này đạt được PHP <5,4 ?

Tôi chưa làm việc nhiều với JSON, nhưng tôi đang cố gắng hỗ trợ một lớp API trong một ứng dụng và kết xuất đối tượng dữ liệu ( nếu không sẽ được gửi đến chế độ xem ) vào JSON sẽ rất hoàn hảo.

Nếu tôi cố gắng tuần tự hóa đối tượng trực tiếp, nó sẽ trả về một chuỗi JSON trống; đó là bởi vì tôi cho rằng json_encode()không biết phải làm gì với đối tượng. Tôi có nên giảm đệ quy đối tượng thành một mảng và sau đó mã hóa không?


Thí dụ

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

echo json_encode($data) tạo ra một đối tượng trống:

{}

var_dump($data) tuy nhiên, hoạt động như mong đợi:

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

Phụ lục

1)

Vì vậy, đây là toArray()hàm tôi đã nghĩ ra cho Mf_Datalớp:

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

Tuy nhiên vì các Mf_Datađối tượng cũng có tham chiếu đến đối tượng cha ( chứa ) của chúng, điều này không thành công với đệ quy. Hoạt động như một sự quyến rũ mặc dù khi tôi xóa _parenttham chiếu.

2)

Chỉ cần theo dõi, hàm cuối cùng để biến đổi một đối tượng nút cây phức tạp mà tôi đã sử dụng là:

// 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)

Tôi đang theo dõi lại, với cách triển khai gọn gàng hơn một chút. Việc sử dụng các giao diện để instanceofkiểm tra có vẻ gọn gàng hơn nhiều method_exists()( tuy nhiên, method_exists()việc thực hiện / kế thừa chéo ).

Việc sử dụng cũng unset()có vẻ hơi lộn xộn và có vẻ như logic nên được cấu trúc lại thành một phương pháp khác. Tuy nhiên, việc triển khai này thực hiện sao chép mảng thuộc tính ( doarray_diff_key ), vì vậy cần xem xét điều gì đó.

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

}

4
+1 Câu hỏi hay, bạn chưa biết tính năng này.
takehin vào

@takeshin - Yeop, ngày chỉnh sửa trên trang doc là 4 ngày trước. Tôi rất vui khi thấy nó!
Dan Lugg,

2
Để tham khảo cho những người khác nhìn vào điều này, json_encode có thể xử lý các đối tượng tốt. Tuy nhiên, nó chỉ mã hóa các thành viên công cộng của đối tượng đó. Vì vậy, nếu bạn có các biến lớp bảo vệ hoặc riêng tư, thì bạn cần một trong các phương thức đã đăng hoặc JsonSerializable.
Matthew Herbst

@MatthewHerbst Chắc chắn. Cũ câu hỏi là cũ bây giờ, và <5.4 là không thực sự là một lựa chọn nữa anyway (hoặc ít nhất là không nên) Chắc chắnJsonSerializable
Dan lugg

Câu trả lời:


45

chỉnh sửa : hiện tại là 2016-09-24 và PHP 5.4 đã được phát hành 2012-03-01 và hỗ trợ đã kết thúc vào 2015-09-01. Tuy nhiên, câu trả lời này dường như nhận được sự ủng hộ. Nếu bạn vẫn đang sử dụng PHP <5,4, bạn đang tạo ra rủi ro bảo mật và kết thúc dự án của mình . Nếu bạn không có lý do thuyết phục nào để ở mức <5.4 hoặc thậm chí đã sử dụng phiên bản> = 5.4, không sử dụng câu trả lời này và chỉ sử dụng PHP> = 5.4 (hoặc, bạn biết đấy, một phiên bản gần đây) và triển khai giao diện JsonSerializable


Bạn sẽ xác định một hàm, ví dụ có tên getJsonData();, hàm này sẽ trả về một mảng, stdClassđối tượng hoặc một số đối tượng khác với các tham số hiển thị thay vì các tham số riêng tư / được bảo vệ, và thực hiện a json_encode($data->getJsonData());. Về bản chất, thực hiện hàm từ 5.4, nhưng gọi nó bằng tay.

Một cái gì đó như thế này sẽ hoạt động, như get_object_vars()được gọi từ bên trong lớp, có quyền truy cập vào các biến riêng tư / được bảo vệ:

function getJsonData(){
    $var = get_object_vars($this);
    foreach ($var as &$value) {
        if (is_object($value) && method_exists($value,'getJsonData')) {
            $value = $value->getJsonData();
        }
    }
    return $var;
}

2
Cảm ơn @Wrikken - Có phím tắt nào để giảm một đối tượng, các đối tượng chứa trong đó ( tất cả các thành viên bất kể khả năng hiển thị hoặc kiểu ) thành một mảng liên kết, hoặc đánh máy nó vào stdClasskhông? Tôi đang nghĩ theo hướng Reflection , nhưng nếu không, tôi sẽ chỉ tìm ra thứ gì đó để thực hiện nó một cách đệ quy.
Dan Lugg,

Suy ngẫm sẽ là một chặng đường dài. Khi bạn đang ở bên trong lớp trong getJsonData()hàm của mình , bạn chỉ có thể gọi get_object_vars()và lặp qua kết quả đó để tìm kiếm nhiều đối tượng hơn.
Wrikken

Tôi gần như đã sắp xếp nó ra; vấn đề bây giờ là đệ quy. Mỗi đối tượng có một thuộc _parenttính để cây có thể được chuyển đến gốc. Xem chỉnh sửa của tôi để cập nhật; có lẽ tôi nên hỏi một câu hỏi khác vì vấn đề này bây giờ đã được tóm tắt từ bản gốc của tôi.
Dan Lugg,

Một đơn giản unset($array['_parent']);trước khi đi bộ nên thực hiện thủ thuật.
Wrikken

Tuyệt vời, cảm ơn @Wrikken - Tôi đã bắt đầu thử các bài kiểm tra bình đẳng phức tạp, chuyển một đối tượng ngữ cảnh $parentdưới dạng dữ liệu người dùng đến array_walk_recursive(). Đơn giản là đẹp! Ngoài ra, $array["\0class\0property"]do ô nhiễm byte rỗng vì tôi đang sử dụng truyền. Tôi nghĩ tôi sẽ chuyển sang get_object_vars().
Dan Lugg,

91

Trong những trường hợp đơn giản nhất, kiểu gợi ý sẽ hoạt động:

$json = json_encode( (array)$object );

7
Điều này cung cấp các tên thuộc tính dài / xấu nếu bạn làm việc với không gian tên và trình tải tự động.
BetaRide

đây là giải pháp tốt nhất, chính xác và ngắn gọn!
Sujal Mandal

4
có cách nào để có được tên tài sản sạch hơn không?
Christoffer

5
tại sao nó lại thêm \ u0000 * \ u0000 vào đầu tên prop?
Elia Weiss

1
Vô dụng với tài sản riêng. Tất cả các bạn nên tìm hiểu về en.wikipedia.org/wiki/Open/closed_principle .
Fabian Picone

19

json_encode()sẽ chỉ mã hóa các biến thành viên công khai. vì vậy, nếu bạn muốn bao gồm riêng tư khi bạn phải tự mình làm điều đó (như những người khác đề xuất)


8

Mã sau đang thực hiện công việc bằng cách sử dụng phản chiếu. Nó giả sử bạn có getters cho các thuộc tính bạn muốn tuần tự hóa

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

1
Tôi rất yêu bạn ngay bây giờ! Tôi sẽ gửi cho bạn một ít thịt xông khói hoặc bia hoặc một chiếc bánh nướng nhỏ còn một chiếc bánh nướng nhỏ thì sao?
Jonathan dos Santos

đây là một Class tuyệt vời! nó cũng hoạt động với các mục đối tượng được bảo vệ.
Roelof Berkepeis


2

Vì loại đối tượng của bạn là tùy chỉnh, tôi sẽ có xu hướng đồng ý với giải pháp của bạn - chia nó thành các phân đoạn nhỏ hơn bằng cách sử dụng phương pháp mã hóa (như JSON hoặc tuần tự hóa nội dung) và ở đầu kia, có mã tương ứng để xây dựng lại đối tượng.


2

Phiên bản của tôi:

json_encode(self::toArray($ob))

Thực hiện:

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


Chính xác những gì tôi đang tìm kiếm. Giải quyết vấn đề với private. Đơn giản và nhỏ.
Fabian Picone

1

Hãy thử sử dụng nó, điều này làm việc tốt cho tôi.

json_encode(unserialize(serialize($array)));

1

Thay đổi các loại biến của bạn privatethànhpublic

Điều này là đơn giản và dễ đọc hơn.

Ví dụ

Không làm việc;

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

Nó đang làm việc;

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

nó rất kỳ quặc. nhưng nó là sự thật.
Abilogos

0

Tôi đã tạo một lớp trợ giúp tuyệt vời để chuyển đổi một đối tượng với các phương thức get thành một mảng. Nó không dựa vào thuộc tính, chỉ là phương thức.

Vì vậy, tôi có một đối tượng đánh giá sau chứa hai phương thức:

Ôn tập

  • getAmountReviews: int
  • getReviews: mảng nhận xét

Bình luận

  • getSubject
  • getDescription

Tập lệnh tôi đã viết sẽ biến đổi nó thành một mảng với các thuộc tính trông giống như sau:

    {
      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."
    }
]}

Nguồn: PHP Serializer chuyển đổi một đối tượng thành một mảng có thể được mã hóa thành JSON.

Tất cả những gì bạn phải làm là quấn json_encode xung quanh đầu ra.

Một số thông tin về kịch bản:

  • Chỉ các phương thức bắt đầu bằng get mới được thêm vào
  • Các phương thức riêng tư bị bỏ qua
  • Hàm tạo bị bỏ qua
  • Các ký tự viết hoa trong tên phương thức sẽ được thay thế bằng một ký tự gạch dưới và viết thường

-7

Tôi đã dành vài giờ cho cùng một vấn đề. Đối tượng cần chuyển đổi của tôi chứa nhiều định nghĩa khác mà tôi không được phép chạm vào (API), vì vậy tôi đã nghĩ ra một giải pháp có thể chậm, tôi đoán là nhưng tôi đang sử dụng nó cho mục đích phát triển.

Cái này chuyển đổi bất kỳ đối tượng nào thành mảng

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

Điều này chuyển đổi bất kỳ đối tượng nào thành 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;
}

Có một câu trả lời tốt và chính xác, đã được chấp nhận. Câu trả lời của bạn có bổ sung điều gì đó hoàn toàn khác, hiệu quả hơn hoặc nhỏ gọn hơn không? Tôi đoán là không
Yaroslav

Tôi sẽ thành thật; Tôi không nghĩ rằng điều này trả lời câu hỏi, cả.
Dan Lugg

5
Nó được khoảng 6 tháng; Tôi đã định kỳ quay lại đây do số phiếu ủng hộ và thực hiện một số chỉnh sửa cho những khách truy cập trong tương lai; Tôi vẫn không biết điều này phải làm cái quái gì.
Dan Lugg

unlink($thisAnswer);
Dan Lugg

mọi người có xu hướng phản đối những gì họ không hiểu. Nó có thể không phải là một giải pháp chính xác nhưng nó là một cái gì đó để xem xét. Trong trường hợp đó, bạn yêu cầu làm rõ trước khi phản đối.
Gimali
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.