json_decode para classe personalizada


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

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()
);
?>

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.


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

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

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.


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


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

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

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();

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.


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".


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 .


-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;}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.