PHP 객체를 JSON으로 직렬화


101

그래서 새로운 JsonSerializable Interface를 우연히 발견했을 때 PHP 객체를 JSON으로 직렬화하는 방법에 대한 정보를 얻기 위해 php.net 을 돌아 다녔습니다 . 그것은 단지의 PHP> = 5.4 불구하고, 나는 5.3.x 환경에서 실행하고 있습니다.

이런 종류의 기능은 어떻게 PHP <5.4를 얻었 습니까?

아직 JSON으로 많이 작업하지는 않았지만 애플리케이션에서 API 레이어를 지원하려고 노력하고 있으며 데이터 개체 ( 그렇지 않으면 뷰로 전송 됨 )를 JSON으로 덤프하는 것이 완벽 할 것입니다.

객체를 직접 직렬화하려고하면 빈 JSON 문자열이 반환됩니다. 그것은 json_encode()도대체 대상으로 무엇을 해야할지 모른다고 가정하기 때문 입니다. 객체를 재귀 적으로 배열로 축소 한 다음 인코딩 해야 합니까?


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

echo json_encode($data) 빈 객체를 생성합니다.

{}

var_dump($data) 그러나 예상대로 작동합니다.

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

추가

1)

그래서 이것은 toArray()내가 Mf_Data수업을 위해 고안 한 기능입니다 .

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

그러나 Mf_Data객체에는 부모 ( 포함 ) 객체에 대한 참조도 있으므로 재귀로 인해 실패합니다. _parent참조를 제거하면 매력처럼 작동합니다 .

2)

후속 작업을 위해 내가 사용했던 복잡한 트리 노드 객체를 변환하는 마지막 함수는 다음과 같습니다.

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

삼)

구현을 좀 더 깔끔하게 정리하여 다시 한 번 확인하겠습니다. instanceof검사를 위해 인터페이스를 사용하는 것보다 훨씬 더 깔끔해 보입니다 method_exists()( 하지만 method_exists()크로스 컷 상속 / 구현 ).

사용 unset()도 약간 지저분 해 보였고 논리를 다른 방법으로 리팩토링해야하는 것 같습니다. 그러나이 구현 속성 배열 ( 때문에)을array_diff_key 복사 하므로 고려해야 할 사항이 있습니다.

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 좋은 질문입니다. 아직이 기능을 몰랐습니다.
takeshin 2011-07-26

@takeshin-네, 문서 페이지의 편집 날짜가 4 일 전입니다. 나는 그것을보고 기쁘다!
Dan Lugg 2011

2
이것을 보는 다른 사람들을 참조하기 위해 json_encode는 객체를 잘 처리 할 수 ​​있습니다. 그러나 해당 개체의 공용 멤버 만 인코딩합니다. 따라서 보호 또는 개인 클래스 변수가있는 경우 게시 된 메서드 중 하나 또는 JsonSerializable이 필요합니다.
Matthew Herbst

@MatthewHerbst 물론입니다. 확실히 오래된 질문은 이제 오래된, 그리고 <5.4은 더 이상 어쨌든 정말 옵션이 아닙니다 (또는 적어도 안)JsonSerializable
댄 Lugg

답변:


45

편집 : 현재 2016-09-24이고 PHP 5.4가 2012-03-01 릴리스되었으며 지원이 2015-09-01 종료되었습니다 . 그래도이 답변은 찬성 투표를받는 것 같습니다. 여전히 PHP <5.4를 사용 하는 경우 보안 위험이 발생하고 프로젝트가 중단 됩니다. <5.4에 머물러야하는 설득력있는 이유가 없거나 이미> = 5.4 버전을 사용하고 있다면이 답변을 사용하지 말고 PHP> = 5.4 (또는 최신 버전)를 사용하고 구현하십시오. 하고 JsonSerializable 인터페이스를 하십시오.


예를 들어라는 함수를 정의 getJsonData();하면 배열, stdClass객체 또는 개인 / 보호 된 매개 변수가 아닌 가시 매개 변수가있는 다른 객체를 반환 하고 json_encode($data->getJsonData());. 본질적으로 5.4에서 함수를 구현하되 직접 호출하십시오.

get_object_vars()클래스 내부에서 호출되는 것처럼 개인 / 보호 된 변수에 액세스 할 수있는 것과 같은 것이 작동 합니다.

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
감사합니다 @Wrikken-객체, 그 안에 포함 된 객체 ( 가시성 또는 유형에 관계없이 모든 구성원 )를 연관 배열로 줄이거 나 유형 캐스팅 하는 지름길 이 stdClass있습니까? Reflection 의 방향으로 생각하고 있지만 그렇지 않다면 재귀 적으로 수행 할 무언가를 알아낼 것입니다.
Dan Lugg 2011

반사는 먼 길입니다. 당신이 따라 내부 사용자의 클래스 getJsonData()기능, 당신은 단지 부를 수있는 get_object_vars()더 많은 개체를 찾고 그 결과를 통해, 루프.
Wrikken 2011

나는 그것을 거의 정리했습니다. 이제 문제는 재귀입니다. 각 개체에는 _parent속성이 있으므로 트리를 루트로 이동할 수 있습니다. 업데이트는 내 편집을 참조하십시오. 이 문제는 이제 내 원본에서 추상화되었으므로 다른 질문을해야합니다.
Dan Lugg 2011

