Domanda

Ho riscontrato un problema strano e non sono sicuro di come risolverlo. Ho diverse classi che sono tutte implementazioni PHP di oggetti JSON. Ecco un esempio del problema

class A
{
    protected $a;

    public function __construct()
    {
        $this->a = array( new B, new B );
    }

    public function __toString()
    {
        return json_encode( $this->a );
    }
}

class B
{
    protected $b = array( 'foo' => 'bar' );

    public function __toString()
    {
        return json_encode( $this->b );
    }
}

$a = new A();

echo $a;

L'output da questo è

[{},{}]

Quando l'output desiderato è

[{"foo":"bar"},{"foo":"bar"}]

Il problema è che stavo facendo affidamento sul gancio __toString () per fare il mio lavoro per me. Ma non può, perché la serializzazione utilizzata da json_encode () non chiamerà __toString (). Quando incontra un oggetto nidificato, serializza semplicemente solo le proprietà pubbliche.

Quindi, la domanda diventa questa: esiste un modo per sviluppare un'interfaccia gestita per le classi JSON che mi consente sia di usare setter e getter per le proprietà, ma anche di ottenere il comportamento di serializzazione JSON che desidero?

Se ciò non è chiaro, ecco un esempio di implementazione che non funzionerà, poiché l'hook __set () viene chiamato solo per l'assegnazione iniziale

class a
{
    public function __set( $prop, $value )
    {
        echo __METHOD__, PHP_EOL;
        $this->$prop = $value;
    }

    public function __toString()
    {
        return json_encode( $this );
    }
}

$a = new a;
$a->foo = 'bar';
$a->foo = 'baz';

echo $a;

Suppongo che potrei anche fare qualcosa del genere

class a
{
    public $foo;

    public function setFoo( $value )
    {
        $this->foo = $value;
    }

    public function __toString()
    {
        return json_encode( $this );
    }
}

$a = new a;
$a->setFoo( 'bar' );

echo $a;

Ma poi dovrei fare affidamento sulla diligenza degli altri sviluppatori per usare i setter - non posso forzare l'adesione programmaticamente con questa soluzione.

&

--- gt; MODIFICA <---

Ora con un test della risposta di Rob Elsner

<?php

class a implements IteratorAggregate 
{
    public $foo = 'bar';
    protected $bar = 'baz';

    public function getIterator()
    {
        echo __METHOD__;
    }
}

echo json_encode( new a );

Quando lo esegui, puoi vedere che il metodo getIterator () non viene mai invocato.

È stato utile?

Soluzione

Un ritardo risponde ma potrebbe essere utile per gli altri con lo stesso problema.

In PHP < 5.4.0 json_encode non chiama alcun metodo dall'oggetto. Questo è valido per getIterator, __serialize, ecc ...

In PHP > v5.4.0, tuttavia, è stata introdotta una nuova interfaccia, chiamata JsonSerializable .

In pratica controlla il comportamento dell'oggetto quando <=> viene chiamato su quell'oggetto.


Fiddle (Beh, in realtà è PHP CodePad, ma stessa cosa ...)

Esempio :

class A implements JsonSerializable
{
    protected $a = array();

    public function __construct()
    {
        $this->a = array( new B, new B );
    }

    public function jsonSerialize()
    {
        return $this->a;
    }
}

class B implements JsonSerializable
{
    protected $b = array( 'foo' => 'bar' );

    public function jsonSerialize()
    {
        return $this->b;
    }
}


$foo = new A();

$json = json_encode($foo);

var_dump($json);

Uscite:

  

stringa (29) " [{" foo ": " bar "}, {" foo " ;: &; &; " bar quot}] quot;

Altri suggerimenti

Non è la tua risposta nei Documenti PHP per json_encode ?

  

Per chiunque abbia riscontrato il problema di non aggiungere proprietà private, puoi semplicemente implementare l'interfaccia IteratorAggregate con il metodo getIterator (). Aggiungi le proprietà che desideri includere nell'output in un array nel metodo getIterator () e restituiscile.

In PHP > v5.4.0 è possibile implementare l'interfaccia chiamata JsonSerializable come descritto in < a href = "https://stackoverflow.com/a/16203263/193617"> la risposta di Tivie .

Per quelli di noi che usano PHP < 5.4.0 è possibile utilizzare una soluzione che impiega get_object_vars() dall'interno dell'oggetto stesso e quindi li fornisce a json_encode() . Questo è quello che ho fatto nell'esempio seguente, usando il metodo __toString(), in modo che quando lancio l'oggetto come una stringa, ottengo una rappresentazione codificata JSON.

