json_decode a clase personalizada

Respuestas:

96

No de forma automática. Pero puedes hacerlo por la ruta antigua.

$data = json_decode($json, true);

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

O, alternativamente, podría hacerlo más automático:

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

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

Editar : volviéndose un poco más elegante:

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
fuente
1
Me gustan sus sugerencias, solo para comentar que no funcionará con objetos anidados (que no sean STDClass o el objeto convertido)
javier_domenech
34

Creamos JsonMapper para mapear objetos JSON en nuestras propias clases de modelo automáticamente. Funciona bien con objetos anidados / secundarios.

Solo se basa en la información de tipo docblock para el mapeo, que la mayoría de las propiedades de clase tienen de todos modos:

<?php
$mapper = new JsonMapper();
$contactObject = $mapper->map(
    json_decode(file_get_contents('http://example.org/contact.json')),
    new Contact()
);
?>
cweiske
fuente
1
¡GUAUU! Eso es simplemente asombroso.
vothaison
¿Puede explicarnos la licencia OSL3? Si utilizo JsonMapper en un sitio web, ¿debo publicar el código fuente de ese sitio web? Si utilizo JsonMapper en el código de un dispositivo que vendo, ¿debe ser todo el código de ese dispositivo de código abierto?
EricP
No, solo tienes que publicar los cambios que realices en el propio JsonMapper.
cweiske
29

Puedes hacerlo, es un torpe pero totalmente posible. Tuvimos que hacer cuando empezamos a almacenar cosas en la base del sofá.

$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 

En nuestros puntos de referencia, esto fue mucho más rápido que intentar iterar a través de todas las variables de clase.

Advertencia: no funcionará para objetos anidados que no sean stdClass

Editar: tenga en cuenta la fuente de datos, se recomienda encarecidamente que no haga esto con datos no confiables de los usuarios sin un análisis muy cuidadoso de los riesgos.

John Pettitt
fuente
1
¿Funciona esto con subclases encapsuladas? Por ejemplo { "a": {"b":"c"} }, ¿dónde el objeto aes de otra clase y no solo una matriz asociativa?
J-Rou
2
no, json_decode crea objetos stdclass, incluidos los subobjetos, si desea que sean cualquier otra cosa, debe modificar cada objeto como se indicó anteriormente.
John Pettitt
Gracias, eso es lo que imaginé
J-Rou
¿Qué tal si usamos esta solución en objetos donde el constructor tiene parámetros? No puedo conseguir que funcione. Esperaba que alguien pudiera señalarme en la dirección correcta para hacer que esta solución funcione con un objeto que tiene un constructor personalizado con parámetros.
Marco
Seguí adelante y construí esto en una función. Tenga en cuenta que todavía no funciona con subclases. gist.github.com/sixpeteunder/2bec86208775f131ce686d42f18d8621
Peter Lenjo
16

Puede utilizar la biblioteca Serializer de J ohannes Schmitt .

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

En la última versión del serializador JMS, la sintaxis es:

$serializer = SerializerBuilder::create()->build();
$object = $serializer->deserialize($jsonData, MyObject::class, 'json');
Malaquías
fuente
2
La sintaxis no depende de la versión del serializador JMS, sino de la versión de PHP; a partir de PHP5.5 puede usar la ::classnotación: php.net/manual/en/…
Ivan Yarych
4

Puede hacer una envoltura para su objeto y hacer que la envoltura parezca que es el objeto en sí. Y funcionará con objetos multinivel.

<?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
fuente
3

No, esto no es posible a partir de PHP 5.5.1.

Lo único posible es tener json_decodematrices asociadas de retorno en lugar de los objetos StdClass.

Gordon
fuente
3

Puedes hacerlo de la siguiente manera.

<?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 obtener más detalles, visite create-custom-class-in-php-from-json-or-array

jigarshahindia
fuente
3

Me sorprende que nadie haya mencionado esto todavía.

Utilice el componente Serializador de Symfony: https://symfony.com/doc/current/components/serializer.html

Serializando de Object a 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

Deserialización de JSON a Object: (este ejemplo usa XML solo para demostrar la flexibilidad de los 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
fuente
2

Utilice Reflexión :

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
fuente
1

Como dice Gordon, no es posible. Pero si está buscando una forma de obtener una cadena que pueda decodificarse como una instancia de una clase determinada, puede usar serializar y unserializar en su lugar.

class Foo
{

    protected $bar = 'Hello World';

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

}

$string = serialize(new Foo);

$foo = unserialize($string);
echo $foo->getBar();
Francesco Terenzani
fuente
Esto no parece abordar la cuestión. Si es así, debe proporcionar alguna explicación.
Felix Kling
1

Una vez creé una clase base abstracta para este propósito. Llamémoslo JsonConvertible. Debe serializar y deserializar los miembros públicos. Esto es posible usando Reflection y enlace estático tardío.

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

Solo de memoria, probablemente no sea impecable. También tendrá que excluir las propiedades estáticas y puede dar a las clases derivadas la oportunidad de hacer que algunas propiedades se ignoren cuando se serializan a / desde json. No obstante, espero que entiendas la idea.

klawipo
fuente
0

JSON es un protocolo simple para transferir datos entre varios lenguajes de programación (y también es un subconjunto de JavaScript) que admite solo ciertos tipos: números, cadenas, matrices / listas, objetos / dictados. Los objetos son simplemente mapas clave = valor y las matrices son listas ordenadas.

Por tanto, no hay forma de expresar objetos personalizados de forma genérica. La solución es definir una estructura en la que su (s) programa (s) sabrán que es un objeto personalizado.

He aquí un ejemplo:

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

Esto podría usarse para crear una instancia de MyClassy establecer los campos ay foopara 123y "bar".

ThiefMaster
fuente
6
Esto puede ser cierto, pero la pregunta no es sobre la representación de objetos de forma genérica. Parece que tiene una bolsa JSON específica que se asigna a una clase específica en uno o ambos extremos. No hay ninguna razón por la que no pueda usar JSON como una serialización explícita de clases con nombre no genéricas de esta manera. Nombrarlo como lo está haciendo está bien si desea una solución genérica, pero tampoco hay nada de malo en tener un contrato acordado sobre la estructura JSON.
DougW
Esto podría funcionar si implementa Serializable en el extremo de la codificación y tiene condicionales en el extremo de la decodificación. Incluso podría funcionar con subclases si se organiza correctamente.
Peter Lenjo
0

Seguí adelante e implementé la respuesta de John Petit , como una función ( esencia ):

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

Esto funcionó perfectamente para mi caso de uso. Sin embargo, la respuesta de Yevgeniy Afanasyev me parece igualmente prometedora. Podría ser posible que su clase tenga un "constructor" adicional, así:

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

Esto también se inspira en esta respuesta .

Peter Lenjo
fuente
-1

Creo que la forma más sencilla es:

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