PHP/json_encode:处理混合数组和具有数字属性的对象

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

我最近不得不解决遗留 PHP 应用程序中的一个错误。此应用程序从另一个应用程序接收到 JSON 格式的请求:

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

将其反序列化为本机 PHP "associative array" 时,列表和映射(具有键 0、1、2 和 3)看起来都像列表。没关系,我可以解决这个问题。但是,此应用程序会对此数据进行计算,并在以大致相同的格式序列化回 JSON 并将其发送给另一个应用程序之前向其添加更多内容。这就是问题所在。开箱即用 json_encode($data) 以上结果:

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

我的钥匙都不见了...

我看到我可以使用 JSON_FORCE_OBJECT a la echo json_encode($data, JSON_FORCE_OBJECT) 但后来我得到:

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

现在我在第一个列表中找到了我不想要的键。 有没有办法序列化这个 JSON 使得 someList 将是一个列表(无键)并且 ratings 将是一个 map/object(具有键 0 , 1, 2, 和 3)?

我通常不会手动建议 "building" JSON,但连接有效 JSON 的位应该是相当安全的,我认为这是您获得此信息的唯一方法正在工作。

$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;

或者如果您有更大的对象要处理,您可以遍历数据以编程方式执行此操作。

$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;

输出:

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

在通过 json_decode($json, false); 解码原始 JSON 时,能够通过使用 stdClass 而不是关联数组找到解决方案。然后,当 json_encodeing 结果 stdClass 时,密钥将被保留。

这是一个完整的例子:

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

输出:

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

在数组列表上调用 json_encode 时,数字连贯索引从 0 开始,PHP 会将列表视为索引数组,而不是关联数组。要强制 php 将其视为关联数组,您可以在调用 json_encode.

之前将数组转换为对象

示例:

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

echo json_encode($final);

输出:

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

我也很难在同一响应中返回 JSON 常规数组和带有数字键的对象。

我找到的一个解决方案是您可以构建一个 stdObject 并使用 $obj->{'0'} 定义键。

这是一个完整的例子:


$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
]);

然后将输出以下内容:

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