I need to flatten a nested JSON Array in PHP, the list I have is:
["Paris", "Barcelona", ["Glasgow", ["Sydney"]]]],"Budapest"]
So far I have this code:
<?php
$array = [
'city' => [
'Paris',
'Berlin',
'London',
],
];
$flattened = iterator_to_array(new RecursiveIteratorIterator(
new RecursiveArrayIterator($array)
), false);
print_r($flattened);
I needs to use this
print_r((new Flattener($jsonArray))→flatten());
This command should return a single-dimensional PHP array.
Any ideas of what I am missing?
You need to
create a class with a constructor (__construct) that takes the array and
create a flatten method which takes the array and flattens it.
An example you could use:
class Flattener {
private $array;
public function __construct($array) {
$this->array = $array;
}
public function flatten() {
return iterator_to_array(new RecursiveIteratorIterator(
new RecursiveArrayIterator($this->array)), false);
}
}
print_r((new Flattener($array))->flatten());
Related
I have 2 array of objects, for example
$arr1 = [
(new MyClass())->setId(1),
(new MyClass())->setId(2),
(new MyClass())->setId(3),
];
$arr2 = [
(new MyClass())->setId(1),
(new MyClass())->setId(2),
];
I'd need to find the difference between these 2 arrays, in this example I'd need to get an array with a single element, the one with id == 3.
I know I should use array_udiff (maybe) but I couldn't figure out how.
The following logic might help:
<?php
class MyClass
{
public $id = null;
public function setId($id) {
$this->id = $id;
return $this;
}
}
$arr1 = [
(new MyClass())->setId(1),
(new MyClass())->setId(2),
(new MyClass())->setId(3),
];
$arr2 = [
(new MyClass())->setId(1),
(new MyClass())->setId(2),
];
$difference = array_udiff($arr1, $arr2,
function ($objOne, $objTwo) {
return $objOne->id - $objTwo->id;
}
);
Output:
Array
(
[2] => MyClass Object
(
[id] => 3
)
)
working demo
I am trying to create a self populating tree by adding arrays to arrays. What I am having trouble with is finding an associative key within the array and then adding new content to it (a new array).
How do I solve this problem?
This could be done recursively. You can search for the key in the master array and merge that value[s] with the data you would like to append/insert.
Below I've created two functions that will achieve this.
/** Append or create to an array searching for the index with the with the value $key. If $key is set to null, append to array using numerical index.
* #param array $masterArray Array to append to
* #param mixed $data
* #param null $key If not set, will append to the master array
*/
function append($key, $data, &$masterArray) {
if($key===null) {
$masterArray[] = $data;
} else if(!_appendHelper($key, $data, $masterArray)) {
$masterArray[$key] = $data;
}
}
/** Helper function to recursively search/merge array */
function _appendHelper($key, $data, &$masterArray) {
foreach($masterArray as $k => &$v) {
if($k===$key) {
$v = array_merge($v, is_array($data) ? $data : [$data]);
return true;
} elseif(is_array($v)) {
_appendHelper($key, $data, $v);
}
}
return false; // Key does not exist
}
$masterArray = ['foo' => ['bar' => ['ram' => ['ewe' => []]]], ['man' => ['chu' => []]]];
append('foo', ['__TEST1__' => [1, 1, 1]], $masterArray);
print_r($masterArray);
append('new_key', ['__TEST2__' => [2, 2, 2]], $masterArray);
print_r($masterArray);
append(null, ['__TEST3__' => [3, 3, 3]], $masterArray);
print_r($masterArray);
Currently I'm working on a simple OOP script in PHP, it needs to compare the ID and the DATE of the array and sort them in the right order.
I was wondering why my constructor in the first class doesn't pass the $elements array properly.
The error I'm getting:
Notice: Undefined variable: elements in /Applications/XAMPP/xamppfiles/htdocs/strategy-pattern-.php on line 58
Catchable fatal error: Argument 1 passed to ObjectCollection::__construct() must be of the type array, null given, called in ... on line 58 and defined in ... on line 12
Code:
<?php
class ObjectCollection
{
var $elements = array(
array('id' => 2, 'date' => '2017-01-01',),
array('id' => 1, 'date' => '2017-02-01'));
var $comparator;
function __construct(array $elements)
{
$this->elements = $elements;
}
function sort()
{
if (!$this->comparator) {
throw new \LogicException('Comparator is not set');
}
uasort($this->elements, [$this->comparator, 'compare']);
return $this->elements;
}
function setComparator(ComparatorInterface $comparator)
{
$this->comparator = $comparator;
}
}
interface ComparatorInterface
{
function compare($a, $b);
}
class DateComparator implements ComparatorInterface
{
function compare($a, $b)
{
$aDate = new \DateTime($a['date']);
$bDate = new \DateTime($b['date']);
return $aDate <> $bDate;
}
}
class IdComparator implements ComparatorInterface
{
function compare($a, $b)
{
return $a['id'] <> $b['id'];
}
}
$collection = new ObjectCollection($elements);
$collection->setComparator(new IdComparator());
$collection->sort();
echo "Sorted by ID:\n <br>";
print_r($collection->elements);
$collection->setComparator(new DateComparator());
$collection->sort();
echo "<br>Sorted by date:\n <br>";
print_r($collection->elements);
?>
I know there may just be a rookie mistake somewhere but I'm really curious what I'm doing wrong haha.
Thanks in advance! :)
At the bottom of your script you have:
$collection = new ObjectCollection($elements);
However, the $elements variable is not defined. This is why you are getting the error.
The specific error is related to the fact that you used a type declaration in your class constructor requiring that an 'array' be passed. Prior to the addition of type declarations to php, the php runtime engine did not care what variables you passed, so long as you passed a number of variables equal to the number of required parameters to a function or method.
As also pointed out in another answer, many of us are assuming that your placement of the --
var $elements = array(
array('id' => 2, 'date' => '2017-01-01',),
array('id' => 1, 'date' => '2017-02-01'));
was never meant to be inside the class. With that said, doing so creates and initializes the $elements class variable, which is a valid technique that has many uses in OOP. However, the syntax used is obsolete, and if you really did want to initialize a class variable to a set value at object creation time, you should be using the syntax that includes a variable visibility keyword like:
protected $elements = array(
array('id' => 2, 'date' => '2017-01-01',),
array('id' => 1, 'date' => '2017-02-01'));
In conclusion, the answer to your question is that either you should define $collection to be an array at the bottom of the script, or pass an array in when you create the ObjectCollection object.
$collection = new ObjectCollection(array(
array('id' => 2, 'date' => '2017-01-01'),
array('id' => 1, 'date' => '2017-02-01'));
class ObjectCollection
{
// define as property
private $elements;
private $comparator;
function __construct(array $elements)
{
$this->elements = $elements;
}
function sort()
{
if (!$this->comparator) {
throw new \LogicException('Comparator is not set');
}
uasort($this->elements, [$this->comparator, 'compare']);
return $this->elements;
}
function setComparator(ComparatorInterface $comparator)
{
$this->comparator = $comparator;
}
}
...
// you need to define $elements to pass
$elements = array(
array('id' => 2, 'date' => '2017-01-01',),
array('id' => 1, 'date' => '2017-02-01'));
// them to the constructor
$collection = new ObjectCollection($elements);
// the way you did it, your $elements definition was in class scope so you got the error they are "NULL" / Not defined
You have declared the elements variable inside the class instead outside
$elements = array(
array('id' => 2, 'date' => '2017-01-01',),
array('id' => 1, 'date' => '2017-02-01'));
class ObjectCollection
{
Having the following PHP class
class SampleObject
{
private $map;
private $array;
public function getMap(): array
{
return $map;
}
public function setMap(array $map)
{
$this->map = $map;
}
public function getArray(): array
{
return $this->array;
}
public function setArray(array $array) {
$this->array = $array;
}
}
and two instances:
$inst1 = new SampleObject();
$inst2 = new SampleObject();
$inst1->setMap(['key' => 'value']);
$inst1->setArray([1]);
$inst2->setMap([]);
$inst2->setArray([]);
When they are serialized with JMS Serializer to json, the first one becomes:
{"map": {"key": "value"}, "array": [1]}
and the second one:
{"map": [], "array": []}
How to force the serializer to serialize the second object as {"map": {}, "array": []}?
As #EmanuelOster suggested in the comment, the custom handler can be used for this purpose. While the solution is not perfect (an annotation on the field would be much better), it works. Here is a sample handler
class SampleObjectSerializer implements SubscribingHandlerInterface {
public static function getSubscribingMethods() {
return [
[
'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
'format' => 'json',
'type' => SampleObject::class,
'method' => 'serialize',
],
];
}
public function serialize(JsonSerializationVisitor $visitor, SampleObject $object, array $type, Context $context) {
return [
'array' => $object->getArray(),
'map' => $this->emptyArrayAsObject($object->getMap()),
];
}
/**
* Forces to searialize empty array as json object (i.e. {} instead of []).
* #see https://stackoverflow.com/q/41588574/878514
*/
private function emptyArrayAsObject(array $array) {
if (count($array) == 0) {
return new \stdClass();
}
return $array;
}
}
If using Symfony, you need to register it.
You can modify the array serialization behavior by setting context information on the serialize method (or using annotations etc.)
You'll find these examples about serializing arrays and hashes in the cookbook:
<?php
// default (let the PHP's json_encode function decide)
$serializer->serialize([1, 2]); // [1, 2]
$serializer->serialize(['a', 'b']); // ['a', 'b']
$serializer->serialize(['c' => 'd']); // {"c" => "d"}
// same as default (let the PHP's json_encode function decide)
$serializer->serialize([1, 2], SerializationContext::create()->setInitialType('array')); // [1, 2]
$serializer->serialize([1 => 2], SerializationContext::create()->setInitialType('array')); // {"1": 2}
$serializer->serialize(['a', 'b'], SerializationContext::create()->setInitialType('array')); // ['a', 'b']
$serializer->serialize(['c' => 'd'], SerializationContext::create()->setInitialType('array')); // {"c" => "d"}
// typehint as strict array, keys will be always discarded
$serializer->serialize([], SerializationContext::create()->setInitialType('array<integer>')); // []
$serializer->serialize([1, 2], SerializationContext::create()->setInitialType('array<integer>')); // [1, 2]
$serializer->serialize(['a', 'b'], SerializationContext::create()->setInitialType('array<integer>')); // ['a', 'b']
$serializer->serialize(['c' => 'd'], SerializationContext::create()->setInitialType('array<string>')); // ["d"]
// typehint as hash, keys will be always considered
$serializer->serialize([], SerializationContext::create()->setInitialType('array<integer,integer>')); // {}
$serializer->serialize([1, 2], SerializationContext::create()->setInitialType('array<integer,integer>')); // {"0" : 1, "1" : 2}
$serializer->serialize(['a', 'b'], SerializationContext::create()->setInitialType('array<integer,integer>')); // {"0" : "a", "1" : "b"}
$serializer->serialize(['c' => 'd'], SerializationContext::create()->setInitialType('array<string,string>')); // {"d" : "d"}
I am trying to convert the associative array to an array of objects.
$assoc = array (
array(
'prop1'=>'val1',
'prop2'=>'val2',
),
array(
'prop1'=>'val1',
'prop2'=>'val2',
),
)
Here Is the code I have so far:
class Assoc {
public function setObject($assoc) {
$this->assoc[] = new Obj($assoc);
}
}
class Obj {
public function __construct($item) {
foreach ( $item as $property=>$value ) {
$this->{$property} = $value;
}
}
}
$test = New Assoc();
$test->setObject($assoc);
This code will work for a single array but not an array of arrays. If you could help with what I believe to be the loop in the setObject function.
Convert the associative array to an array of objects:
$output = array_map(function($element) {
return (object) $element;
}, $assoc);
Simple enough.
EDIT: If you need to make objects of a specific class:
$output = array_map(function($element) use ($classType) {
return new $classType($element);
}, $assoc);
You can generalize it into just about anything, really.
EDIT for specific object:
To adhere to your existing style as close as possible without messing with array_map voodoo:
class Assoc {
public function setObject($assoc) {
foreach ($assoc as $arr) {
$this->assoc[] = new Obj($arr);
}
}
}
class Obj {
public function __construct($item) {
foreach ( $item as $property=>$value ) {
$this->{$property} = $value;
}
}
}
$test = New Assoc();
$test->setObject($assoc);
Original:
If you just need generic conversion, and not into specific custom objects (not exactly clear in your post?) you can try this:
$new_array = array();
foreach ($assoc as $to_obj)
{
$new_array[] = (object)$to_obj;
}
// Print results
var_dump($new_array);
outputs:
array(2) {
[0]=>
object(stdClass)#1 (2) {
["prop1"]=>
string(4) "val1"
["prop2"]=>
string(4) "val2"
}
[1]=>
object(stdClass)#2 (2) {
["prop1"]=>
string(4) "val1"
["prop2"]=>
string(4) "val2"
}
}
$len = count($assoc);
for($i=0;$i<$len; $i++){
$assoc[$i] = (Object)$assoc[$i];
}
You have an indexed array of associative arrays. If you convert it to json then back to an iterable state with the default behavior of json_decode(), the top level (indexed array) will be cast as array-type while the subarrays will become object-type.
Note that this will conversion will permeate all the way through subsequent levels of data (in case researchers might have deeper data structures). Effectively, indexed arrays remain indexed arrays and associative arrays become objects.
This is such a basic call that I am not sure that creating a wrapper for it is necessary.
Code: (Demo)
$assoc = array (
array(
'prop1'=>'val1',
'prop2'=>'val2',
),
array(
'prop1'=>'val1',
'prop2'=>'val2',
),
);
var_export(
json_decode(json_encode($assoc))
);
Output:
array (
0 =>
(object) array(
'prop1' => 'val1',
'prop2' => 'val2',
),
1 =>
(object) array(
'prop1' => 'val1',
'prop2' => 'val2',
),
)