扩展json_encode以支持嵌套对象

3
我正在使用PHP 5.2.x并希望仅对具有私有成员的自定义PHP类的对象进行编码。其中一个私有成员是另一个自定义类的对象数组。
我尝试了https://dev59.com/IGw05IYBdhLWcg3w0VKa#7005915中概述的解决方案,但显然不能递归地工作。唯一的解决方案是以某种方式扩展json_encode方法,以便调用类的版本而不是默认方法。
参考代码如下:
Class A {
    private $x;
    private $y;
    private $z;
    private $xy;
    private $yz;
    private $zx;

    public function f1() {
        ...
    }

    public function f2() {
        ...
    }
    .
    .
    .
    public function getJSONEncode() {
         return json_encode(get_object_vars($this));
    }
}

class B {

    private $p; //This finally stores objects of class A
    private $q;
    private $r;

    public function __construct() {
            $this->p = array();
    }

    public function fn1() {
        ...
    }

    public function fn2() {
        ...
    }

    .
    .
    .

    public function getJSONEncode() {
         return json_encode(get_object_vars($this));
    }

}

class C {

    private $arr;

    public function __construct() {
            $this->arr = array();
    }

    public function fillData($data) {
        $obj = new B();
        //create objects of class B and fill in values for all variables
        array_push($this->arr, $obj)
    }

    public function returnData() {
          echo $this->arr[0]->getJSONEncode(); //Edited to add

    }

}

如何实现嵌套的json编码是最佳方式?

编辑添加:

当执行returnData方法时,我得到的输出结果为:

{"p":[{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}],"q":"Lorem Ipsum","r":"Dolor Sit Amet"}

1
我这里没有看到任何嵌套的JSON... Class A { class PostAttributes { 是无效的..我不确定我完全理解你想要什么。 - Baba
私有值的本质就是隐私,为什么要将它们翻译成没有隐私概念的数据结构呢?您希望将它们发送回JavaScript吗?如果是这样,为什么不使用公共项目,考虑到它们最终将变为公共项目? - Pebbl
@Baba:Class C->returnData(); //我在那里添加了一个echo语句,只是为了简单起见。假设returnData在某个地方被调用。 - AJ.
好的,如果您停止使用私有元素并将它们设置为公共元素,会发生什么? - Pebbl
对象将被编码。然而,我不希望仅仅为了编码而公开类的属性。我有额外的代码将访问数据,包括从数据库中读取和写入数据,并且更喜欢使用适当的getter和setter来进行干净的访问/写入数据。 - AJ.
显示剩余6条评论
1个回答

2

虽然我认为你最好为每个类编写一个适当的导出/编码函数(它将从私有和公共值构造一个公共对象仅用于编码 - 你可以相当聪明地使用php的reflection能力),但是你也可以使用以下代码:

/// example class B
class TestB {
  private $value = 123;
}

/// example class A
class TestA {
  private $prop;
  public function __construct(){
    $this->prop = new TestB();
  }
}

function json_encode_private($obj){
  /// export the variable to find the privates
  $exp = var_export($obj, true);
  /// get rid of the __set_state that only works 5.1+
  $exp = preg_replace('/[a-z0-9_]+\:\:__set_state\(/i','((object)', $exp);
  /// rebuild the object
  eval('$enc = json_encode('.$exp.');');
  /// return the encoded value
  return $enc;
}

echo json_encode_private(new TestA());

/// {"prop":{"value":123}}

所以上述代码应该可以工作,但我不建议在php中使用eval - 因为我总是听到远处传来的警报声 :)

更新

刚想到一个可能会让这个更安全的方法,而不是使用eval,你可以使用create_function,它会限制一些创建功能或至少其功能范围的能力...

function json_encode_private($obj){
  $exp = var_export($obj, true);
  $exp = preg_replace('/[a-z0-9_]+\:\:__set_state\(/i','((object)', $exp);
  $enc = create_function('','return json_encode('.$exp.');');
  return $enc();
}

更新 2

我有机会尝试另一种将具有私有属性的对象转换为具有公共属性的对象的方法 - 仅使用一个简单的函数 (而没有使用 eval)。由于转换后的私有属性中存在奇怪的\0类名\0前缀(请参见代码中的注释),因此需要在您使用的任何版本的 PHP 上进行测试,因为它的行为可能不可靠。

有关这种奇怪前缀行为的更多信息:
http://uk3.php.net/language.types.array.php#language.types.array.casting

无论如何,使用一个测试类:

class RandomClass {
  private $var = 123;
  private $obj;
  public function __construct(){
    $this->obj = (object) null;
    $this->obj->time = time();
  }
}

我们可以使用以下函数将其转换为公共对象:
function private_to_public( $a ){
  /// grab our class, convert our object to array, build our return obj
  $c = get_class( $a ); $b = (array) $a; $d = (object) null;
  /// step each property in the array and move over to the object
  /// usually this would be as simple as casting to an object, however
  /// my version of php (5.3) seems to do strange things to private 
  /// properties when casting to an array... hence the code below:
  foreach( $b as $k => $v ){
    /// for some reason private methods are prefixed with a \0 character
    /// and then the classname, followed by \0 before the actual key value. 
    /// This must be some kind of internal protection causing private  
    /// properties to be ignored. \0 is used by some languges to terminate  
    /// strings (not php though, as far as i'm aware).
    if ( ord($k{0}) === 0 ) {
      /// trim off the prefixed weirdnesss..?!
      $e = substr($k, 1 + strlen($c) + 1);
      /// unset the $k var first because it will remember the \0 even 
      /// if other values are assigned to it later on....?!
      unset($k); $k = $e;
    }
    /// so if we have a key, either public or private - set our value on
    /// the destination object.
    if ( $k !== '' && $k !== NULL && $k !== FALSE )  {
      $d->{$k} = $v;
    }
  }
  return $d;
}

所以,如果我们把所有的东西放在一起:
$a = new RandomClass();

echo json_encode( private_to_public( $a ) );

/// {"var":123,"obj":{"time":1349777323}}

如果您想要转换每个类的方法,最好的选择是为每个类编写定制代码,或者使用Class Reflection创建某种通用解决方案,但后者比StackOverflow答案更复杂...至少在我有空的时间内 ;)

