2

How can we convert the keys of a toJson() returned object to lowercase or camelCase? Consider the following example:

Query:

     $foo = FooQuery::create()
        ->filterByBar($bar)
        ->findOne()
        ->toJson();

Result:

{"Id": 1, "Bar":"StackOverflow"}

It seems to be PascalCase by default. How can I get lowercase properties on the json result?

The function I'm referring to can be found here and is applied to an ObjectCollection.

Update: I want to avoid using arrays as: array_change_key_case() does not work for multidimensional arrays when dealing with complex objects.

I know this can be achieved through some modifications but I want to know if there's a better approach, preferably without casting to an array first for performance purposes.

scripton
  • 228
  • 5
  • 15

2 Answers2

0

For changing the behavior every time

I don't think Propel gives you a direct easy way to do this via an option you pass in to the method. You can, however, override *Base methods in your classes.

public function toJSON() {
  $fields = array_change_key_case(parent::toJSON());
  return $fields;
}

For one-off case changes

Still using the function above but with more detail: array_change_key_case changes the case of all keys in an array. You can read about it in the PHP official docs.

array_change_key_case ( array $array [, int $case = CASE_LOWER ] ) Returns an array with all keys from array lowercased or uppercased. Numbered indices are left as is.

Parameters

array The array to work on

case Either CASE_UPPER or CASE_LOWER (default)

Return Values

Returns an array with its keys lower or uppercased, or FALSE if array is not an array.

Works in (PHP 4 >= 4.2.0, PHP 5, PHP 7)

Example

<?php
$input_array = array("FirSt" => 1, "SecOnd" => 4);
print_r(array_change_key_case($input_array, CASE_UPPER));
?>

Outputs

Array
(
    [FIRST] => 1
    [SECOND] => 4
)

For you...

In your example, you could simply type $lower_foo = array_change_key_case($foo); on the next line since lowercase is the default.

Palu Macil
  • 1,708
  • 1
  • 24
  • 34
  • Thanks for the input but I already know about array_change_key_case. I want to integrate it into the propel query so that I don't have to convert my results for every single db call. – scripton Nov 18 '15 at 19:47
  • @scripton I don't think propel provides the option, but if you override the base class's JSON method, it's only a single line of code and does what you want. I edited my answer to mention that. You could also decode the JSON, add another custom property, and re-encode. (That's not useful to you here, but I think it's the more common reason to override this.) – Palu Macil Nov 18 '15 at 20:16
  • Thanks for the update. I tried using that function in my EntityQuery class but that did not seem to work as the parent does not have that function. Where exactly in which Propel class(es) would I need to add that function? – scripton Nov 18 '15 at 20:33
0

There is a way to configure your generated classes to use camelCase keys. In your propel.json (or .yaml, .php .ini .xml) configuration file add the objectModel as follows:

"generator": {
  "defaultConnection": "bookstore",
  "connections": [ "bookstore" ],
  "objectModel": {
    "defaultKeyType": "camelName"
  }
}

This will make all your keys camelCased but it turns out that this only works with the toArray() method. When you call toJSON() you're actually using the exportTo('JSON') method. If you look at the exportTo method you can see that it is calling:

$this->toArray(TableMap::TYPE_PHPNAME, $includeLazyLoadColumns, array(), true)

This is forcing exportTo('JSON') and toJSON() to use TableMap::TYPE_PHPNAME as the key type. If you look at the toArray method definition it uses your "defaultKeyType" as the default $keyType. If you call toArray() without any parameters and you have "defaultKeyType": "camelName" then it will use TableMap::TYPE_CAMELNAME and therefore return all the keys as camelCase.

The root of the problem is in Propel's generator classes. The base classes are generated in propel/src/Propel/Generator/Builder/Om/ObjectBuilder.php If we look at how it generates the toArray method we find:

public function toArray(\$keyType = TableMap::$defaultKeyType, \$includeLazyLoadColumns = true, \$alreadyDumpedObjects = array()" . ($hasFks ? ", \$includeForeignObjects = false" : '') . ")

The important point here is that it is using TableMap::$defaultKeyType. Now if we look at exportTo method generation we have to look in templates/baseObjectMethods.php and the exportTo method definition is as follows:

public function exportTo($parser, $includeLazyLoadColumns = true)
{
    if (!$parser instanceof AbstractParser) {
        $parser = AbstractParser::getParser($parser);
    }

    return $parser->fromArray($this->toArray(TableMap::TYPE_PHPNAME, $includeLazyLoadColumns, array(), true));
}

The important point here is that it uses the hardcoded value TableMap::TYPE_PHPNAME. If you change that hardcoded value to TableMap::TYPE_CAMELNAME and regenerate your classes then toJSON() will give all keys as camelCase.

So unfortunately you cannot make toJSON use camelCase without modifying the source. I would think that the exportTo method should use the defaultKeyType so we can use the configuration to modify this behavior. That being said there may be a perfectly good reason to have the hardcoded value instead of a configurable value.

Update: It looks like this only works with a single instance of each of the generated model classes. With the ObjectCollection and Collection classes the toArray and exportTo methods use hardcoded values of TableMap::TYPE_PHPNAME

Propel/Runtime/Collection/Collection.php

public function exportTo($parser, $usePrefix = true, $includeLazyLoadColumns = true)
{
    if (!$parser instanceof AbstractParser) {
        $parser = AbstractParser::getParser($parser);
    }

    $array = $this->toArray(null, $usePrefix, TableMap::TYPE_PHPNAME, $includeLazyLoadColumns);

    return $parser->listFromArray($array, lcfirst($this->getPluralModelName()));
}

Propel/Runtime/Collection/ObjectCollection.php

public function toArray($keyColumn = null, $usePrefix = false, $keyType = TableMap::TYPE_CAMELNAME, $includeLazyLoadColumns = true, $alreadyDumpedObjects = [])
{
    $ret = [];
    $keyGetterMethod = 'get' . $keyColumn;

    /** @var $obj ActiveRecordInterface */
    foreach ($this->data as $key => $obj) {
        $key = null === $keyColumn ? $key : $obj->$keyGetterMethod();
        $key = $usePrefix ? ($this->getModel() . '_' . $key) : $key;
        $ret[$key] = $obj->toArray($keyType, $includeLazyLoadColumns, $alreadyDumpedObjects, true);
    }

    return $ret;
}

So once again it would be nice if we could use the configuration file to set these to TableMap::CAMELNAME but unfortunately that won't work.

sullimt
  • 391
  • 1
  • 4