PHP - recursive Array to Object?
Asked Answered
B

14

53

Is there a way to convert a multidimensional array to a stdClass object in PHP?

Casting as (object) doesn't seem to work recursively. json_decode(json_encode($array)) produces the result I'm looking for, but there has to be a better way...

Bax answered 25/1, 2011 at 5:55 Comment(0)
M
74

As far as I can tell, there is no prebuilt solution for this, so you can just roll your own:

function array_to_object($array) {
   $obj = new stdClass();

   foreach ($array as $k => $v) {
      if (strlen($k)) {
         if (is_array($v)) {
            $obj->{$k} = array_to_object($v); //RECURSION
         } else {
            $obj->{$k} = $v;
         }
      }
   }
   
   return $obj;
}
Mcburney answered 25/1, 2011 at 6:3 Comment(5)
It's possible for PHP arrays to have empty keys, but objects can't have empty properties. It might be worth changing the else to elseif ($k !== '')Sjoberg
Thanks for noticing that it can't be done just with the elseif!Sjoberg
It seems as if this function will silently remove values having null keys.Selfstyled
and what happens with the nested arrays having numeric keys? I would opt for the json encode/decode solution. It may not be optimal but is the most bullet-proof.Brunner
The json encode/decode method will convert non StdClass objects into StdClass objects.Petcock
D
54

I know this answer is coming late but I'll post it for anyone who's looking for a solution.

Instead of all this looping etc, you can use PHP's native json_* function. I've got a couple of handy functions that I use a lot

/**
 * Convert an array into a stdClass()
 * 
 * @param   array   $array  The array we want to convert
 * 
 * @return  object
 */
function arrayToObject($array)
{
    // First we convert the array to a json string
    $json = json_encode($array);

    // The we convert the json string to a stdClass()
    $object = json_decode($json);

    return $object;
}


/**
 * Convert a object to an array
 * 
 * @param   object  $object The object we want to convert
 * 
 * @return  array
 */
function objectToArray($object)
{
    // First we convert the object into a json string
    $json = json_encode($object);

    // Then we convert the json string to an array
    $array = json_decode($json, true);

    return $array;
}

Hope this can be helpful

Danyelldanyelle answered 7/2, 2012 at 22:59 Comment(4)
I appreciate the better-late-than-never post, but my original question referenced this method.Bax
I have no valid defense on how I managed to overlook that :/ Only thing I can do is put my glasses on and quote Steve Erkle; Did I do that!? Back on topic: To my knowledge and in my humble opinion, this is the cleanest and most optimal way to do it. Please correct me if I'm wrong :)Danyelldanyelle
It may be the cleanest if you're judging by lines of code, but there's much more overhead involved. Jacob Relkin's answer is more efficient and just as easily coded.Bax
This only works with compatible JSON data types. if you're storing closures inside your multidimensional array, as I did, this will not work.Broads
P
14

You and many others have pointed to the JSON built-in functions, json_decode() and json_encode(). The method which you have mentioned works, but not completely: it won't convert indexed arrays to objects, and they will remain as indexed arrays. However, there is a trick to overcome this problem. You can use JSON_FORCE_OBJECT constant:

// Converts an array to an object recursively
$object = json_decode(json_encode($array, JSON_FORCE_OBJECT));

Tip: Also, as mentioned here, you can convert an object to array recursively using JSON functions:

// Converts an object to an array recursively
$array = json_decode(json_encode($object), true));    

Important Note: If you do care about performance, do not use this method. While it is short and clean, but it is the slowest among alternatives. See my other answer in this thread relating this.

Peirsen answered 6/1, 2019 at 14:37 Comment(0)
Z
7
function toObject($array) {
    $obj = new stdClass();
    foreach ($array as $key => $val) {
        $obj->$key = is_array($val) ? toObject($val) : $val;
    }
    return $obj;
}
Zeniazenith answered 2/8, 2011 at 8:18 Comment(1)
This will generate fatal error(Cannot access empty property) for empty keys in array. Observe strlen in Jacob's answerSelfstyled
R
7

You can use the array_map recursively:

public static function _arrayToObject($array) {
    return is_array($array) ? (object) array_map([__CLASS__, __METHOD__], $array) : $array;
}