È inclusa anche un'implementazione dell'interfaccia IteratorAggregate , con il suo metodo getIterator() , in modo da poter scorrere le proprietà dell'oggetto come se fossero una matrice.

<?php
class TestObject implements IteratorAggregate {

  public $public = "foo";
  protected $protected = "bar";
  private $private = 1;
  private $privateList = array("foo", "bar", "baz" => TRUE);

  /**
   * Retrieve the object as a JSON serialized string
   *
   * @return string
   */
  public function __toString() {
    $properties = $this->getAllProperties();

    $json = json_encode(
      $properties,
      JSON_FORCE_OBJECT | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
    );

    return $json;
  }

  /**
   * Retrieve an external iterator
   *
   * @link http://php.net/manual/en/iteratoraggregate.getiterator.php
   * @return \Traversable
   *  An instance of an object implementing \Traversable
   */
  public function getIterator() {
    $properties = $this->getAllProperties();
    $iterator = new \ArrayIterator($properties);

    return $iterator;
  }

  /**
   * Get all the properties of the object
   *
   * @return array
   */
  private function getAllProperties() {
    $all_properties = get_object_vars($this);

    $properties = array();
    while (list ($full_name, $value) = each($all_properties)) {
      $full_name_components = explode("\0", $full_name);
      $property_name = array_pop($full_name_components);
      if ($property_name && isset($value)) $properties[$property_name] = $value;
    }

    return $properties;
  }

}

$o = new TestObject();

print "JSON STRING". PHP_EOL;
print "------" . PHP_EOL;
print strval($o) . PHP_EOL;
print PHP_EOL;

print "ITERATE PROPERTIES" . PHP_EOL;
print "-------" . PHP_EOL;
foreach ($o as $key => $val) print "$key -> $val" . PHP_EOL;
print PHP_EOL;

?>

Questo codice produce il seguente output:

JSON STRING
------
{"public":"foo","protected":"bar","private":1,"privateList":{"0":"foo","1":"bar","baz":true}}

ITERATE PROPERTIES
-------
public -> foo
protected -> bar
private -> 1
privateList -> Array

Anche se la tua variabile protetta fosse pubblica anziché protetta, non avrai l'input desiderato poiché questo produrrà l'intero oggetto in questo modo:

[{"b":{"foo":"bar"}},{"b":{"foo":"bar"}}]

Invece di:

[{"foo":"bar"},{"foo":"bar"}]

Molto probabilmente vanificherà il tuo scopo, ma sono più propenso a convertirlo in json nella classe originale con un getter predefinito e chiamando direttamente i valori

class B
{
    protected $b = array( 'foo' => 'bar' );

    public function __get($name)
    {
        return json_encode( $this->$name );
    }
}

Quindi potresti fare con loro quello che desideri, anche annidando i valori in un array aggiuntivo come fa la tua classe A, ma usando json_decode .. sembra ancora un po 'sporco, ma funziona.

class A
{
    protected $a;

    public function __construct()
    {
        $b1 = new B;
        $b2 = new B;
        $this->a = array( json_decode($b1->b), json_decode($b2->b) );
    }

    public function __toString()
    {
        return json_encode( $this->a );
    }
}

Nella documentazione ci sono alcune risposte a questo problema (anche se non mi piace la maggior parte di loro, serializzare + rimuovere le proprietà mi fa sentire sporco).

Hai ragione __toString () per la classe B non viene chiamato, perché non c'è motivo di farlo. Quindi, per chiamarlo, puoi usare un cast

class A
{
    protected $a;

    public function __construct()
    {
        $this->a = array( (string)new B, (string)new B );
    }

    public function __toString()
    {
        return json_encode( $this->a );
    }
}

Nota: il cast (stringa) prima delle nuove B ... questo chiamerà il metodo _toString () della classe B, ma non ti darà quello che vuoi, perché ti imbatterai nel classico quot; doppia codifica " problemi, poiché l'array è codificato nel metodo _toString () di classe B e verrà codificato di nuovo nel metodo _toString () di classe A.

Quindi è possibile scegliere di decodificare il risultato dopo il cast, ovvero:

 $this->a = array( json_decode((string)new B), json_decode((string)new B) );

o avrai bisogno di ottenere l'array, creando un metodo toArray () nella classe B che restituisce l'array straight. Il che aggiungerà del codice alla riga sopra perché non puoi usare direttamente un costruttore PHP (non puoi fare un nuovo B () - & Gt; toArray ();) Quindi potresti avere qualcosa del tipo:

$b1 = new B;
$b2 = new B;
$this->a = array( $b1->toArray(), $b2->toArray() );
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top