I am consuming an API that returns data in a strange way.
When a successfull request is made I get a response like this:
{
"data": {
...,
"rooms_available": [
{
"1": {
"hotel": "d9c5e4ca-1234-4321-8a97-kas123s",
"hotel_name": "Hotel name",
"history": [
{
"uuid": "d9c76322-f404-aa-b913-asd123",
"quantity": 0,
"room_id": "7b15fa32-f427-22-83bd-asd123",
"prices": {
"2023-04-13": {
"MXN": {
"regular_price_day": 0,
"member_price_day": 0,
"promotion_price_day": 0
},
"USD": {
"regular_price_day": 0,
"member_price_day": 0,
"promotion_price_day": 0
}
}
}
}
]
}
}
]
...,
}
}
I have the following model:
class MyResponse {
public Data $data;
public function __construct(Data $data) {
$this->data = $data;
}
public static function fromJson(array $data): self {
return new self(
Data::fromJson($data['data'])
);
}
}
class Data {
public array $roomsAvailable;
public function __construct(array $roomsAvailable) {
$this->roomsAvailable = $roomsAvailable;
}
public static function fromJson(array $data): self {
return new self(
array_map(static function($data) {
return RoomsAvailable::fromJson($data);
}, $data['rooms_available'])
);
}
}
I am doing the request and then I serialize the response doing:
$response = MyResponse::fromJson( $request->getContent() ); // getting the raw content of result
Everything is ok but the issue is that, for rooms_available and some other keys, it has dynamic keys like "1" or dates like "2023-04-13".
The thing is that I don't know how to represent that into a PHP object.
I am using this tool to convert JSON to PHP objects
https://jacobdekeizer.github.io/json-to-php-generator/#/
I have being using the fromJson strategy to serialize the request from the entire project and it has being going ok until now.
Which for the prices object from the respose, it generates this model:
class Prices
{
public 2023_04_13 $2023_04_13;
public function __construct(2023_04_13 $2023_04_13)
{
$this->2023_04_13 = $2023_04_13;
}
public static function fromJson(array $data): self
{
return new self(
2023_04_13::fromJson($data['2023-04-13'])
);
}
}
or the RoomsAvailable response
class RoomsAvailable
{
public 1 $1;
public function __construct(1 $1)
{
$this->1 = $1;
}
public static function fromJson(array $data): self
{
return new self(
1::fromJson($data['1'])
);
}
}
These classes are not valid due to those numbers used as keys in the JSON response.
How can I properly serialize the response into a PHP object?
I tried flatting the array one level on those nodes where there are number keys but it didn't worked.
Related
I can't understand why the data is not transmitted in the custom facade (Format) method parameter. I'm doing exception handling for the API and using my visualization handler (JsonExceptionFormatter).
Interface CanFormat:
interface CanFormat
{
public function format($data);
}
Facade Format:
class Format extends Facade
{
protected static function getFacadeAccessor(): string
{
return \Hopex\VkSdk\Foundation\Format::class;
}
}
Class Format:
namespace Hopex\VkSdk\Facades;
class Format implements CanFormat
{
private array $formatters = [];
public function with(string $formatter): static
{
$formatter = new $formatter();
if ($formatter instanceof CanFormat) {
$this->formatters[] = $formatter;
}
return $this;
}
public function format($data): mixed
{
foreach ($this->formatters as $formatter) {
$data = $formatter->format($data);
}
return $data;
}
}
Exception render method:
final public function render(Request $request)
{
if (env('LOG_LEVEL') === 'debug') {
dump($this->getMessage()); // added for test (next "dump 1")
return new JsonResponse(
Format::with(JsonExceptionFormatter::class)->format($this->getMessage()),
$this->getCode()
);
}
}
Visualization handler:
class JsonExceptionFormatter implements CanFormat
{
public function format($data): array
{
dump($data); // added for test (next "dump 2")
return [
'type' => 'error',
'message' => $data instanceof SdkException ? $data->getMessage() : $data
];
}
}
It's dumps:
[dump 1]: "ApiException: User authorization failed"
[dump 2]: []
P.S. Other formats work without problems.
Ive made a class that stores columns and rows these 2 are both arrays who both have extra data, like this:
<?php
class Table{
private array $Columns = array();
private array $Rows = array();
public function setColumn(Columns $c)
{
array_push($this->Columns, $c);
}
public function getColumn(): array
{
return $this->Columns;
}
public function setRow(Rows $c)
{
array_push($this->Rows, $c);
}
public function getRow(): array
{
return $this->Rows;
}
}
class Columns{
public string $Someinfo;
public function setSomeinfo(string $c)
{
$this->Someinfo= $c;
}
public function getSomeinfo(): int
{
return $this->Someinfo;
}
}
class Rows{
public string $Someinfo;
public function setSomeinfo(string $c)
{
$this->Someinfo= $c;
}
public function getSomeinfo(): int
{
return $this->Someinfo;
}
}
?>
My Symfony serializer sees and uses this class when its public but when I turn it private it suddenly stops working.
Symfony serializer:
$serializer = new Serializer([new ObjectNormalizer()], [new JsonEncoder()]);
return $serializer->deserialize($data, $table, 'json');
Ive tried almost all types of set, get and adders but none works or gets triggered by the serializer. The documentation has some examples but they are the same as my code at chapter: Deserializing a object.
https://symfony.com/doc/current/components/serializer.html#component-serializer-attributes-groups
In PHP, I have a product object that contains a collection of attributes. json_encode produces this:
{"id":"123","name":"abc","attributes":{"attributes":[{"key":"sku","value":"xyz"}]}}
"attributes" listed twice is redundant. What's the best way of structuring object collections so that the json is clean?
class Product {
public $id;
public $name;
public $attributes;
public function __construct()
{
$this->attributes = new Attributes();
}
public function get($id)
{
$this->id = "123";
$this->name = "abc";
$attribute = new Attribute("sku", "xyz");
$this->attributes->add($attribute);
}
}
class Attributes
{
public $attributes;
public function __construct()
{
$this->attributes = array();
}
public function add($attribute)
{
array_push($this->attributes, $attribute);
}
}
class Attribute
{
public $key;
public $value;
public function __construct($key, $value)
{
$this->set($key, $value);
}
}
I would just use an associative array.
class Product {
...
public $attributes=array();
...
public function get($id)
{
...
$this->attributes["sku"]="xyz";
$this->attributes["foo"]="bar";
}
}
json_encode() should produce something like this:
{"id":"123","name":"abc","attributes":{"sku":"xyz","foo":"bar"}}
OR using variable variables:
class Attributes
{
public function add($key,$value)
{
$this->{$key}=$value;
}
public function drop($key)
{
unset($this->{$key});
}
}
$a=new Attributes();
$a->add('sku','xyz');
$a->add('foo','bar');
echo json_encode($a).'<br>';
$a->drop('sku');
echo json_encode($a).'<br>';
Output:
{"sku":"xyz","foo":"bar"}
{"foo":"bar"}
You can give your classes a custom json encoding format by implementing JsonSerializable.
In your case you'll just need to have Attributes implement that and give it a jsonSerialize method which returns $this->attributes.
I have class which implements Countable, ArrayAccess, Iterator and Serializable.
I have a public varibale $data, in array form. And my iteration implementions:
public function rewind() { return reset($this->data); }
public function current() { return current($this->data); }
public function key() { return key($this->data); }
public function next() { return next($this->data); }
public function valid() { return isset($this->data[$this->key()]); }
Well everything works with foreach loop, but if i manually call current($arrayObject), it returns whole $data array, not teh current in it. I can do current($arrayObject->data), but like to keep native array functionality as where i can.
This is php behavior right? (not mine code bug) And is there any workaround this, without custom function(fingers crossed)?
[EDIT] Simplified version of full class(working):
$arrayObject = mysqli_fetch_object($this->result_id, "simpleMysqliResult ", array(array(
"fields" => array( "field1", "field2", "field3" )
)));
class simpleMysqliResult implements Countable, ArrayAccess, Iterator, Serializable {
public $data = array();
public function __construct($input) {
extract($input);
foreach ($fields as $field) {
$this->data[$field] = $this->{$field};
unset($this->{$field});
}
}
public function &toArray() { return $this->data; }
public function offsetGet($index) { return $this->data[$index]; }
public function offsetSet($index, $value) { $this->data[$index] = $value; }
public function offsetUnset($index) { unset($this->data[$index]); }
public function offsetExists($index) { return $this->offsetGet($index) !== null; }
public function count() { return count($this->data); }
public function rewind() { return reset($this->data); }
public function current() { return current($this->data); }
public function key() { return key($this->data); }
public function next() { return next($this->data); }
public function valid() { return isset($this->data[$this->key()]); }
public function serialize() { return serialize($this->data); }
public function unserialize($str) { return $this->data = unserialize($str); }
public function __call($func, $argv) {
if (!is_callable($func) || substr($func, 0, 6) !== 'array_')
{
throw new BadMethodCallException(__CLASS__.'->'.$func);
}
return call_user_func_array($func, array_merge(array($this->data), $argv));
}
}
Try placing your data in private mode. I suspect you have an external procedure playing around with it and making changes to it. I copied your class and altered your constructor a bit since i didn't have the same input as you and i get no strange behavior. Ultimately, you might want to look into why you extract($input) also, just use $input['fields'] or $input->fields as you see fit, it'll still work.
Here are my corrections:
private $data = array();
public function __construct($input) {
foreach($input['fields'] as $field) {
$this->data[$field] = $input[$field];
}
}
Here are my tests
$data = array('fields' => array('id', 'name'), 'id' => 1, 'name' => 'Franco');
$smr = new simpleMysqliResult($data);
var_dump($smr->current());
var_dump($smr->current());
var_dump($smr->current());
var_dump($smr->current());
var_dump($smr->next());
var_dump($smr->current());
var_dump($smr->current());
And my output is ok
int(1) int(1) int(1) int(1) string(6) "Franco" string(6) "Franco" string(6) "Franco"
So like i said, i think your problem mainly lies in the fact that your $data is public and something else is playing with it.
OR
It lies in your constructor that i had to fix to make it work on my side.
Good luck
i wrote an array wrapper class PersonArray which can contain objects of a certain type (Person). Every person has a unique getHash() function which returns the ID + Name as a unique identifier. This allows for speedy retrieval of the Person from the PersonArray. The PersonArray actually holds two internal Arrays. One for the storage of Person objects ($items), and one for the storage of the Hash values ($itemsHash).
I want to create a insertAt(index, Person) function which puts the Person object at the [index] position in the $items array. Is there a way to insertAt a certain position in an array? If so how can I also update the $itemsHash of the PersonArray?
class Person {
function getHash() {
return $this->id . $this->name;
}
}
class PersonArray implements Iterator {
public $items = array();
public $itemsHash = array();
public function Find($pKey) {
if($this->ContainsKey($pKey)) {
return $this->Item($this->internalRegisteredHashList[$pKey]);
}
}
public function Add($object) {
if($object->getHash()) {
$this->internalRegisteredHashList[$object->getHash()] = $this->Count();
array_push($this->items, $object);
}
}
public function getItems() {
return $this->items;
}
function ContainsKey($pKey) {}
function Count() {}
function Item($pKey) {}
//Iteration implementation
public function rewind() {}
public function current() {}
public function key() {}
public function next() {}
public function valid() {}
}
You may find it is faster and easier to use PHP's associative arrays rather than re-implementing them.
As an aside you can also implement the simpler IteratorAggregate if you are actually just iterating over an array.
e.g.
class PersonArray implements IteratorAggregate {
public $items = array();
public function getItems() {
return $this->items;
}
public function Add($object) {
if($object->getHash()) {
$this->items[$object->getHash()] = $object;
}
}
public function Find($pKey) {
if(isset($this->items[$pKey])) {
return $this->items[$pKey];
}
}
public function insertAt($index, $person) {
$tmp = array_slice($this->items, 0, $index);
$tmp[$person->getHash()] = $person;
$tmp = array_merge($tmp, array_slice($this->items, $index));
$this->items = $tmp;
}
//IteratorAggregate implementation
public function getIterator() {
return new ArrayIterator($this->items);
}
}