Works perfect for me since it doesn't cast for example Carbon objects to a basic stdClass (which the json encode/decode does)

Rabb answered 11/11, 2017 at 16:28 Comment(1)
This is simple and sweet and useful when you need this type of utility within a class; references of which are hard to find. You should wrap it in an example class just for clarity. Otherwise, good example of the CLASS, METHOD magic... you don't see it often.Aladdin
K
2
/**
 * Recursively converts associative arrays to stdClass while keeping integer keys subarrays as arrays
 * (lists of scalar values or collection of objects).
 */
function a2o( array $array ) {
    $resultObj = new \stdClass;
    $resultArr = array();
    $hasIntKeys = false;
    $hasStrKeys = false;
    foreach ( $array as $k => $v ) {
        if ( !$hasIntKeys ) {
            $hasIntKeys = is_int( $k );
        }
        if ( !$hasStrKeys ) {
            $hasStrKeys = is_string( $k );
        }
        if ( $hasIntKeys && $hasStrKeys ) {
            $e = new \Exception( 'Current level has both integer and string keys, thus it is impossible to keep array or convert to object' );
            $e->vars = array( 'level' => $array );
            throw $e;
        }
        if ( $hasStrKeys ) {
            $resultObj->{$k} = is_array( $v ) ? a2o( $v ) : $v;
        } else {
            $resultArr[$k] = is_array( $v ) ? a2o( $v ) : $v;
        }
    }
    return ($hasStrKeys) ? $resultObj : $resultArr;
} 
Knox answered 8/2, 2014 at 19:23 Comment(0)
E
2

Some of the other solutions posted here fail to tell apart sequential arrays (what would be [] in JS) from maps ({} in JS.) For many use cases it's important to tell apart PHP arrays that have all sequential numeric keys, which should be left as such, from PHP arrays that have no numeric keys, which should be converted to objects. (My solutions below are undefined for arrays that don't fall in the above two categories.)

The json_decode(json_encode($x)) method does handle the two types correctly, but is not the fastest solution. It's still decent though, totaling 25µs per run on my sample data (averaged over 1M runs, minus the loop overhead.)

I benchmarked a couple of variations of the recursive converter and ended up with the following. It rebuilds all arrays and objects (performing a deep copy) but seems to be faster than alternative solutions that modify the arrays in place. It clocks at 11µs per execution on my sample data:

function array_to_object($x) {
    if (!is_array($x)) {
        return $x;
    } elseif (is_numeric(key($x))) {
        return array_map(__FUNCTION__, $x);
    } else {
        return (object) array_map(__FUNCTION__, $x);
    }
}

Here is an in-place version. It may be faster on some large input data where only small parts need to be converted, but on my sample data it took 15µs per execution:

function array_to_object_inplace(&$x) {
    if (!is_array($x)) {
        return;
    }
    array_walk($x, __FUNCTION__);
    reset($x);
    if (!is_numeric(key($x))) {
        $x = (object) $x;
    }
}

I did not try out solutions using array_walk_recursive()

Ette answered 14/11, 2016 at 13:47 Comment(2)
The first method, just created an empty object. The second method for me only managed to make the outermost associative array into an stdClass object. Everything else internally remained an array.Unbounded
@KimberlyW I just tested both and they still work in PHP 7. See this exampleEtte
P
1

Because the performance is mentioned, and in fact it should be important in many places, I tried to benchmark functions answered here.

You can see the code and sample data here in this gist. The results are tested with the data exists there (a random JSON file, around 200 KB in size), and each function repeated one thousand times, for the results to be more accurate.

Here are the results for different PHP configurations:

PHP 7.4.16 (no JIT)

$ php -dopcache.enable_cli=1 benchmark.php
pureRecursive():                        Completed in 0.000560s
pureRecursivePreservingIntKeys():       Completed in 0.000580s
jsonEncode():                           Completed in 0.002045s
jsonEncodeOptimized():                  Completed in 0.002060s
jsonEncodeForceObject():                Completed in 0.002174s
arrayMap():                             Completed in 0.000561s
arrayMapPreservingIntKeys():            Completed in 0.000592s
arrayWalkInplaceWrapper():              Completed in 0.001016s

