Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP/json_encode: dealing with mixed arrays and objects with numeric properties

I recently had to tackle a bug in a legacy PHP application. This application receives a request from another application with JSON of the form:

{
  "someList": [
    "item A",
    "item B"
  ],
  "ratings": {
    "0": 0.001234,
    "1": 0.0666,
    "2": 0.09876,
    "3": 0.777777
  }
}

When this is deserialized to a native PHP "associative array", both the list and the map (with keys 0, 1, 2, and 3) look like lists. That's fine, I can work around that. However, this application does calculations on this data and adds some more to it before serializing back to JSON in roughly the same format and sends it along to another application. Here's where the problem is. Out of the box json_encode($data) of the above results in:

{
  "someList": [
    "item A",
    "item B"
  ],
  "ratings": [
    0.001234,
    0.0666,
    0.09876,
    0.777777
  ]
}

My keys are all gone...

I see that I can use JSON_FORCE_OBJECT a la echo json_encode($data, JSON_FORCE_OBJECT) but then I get:

{
  "someList": {
    "0": "item A",
    "1": "item B"
  },
  "ratings": {
    "0": 0.001234,
    "1": 0.0666,
    "2": 0.09876,
    "3": 0.777777
  }
}

Now I've got keys in the first list, which I don't want. Is there a way to serialize this JSON such that someList will be a list (no keys) and ratings will be a map/object (with keys 0, 1, 2, and 3)?

like image 306
Josh Johnson Avatar asked Oct 06 '18 00:10

Josh Johnson


3 Answers

When calling json_encode on an array list, with numeric coherent indexes starting with 0, PHP will treat the list as an indexed array, and not an associative array. To force php to treat it as an associative array, you can cast the array to an object before calling json_encode.

Example:

$somelist = ["item A", "item B"];
$ratings = [0.001234, 0.666, 0.09876, 0.777777];
$final = ['someList' => $somelist, 'ratings' => (object) $ratings];

echo json_encode($final);

Output:

{["item A","item B"],{"0":0.001234,"1":0.666,"2":0.09876,"3":0.777777}}
like image 185
Andreas Daniloff Avatar answered Nov 14 '22 22:11

Andreas Daniloff


I've also struggled with returning JSON with both regular arrays and objects with numeric keys in the same response.

A solution I found is that you can build up a stdObject and defining the keys using $obj->{'0'} for example.

Here's a full example:


$decoded = json_decode('{
  "someList": [
    "item A",
    "item B"
  ],
  "ratings": {
    "0": 0.001234,
    "1": 0.0666,
    "2": 0.09876,
    "3": 0.777777
  }
}', true);

// Do stuff with $decoded['ratings']

$ratings = new \stdClass;
foreach ($decoded['ratings'] as $key => $rating) {
    $ratings->{$key} = $rating;
}

echo json_encode([
    'someList' => $decoded['someList'],
    'ratings' => $ratings
]);

Which then will output the following:

{
  "someList": [
    "item A",
    "item B"
  ],
  "ratings": {
    "0": 0.001234,
    "1": 0.0666,
    "2": 0.09876,
    "3": 0.777777
  }
}
like image 29
Ole Gunnar Nedrebø Avatar answered Nov 14 '22 22:11

Ole Gunnar Nedrebø


I wouldn't normally suggest "building" JSON manually, but concatenating bits of valid JSON should be fairly safe, and I think it's the only way you'll get this working.

$somelist = ["item A", "item B"];
$ratings = [0.001234, 0.666, 0.09876, 0.777777];

$json = sprintf(
    '{"somelist":%s,"ratings":%s}',
    json_encode($somelist),
    json_encode($ratings, JSON_FORCE_OBJECT)
);
echo $json;

Or in case you have a larger object to work with, you can loop over the data to do this programmatically.

$original_json = '{"someList":["item A","item B"],"ratings""{"0":0.001234,"1":0.0666,"2":0.09876,"3":0.777777}}';
$data = json_decode($original_json);
// do whatever you need to do with the data
array_walk($data->someList, function(&$v, $k){$v .= " is changed";});

$vsprintf_args = [];
$format_str = "{";
foreach($data as $k=>$v) {
    $format_str .= '%s:%s,';
    $vsprintf_args[] = json_encode($k);
    $vsprintf_args[] = json_encode($v, ($k === "ratings" ? JSON_FORCE_OBJECT : 0));
}
$format_str = trim($format_str, ",") . "}";
$json = vsprintf($format_str, $vsprintf_args);
echo $json;

Output:

{"somelist":["item A","item B"],"ratings":{"0":0.001234,"1":0.666,"2":0.09876,"3":0.777777}}
like image 30
miken32 Avatar answered Nov 14 '22 21:11

miken32