unset($array['_parent']);걷기 전에 간단한 것이 트릭을해야합니다.
Wrikken 2011

감사합니다 @Wrikken-컨텍스트 객체 $parent를 사용자 데이터로 전달하여 복잡한 동등성 테스트를 시도하기 시작 했습니다 array_walk_recursive(). 단순함이 아름답습니다! 또한 $array["\0class\0property"]캐스팅을 사용했기 때문에 널 바이트 오염 때문입니다. 으로 전환 할 것 같습니다 get_object_vars().
Dan Lugg 2011

91

가장 간단한 경우 유형 힌트가 작동합니다.

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

7
이것은 네임 스페이스와 자동 로더로 작업하는 경우 길고보기 흉한 속성 이름을 제공합니다.
BetaRide

이것은 정확하고 간결한 최고의 솔루션입니다!
Sujal Mandal

4
더 깨끗한 속성 이름을 얻는 방법이 있습니까?
Christoffer 2015

5
왜 소품 이름의 시작 부분에 \ u0000 * \ u0000을 추가합니까?
Elia Weiss

1
사유 재산에는 쓸모가 없습니다. 여러분 모두 en.wikipedia.org/wiki/Open/closed_principle에 대해 배워야 합니다.
Fabian Picone

19

json_encode()공용 멤버 변수 만 인코딩합니다. 그래서 당신이 혼자해야 할 때 비공개를 포함하고 싶다면 (다른 사람들이 제안한 것처럼)


8

다음 코드는 리플렉션을 사용하여 작업을 수행합니다. 직렬화하려는 속성에 대한 getter가 있다고 가정합니다.

    <?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
나는 지금 당신을 너무 사랑합니다! 베이컨이나 맥주, 컵 케이크를 보내 드릴게요 컵 케이크는 요?
Jonathan dos Santos

이것은 훌륭한 수업입니다! 보호 오브젝트 항목에서도 작동합니다.
Roelof Berkepeis


2

개체 유형이 사용자 지정이기 때문에 솔루션에 동의하는 경향이 있습니다. 인코딩 방법 (예 : JSON 또는 콘텐츠 직렬화)을 사용하여 더 작은 세그먼트로 나누고 다른쪽에는 개체를 재구성하는 해당 코드가 있습니다.


2

내 버전 :

json_encode(self::toArray($ob))

이행:

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


내가 찾던 바로 그것. 사생활 문제를 해결합니다. 간단하고 작습니다.
Fabian Picone

1

이것을 사용해보십시오, 이것은 나를 위해 잘 작동했습니다.

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

1

당신의 변수 유형 변경 privatepublic

이것은 간단하고 더 읽기 쉽습니다.

예를 들면

작동하지 않는;

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

작동 중입니다.

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

매우 이상합니다. 하지만 사실입니다.
Abilogos

0

get 메서드를 사용하여 개체를 배열로 변환하는 멋진 도우미 클래스를 만들었습니다. 속성에 의존하지 않고 메서드 만 사용합니다.

그래서 두 가지 방법을 포함하는 다음 리뷰 객체가 있습니다.

리뷰

  • getAmountReviews : int
  • getReviews : 주석 배열

논평

  • getSubject
  • getDescription

내가 작성한 스크립트는 다음과 같은 속성을 가진 배열로 변환합니다.

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

출처 : 객체를 JSON으로 인코딩 할 수있는 배열로 변환하는 PHP Serializer.

당신이해야 할 일은 출력에 json_encode를 감싸는 것입니다.

스크립트에 대한 몇 가지 정보 :

  • get으로 시작하는 메소드 만 추가됩니다.
  • 개인 메서드는 무시됩니다.
  • 생성자는 무시됩니다.
  • 메서드 이름의 대문자는 밑줄과 소문자로 대체됩니다.

-7

나는 같은 문제에 몇 시간을 보냈다. 변환 할 객체에는 내가 손대지 말아야하는 정의 (API)를 가진 다른 많은 항목이 포함되어 있으므로 느릴 수있는 솔루션을 생각해 냈지만 개발 목적으로 사용하고 있습니다.

이것은 모든 객체를 배열로 변환합니다.

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

이것은 모든 객체를 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;
}

이미 받아 들여진 또 다른 훌륭하고 정확한 답변이 있습니다. 귀하의 답변은 근본적으로 다르거 나 더 효율적이거나 간결한 것을 추가합니까? 난 안 추측
야로 슬라브

나는 정직 할 것입니다. 나는 이것이 질문에 전혀 대답하지 않는다고 생각합니다.
Dan Lugg 2013 년

5
약 6 개월이 지났습니다. 나는 찬성 투표로 인해 여기에 정기적으로 돌아 왔고 향후 방문자를 위해 약간의 수정을했습니다. 나는 여전히 이는 어떻게해야되는 도대체 아무 생각이 없습니다.
Dan Lugg 2014

unlink($thisAnswer);
Dan Lugg

사람들은 이해하지 못하는 것에 대해 반대하는 경향이 있습니다. 정확한 해결책이 아닐 수도 있지만 살펴볼 것입니다. 그런 경우에 당신은 반대 투표 전에 설명을 요청합니다.
Gimali
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.