PHP 8.0.2 (no JIT)

$ php -dopcache.enable_cli=1 benchmark.php
pureRecursive():                        Completed in 0.000535s
pureRecursivePreservingIntKeys():       Completed in 0.000578s
jsonEncode():                           Completed in 0.001991s
jsonEncodeOptimized():                  Completed in 0.001990s
jsonEncodeForceObject():                Completed in 0.002164s
arrayMap():                             Completed in 0.000579s
arrayMapPreservingIntKeys():            Completed in 0.000615s
arrayWalkInplaceWrapper():              Completed in 0.001040s

PHP 8.0.2 (tracing JIT)

$ php -dopcache.enable_cli=1 -dopcache.jit_buffer_size=250M -dopcache.jit=tracing benchmark.php
pureRecursive():                        Completed in 0.000422s
pureRecursivePreservingIntKeys():       Completed in 0.000410s
jsonEncode():                           Completed in 0.002004s
jsonEncodeOptimized():                  Completed in 0.001997s
jsonEncodeForceObject():                Completed in 0.002094s
arrayMap():                             Completed in 0.000577s
arrayMapPreservingIntKeys():            Completed in 0.000593s
arrayWalkInplaceWrapper():              Completed in 0.001012s

As you see, the fastest method with this benchmark is pure recursive PHP functions (posted by @JacobRelkin and @DmitriySintsov), especially when it comes to the JIT compiler. When it comes to json_* functions, they are the slowest ones. They are about 3x-4x (in the case of JIT, 5x) slower than the pure method, which may seem unbelievable.

One thing to note: If you remove iterations (i.e. run each function only one time), or even strictly lower its count, the results would differ. In such cases, arrayMap*() variants win over pureRecursive*() ones (still json_* functions method should be the slowest). But, you should simply ignore these cases. In the terms of performance, scalability is much more important.

As a result, in the case of converting arrays to object (and vice versa?), you should always use pure PHP functions, resulting in the best performance, perhaps independent from your configurations.

Peirsen answered 22/5, 2021 at 12:48 Comment(0)
A
0

Here's a function to do an in-place deep array-to-object conversion that uses PHP internal (shallow) array-to-object type casting mechanism. It creates new objects only when necessary, minimizing data duplication.

function toObject($array) {
    foreach ($array as $key=>$value)
        if (is_array($value))
            $array[$key] = toObject($value);
    return (object)$array;
}

Warning - do not use this code if there is a risk of having circular references.

Acculturation answered 28/1, 2013 at 14:12 Comment(0)
H
0

EDIT: This function is conversion from object to array.

From https://forrst.com/posts/PHP_Recursive_Object_to_Array_good_for_handling-0ka

protected function object_to_array($obj)
{
    $arrObj = is_object($obj) ? get_object_vars($obj) : $obj;
    foreach ($arrObj as $key => $val) {
            $val = (is_array($val) || is_object($val)) ? $this->object_to_array($val) : $val;
            $arr[$key] = $val;
    }
    return $arr;
}
Hypothec answered 26/2, 2014 at 12:24 Comment(1)
Because I misunderstood the quetion, this fn is conversion from object to array. I'll update my answer.Hypothec
T
0

I was looking for a way that acts like json_decode(json_encode($array))

The problem with most other recursive functions here is that they also convert sequential arrays into objects. However, the JSON variant does not do this by default. It only converts associative arrays into objects.

The following implementation works for me like the JSON variant:

function is_array_assoc ($arr) {
    if (!is_array($arr)) return false;
    foreach (array_keys($arr) as $k => $v) if ($k !== $v) return true;
    return false;
}

// json_decode(json_encode($array))
function array_to_object ($arr) {
    if (!is_array($arr) && !is_object($arr)) return $arr;
    $arr = array_map(__FUNCTION__, (array)$arr);
    return is_array_assoc($arr) ? (object)$arr : $arr;
}

// json_decode(json_encode($array, true))
// json_decode(json_encode($array, JSON_OBJECT_AS_ARRAY))
function object_to_array ($obj) {
    if (!is_object($obj) && !is_array($obj)) return $obj;
    return array_map(__FUNCTION__, (array)$obj);
}

