json_decode para classe personalizada

87

É possível decodificar uma string json para um objeto diferente de stdClass?

Tom
fonte
3
Nada de novo nisso depois de tantos anos?
Victor
Encontrei uma solução que funciona para mim stackoverflow.com/a/48838378/8138241
Jason Liu

Respostas:

96

Não automaticamente. Mas você pode fazer isso da maneira antiga.

$data = json_decode($json, true);

$class = new Whatever();
foreach ($data as $key => $value) $class->{$key} = $value;

Ou, alternativamente, você pode tornar isso mais automático:

class Whatever {
    public function set($data) {
        foreach ($data AS $key => $value) $this->{$key} = $value;
    }
}

$class = new Whatever();
$class->set($data);

Edit : ficando um pouco mais sofisticado:

class JSONObject {
    public function __construct($json = false) {
        if ($json) $this->set(json_decode($json, true));
    }

    public function set($data) {
        foreach ($data AS $key => $value) {
            if (is_array($value)) {
                $sub = new JSONObject;
                $sub->set($value);
                $value = $sub;
            }
            $this->{$key} = $value;
        }
    }
}

// These next steps aren't necessary. I'm just prepping test data.
$data = array(
    "this" => "that",
    "what" => "who",
    "how" => "dy",
    "multi" => array(
        "more" => "stuff"
    )
);
$jsonString = json_encode($data);

// Here's the sweetness.
$class = new JSONObject($jsonString);
print_r($class);
Michael McTiernan
fonte
1
Gosto das suas sugestões, apenas para observar que não funcionará com objetos aninhados (exceto STDClass ou o objeto convertido)
javier_domenech
34

Construímos JsonMapper para mapear objetos JSON em nossas próprias classes de modelo automaticamente. Ele funciona bem com objetos aninhados / filhos.

Ele depende apenas das informações do tipo docblock para mapeamento, que a maioria das propriedades de classe possui:

<?php
$mapper = new JsonMapper();
$contactObject = $mapper->map(
    json_decode(file_get_contents('http://example.org/contact.json')),
    new Contact()
);
?>
Cweiske
fonte
1
UAU! Isso é incrível.
vothaison
Você pode explicar a licença OSL3? Se eu usar o JsonMapper em um site, devo liberar o código-fonte desse site? Se eu usar o JsonMapper no código de um dispositivo que vendo, todo o código desse dispositivo deve ser de código aberto?
EricP
Não, você só precisa publicar as alterações feitas no próprio JsonMapper.
cweiske
29

Você pode fazer isso - é uma confusão, mas totalmente possível. Tivemos que fazer quando começamos a armazenar coisas no couchbase.

$stdobj = json_decode($json_encoded_myClassInstance);  //JSON to stdClass
$temp = serialize($stdobj);                   //stdClass to serialized

// Now we reach in and change the class of the serialized object
$temp = preg_replace('@^O:8:"stdClass":@','O:7:"MyClass":',$temp);

// Unserialize and walk away like nothing happend
$myClassInstance = unserialize($temp);   // Presto a php Class 

Em nossos benchmarks, isso foi muito mais rápido do que tentar iterar por todas as variáveis ​​de classe.

Advertência: não funciona para objetos aninhados diferentes de stdClass

Editar: tenha em mente a fonte de dados, é altamente recomendável que você não faça isso com dados não confiáveis ​​de usuários sem uma análise cuidadosa dos riscos.

John Pettitt
fonte
1
Isso funciona com subclasses encapsuladas. Por exemplo { "a": {"b":"c"} }, onde o objeto in aé de outra classe e não apenas um array associativo?
J-Rou
2
não, json_decode cria objetos stdclass, incluindo subobjetos, se você quiser que sejam qualquer outra coisa, você terá que fazer o kludge de cada objeto como acima.
John Pettitt de
Obrigado, isso é o que imaginei
J-Rou
Que tal usar essa solução em objetos onde o construtor possui parâmetros. Eu não posso fazê-lo funcionar. Eu esperava que alguém pudesse me apontar a direção certa para fazer essa solução funcionar com um objeto que tem um construtor personalizado com parâmetros.
Marco
Eu fui em frente e criei isso como uma função. Observe que ainda não funciona com subclasses. gist.github.com/sixpeteunder/2bec86208775f131ce686d42f18d8621
Peter Lenjo
17

