6

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)?

1
  • I don't know any other solutions, but with JSON_FORCE_OBJECT after you decode the data, you got someList and ratings as objects, then you can force "someList" to be an array instead of object Commented Oct 6, 2018 at 0:54

4 Answers 4

3

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}}
Sign up to request clarification or add additional context in comments.

Comments

3

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
  }
}

Comments

1

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}}

4 Comments

Was hoping this wouldn't be the answer...my real-world issue involves a much larger object
Should be able to do a foreach loop over the decoded data and build it programmatically. See my edit.
Ah good idea, and thanks for the update! I found a way to do it without manually building json. Check out the answer I added.
Good to hear. I had thought that using true for the second parameter would result in everything being an object including things like somelist.
1

Was able to find a solution by using stdClass instead of an associative array when decoding the original JSON via json_decode($json, false);. Then when json_encodeing the resulting stdClass the keys will be preserved.

Here's a full example:

<?php

$json = <<<JSON
{
  "someList": [
    "item A",
    "item B"
  ],
  "ratings": {
    "0": 0.001234,
    "1": 0.0666,
    "2": 0.09876,
    "3": 0.777777
  }
}
JSON;

// Passing false for the second param (or omitting it)
// returns a stdClass instead of associative array
$data = json_decode($json, false);

echo json_encode($data, JSON_PRETTY_PRINT);

Which outputs:

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.