If you want to have the functions as a class:

class ArrayUtils {
    public static function isArrAssoc ($arr) {
        if (!is_array($arr)) return false;
        foreach (array_keys($arr) as $k => $v) if ($k !== $v) return true;
        return false;
    }

    // json_decode(json_encode($array))
    public static function arrToObj ($arr) {
        if (!is_array($arr) && !is_object($arr)) return $arr;
        $arr = array_map(__METHOD__, (array)$arr);
        return self::isArrAssoc($arr) ? (object)$arr : $arr;
    }

    // json_decode(json_encode($array, true))
    // json_decode(json_encode($array, JSON_OBJECT_AS_ARRAY))
    public static function objToArr ($obj) {
        if (!is_object($obj) && !is_array($obj)) return $obj;
        return array_map(__METHOD__, (array)$obj);
    }
}

If anyone finds any mistakes please let me know.

Tawana answered 20/3, 2021 at 13:57 Comment(0)
D
0
/**
 * Convert a multidimensional array to an object recursively.
 * For any arrays inside another array, the result will be an array of objects.
 *
 * @author Marcos Freitas
 * @param array|any $props
 * @return array|any
 */
function array_to_object($props, $preserve_array_indexes = false) {
    $obj = new \stdClass();

    if (!is_array($props)) {
        return $props;
    }

    foreach($props as $key => $value) {

        if (is_numeric($key) && !$preserve_array_indexes) {
            if(!is_array($obj)) {
                $obj = [];
            }

            $obj[] = $this->array_to_object($value);
            continue;
        }

        $obj->{$key} = is_array($value) ? $this->array_to_object($value) : $value;
    }

    return $obj;
}
Diecious answered 20/5, 2022 at 4:17 Comment(2)
I know how your answer is intended to work, but there will be researchers that will not. Please always explain all of your snippets. $obj->{$key} can be $obj->$key, right?Grew
It looks like you are not preserving the preserving variable while making subsequent calls of array_to_object().Grew
U
0

For me, the thing that bothers me most about json_decode(json_encode($input)) is not performance, but rather the fact that it destroys custom classes in my arrays (it converts them to stdClass instances).

Since PHP 8.1, there is the function array_is_list to detect non-associative arrays. This helped me to build a replicate of json_decode(json_encode($value)) that will preserve custom class instances:

class Utils {
    /**
     * Re-implementation of json_decode(json_encode($value)), with the added benefit of 
     * keeping custom classes intact. Uses `array_is_list` to detect non-associative arrays.
     * Required PHP version: >= 8.1
     */
    public static function to_object($input): array|\stdClass {
        if (!is_array($input) && !($input instanceof \stdClass)) {
            return $input;
        }
        // keep non-associative arrays as such, otherwise use a stdClass
        $result = is_array($input) && array_is_list($input) ? [] : new \stdClass();
        foreach ($input as $key => $value) {
            // If the value is an array or a standard object, recursively convert it
            if (is_array($value) || $value instanceof \stdClass) {
                $value = call_user_func(__METHOD__, $value);
            }

            // Add the value to the result (either an stdClass or a non-associative array)
            if ($result instanceof \stdClass) {
                $result->$key = $value;
            } else {
                $result[$key] = $value;
            }

        }
        return $result;
    }
}

Overly simplistic usage example:

Utils::to_object(['foo' => 'bar', 'baz' => ['bat']]);

A live demo with tests for a simple array, an associative array and a stdClass can be found here: https://onlinephp.io/c/26a53

Uglify answered 24/3 at 20:36 Comment(0)
S
-1

Here is a smooth way to do it that can handle an associative array with great depth and doesn't overwrite object properties that are not in the array.

    <?php

    function setPropsViaArray( $a, $o )
    {
        foreach ( $a as $k => $v )
        {
            if ( is_array( $v ) )
            {
                $o->{$k} = setPropsViaArray( $v, ! empty ( $o->{$k} ) ? $o->{$k} : new stdClass() );
            }
            else
            {
                $o->{$k} = $v;
            }
        }
        return $o;
    };

    setPropsViaArray( $newArrayData, $existingObject );
Subsidize answered 22/2, 2016 at 2:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.