Você pode usar a biblioteca Serializer de J ohannes Schmitt .

$serializer = JMS\Serializer\SerializerBuilder::create()->build();
$object = $serializer->deserialize($jsonData, 'MyNamespace\MyObject', 'json');

Na versão mais recente do serializador JMS, a sintaxe é:

$serializer = SerializerBuilder::create()->build();
$object = $serializer->deserialize($jsonData, MyObject::class, 'json');
Malaquias
fonte
2
A sintaxe não depende da versão do JMS Serializer, mas sim da versão do PHP - a partir do PHP5.5 você pode usar a ::classnotação: php.net/manual/en/…
Ivan Yarych
4

Você pode fazer um invólucro para o seu objeto e fazer com que ele pareça ser o próprio objeto. E funcionará com objetos de vários níveis.

<?php
class Obj
{
    public $slave;

    public function __get($key) {
        return property_exists ( $this->slave ,  $key ) ? $this->slave->{$key} : null;
    }

    public function __construct(stdClass $slave)
    {
        $this->slave = $slave;
    }
}

$std = json_decode('{"s3":{"s2":{"s1":777}}}');

$o = new Obj($std);

echo $o->s3->s2->s1; // you will have 777
Yevgeniy Afanasyev
fonte
3

Não, isso não é possível a partir do PHP 5.5.1.

A única coisa possível é json_decoderetornar matrizes associadas em vez de objetos StdClass.

Gordon
fonte
3

Você pode fazer isso da maneira abaixo ..

<?php
class CatalogProduct
{
    public $product_id;
    public $sku;
    public $name;
    public $set;
    public $type;
    public $category_ids;
    public $website_ids;

    function __construct(array $data) 
    {
        foreach($data as $key => $val)
        {
            if(property_exists(__CLASS__,$key))
            {
                $this->$key =  $val;
            }
        }
    }
}

?>

Para obter mais detalhes, visite create-custom-class-in-php-from-json-or-array

Jigarshahindia
fonte
3

Estou surpreso que ninguém tenha mencionado isso ainda.

Use o componente Symfony Serializer: https://symfony.com/doc/current/components/serializer.html

Serializando do objeto para JSON:

use App\Model\Person;

$person = new Person();
$person->setName('foo');
$person->setAge(99);
$person->setSportsperson(false);

$jsonContent = $serializer->serialize($person, 'json');

// $jsonContent contains {"name":"foo","age":99,"sportsperson":false,"createdAt":null}

echo $jsonContent; // or return it in a Response

Desserializando de JSON para objeto: (este exemplo usa XML apenas para demonstrar a flexibilidade dos formatos)

use App\Model\Person;

$data = <<<EOF
<person>
    <name>foo</name>
    <age>99</age>
    <sportsperson>false</sportsperson>
</person>
EOF;

$person = $serializer->deserialize($data, Person::class, 'xml');
Lucas Bustamante
fonte
2

Use reflexão :

function json_decode_object(string $json, string $class)
{
    $reflection = new ReflectionClass($class);
    $instance = $reflection->newInstanceWithoutConstructor();
    $json = json_decode($json, true);
    $properties = $reflection->getProperties();
    foreach ($properties as $key => $property) {
        $property->setAccessible(true);
        $property->setValue($instance, $json[$property->getName()]);
    }
    return $instance;
}
luke23489
fonte
1

Como diz Gordon, não é possível. Mas se você está procurando uma maneira de obter uma string que possa ser decodificada como uma instância de uma determinada classe, você pode usar serializar e desserializar.

class Foo
{

    protected $bar = 'Hello World';

    function getBar() {
        return $this->bar;
    }

}