更多信息

上述代码将在尝试从任何地方访问对象时起作用,实现此目的的原因是因为我的第一次尝试显然是使用以下内容:

echo json_encode( get_object_vars($a) );

/// you will get {} which isn't what you expect

似乎如果你想使用get_object_vars,必须在能够访问所有属性的上下文中使用它,即从你正在公开的类内部使用:
public function getAllProperties(){
  return get_object_vars( $this );
}

那么,假设我们将以上内容添加到RandomClass定义中:

echo json_encode( $a->getAllProperties() );

/// you will get {"var":123,"obj":{"time":1349777323}}

这种方法可行的原因是类的成员可以访问所有公有或私有属性...所以,正如我所说,这种方式工作起来要好得多;更优秀,但并非总是可行。


抱歉,我不是很熟悉PHP中的面向对象编程,但是有没有办法可以扩展json_encode方法(或我的类),以便在尝试编码时自动调用它们?- 类似于JSONSerialize所实现的功能,但在PHP 5.4及以上版本中。 - AJ.
不,据我所知没有这样的方法,这也是为什么 JSONSerialize 被创建的原因吧。虽然有魔术函数 __sleep__wakeup,但它们只适用于 serializeunserialize。还有一个魔术函数 __set_state,但那是用于 var_export 的。http://php.net/manual/en/language.oop5.magic.php - 我刚发现还有一个与此类似的 SO 问题 https://dev59.com/bmoy5IYBdhLWcg3wF6ML (那里也没有非黑客答案) - Pebbl
感谢提供多种解决此问题的选项。 我也会尝试推动我的托管提供商升级他们的PHP版本,以便我可以使用更简单的JSONSerialize接口。在那之前,您的答案是正确的选择 :) - AJ.
@AJ。没问题...你的问题让我学到了有关get_object_vars的新知识,所以一切都好!是的,要求他们升级听起来像个好主意 :) 唯一可能的问题是php 5.4发布于2012-03-01,我所知道的大多数主机在至少一年甚至更长时间内才会考虑升级。如果你能获得专用服务器,你可以做任何你想做的事情,尽管这将成为一个相当昂贵的json_encode函数 ;) - Pebbl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接