$string = serialize(new Foo);

$foo = unserialize($string);
echo $foo->getBar();
Francesco Terenzani
fonte
Isso não parece responder à questão. Em caso afirmativo, você deve fornecer alguma explicação.
Felix Kling
1

Certa vez, criei uma classe base abstrata para esse propósito. Vamos chamá-lo de JsonConvertible. Ele deve serializar e desserializar os membros públicos. Isso é possível usando Reflection e late static binding.

abstract class JsonConvertible {
   static function fromJson($json) {
       $result = new static();
       $objJson = json_decode($json);
       $class = new \ReflectionClass($result);
       $publicProps = $class->getProperties(\ReflectionProperty::IS_PUBLIC);
       foreach ($publicProps as $prop) {
            $propName = $prop->name;
            if (isset($objJson->$propName) {
                $prop->setValue($result, $objJson->$propName);
            }
            else {
                $prop->setValue($result, null);
            }
       }
       return $result;
   }
   function toJson() {
      return json_encode($this);
   }
} 

class MyClass extends JsonConvertible {
   public $name;
   public $whatever;
}
$mine = MyClass::fromJson('{"name": "My Name", "whatever": "Whatever"}');
echo $mine->toJson();

Só de memória, provavelmente não é perfeito. Você também terá que excluir as propriedades estáticas e pode dar às classes derivadas a chance de fazer com que algumas propriedades sejam ignoradas quando serializadas de / para json. Espero que você tenha a ideia, no entanto.

Klawipo
fonte
0

JSON é um protocolo simples para transferir dados entre várias linguagens de programação (e também é um subconjunto de JavaScript) que suporta apenas alguns tipos: números, strings, arrays / listas, objetos / dicts. Os objetos são apenas mapas chave = valor e os Arrays são listas ordenadas.

Portanto, não há como expressar objetos personalizados de maneira genérica. A solução é definir uma estrutura onde seu (s) programa (s) saberão que é um objeto personalizado.

Aqui está um exemplo:

{ "cls": "MyClass", fields: { "a": 123, "foo": "bar" } }

Isso pode ser usado para criar uma instância de MyClasse definir os campos ae foopara 123e "bar".

ThiefMaster
fonte
6
Isso pode ser verdade, mas a questão não é a de representar objetos de maneira genérica. Parece que ele tem uma bolsa JSON específica que mapeia para uma classe específica em uma ou ambas as extremidades. Não há motivo para não usar JSON como uma serialização explícita de classes nomeadas não genéricas dessa maneira. Nomear como você está fazendo é bom se você quiser uma solução genérica, mas também não há nada de errado em ter um contrato acordado na estrutura JSON.
DougW
Isso pode funcionar se você implementar Serializable no final da codificação e tiver condicionais no final da decodificação. Pode até funcionar com subclasses se organizado corretamente.
Peter Lenjo 01 de
0

Fui em frente e implementei a resposta de John Petit , como uma função ( essência ):

function json_decode_to(string $json, string $class = stdClass::class, int $depth = 512, int $options = 0)
{
    $stdObj = json_decode($json, false, $depth, $options);
    if ($class === stdClass::class) return $stdObj;

    $count = strlen($class);
    $temp = serialize($stdObj);
    $temp = preg_replace("@^O:8:\"stdClass\":@", "O:$count:\"$class\":", $temp);
    return unserialize($temp);  
}

Isso funcionou perfeitamente para o meu caso de uso. No entanto, a resposta de Yevgeniy Afanasyev parece igualmente promissora para mim. Pode ser possível que sua classe tenha um "construtor" extra, como:

public static function withJson(string $json) {
    $instance = new static();
    // Do your thing
    return $instance;
}

Isso também é inspirado por esta resposta .

Peter Lenjo
fonte
-1

Acho que a maneira mais simples é:

function mapJSON($json, $class){
$decoded_object = json_decode($json);
   foreach ($decoded_object as $key => $value) {
            $class->$key = $value;
   }
   return $class;}
JCoreX
fonte