PHP で JSON を操作するにはどうすればよいですか?この記事では、PHP の JSON 関連関数について詳しく説明し、その関数を使用する際の注意点をいくつか紹介します。
#私たちが取り組み始めた時代はまだ XML が主流でしたが、今では JSON データ形式がさまざまなアプリケーションでの送信のデファクトスタンダードになりました。 。近年プログラミングと開発を学び始めた学生は、データ送信に XML を使用することに一度も触れたことがないかもしれません。もちろん、時代は常に進歩しており、JSON は XML よりも便利で、高速で、読みやすくなっています。しかし実際には、意味論的な観点から見ると、XML の方が表現力が豊かです。
あまり言う必要はありませんが、PHP での JSON の操作は実際には非常に簡単で、最もよく使用される 2 つの関数は json_encode() と json_decode() です。彼らには気をつけるべきことがいくつかあり、楽しむべきこともいくつかあります。今日はそれを徹底的に勉強してみましょう。
まず、後続のエンコード操作のための配列を準備します。
$data = [ 'id' => 1, 'name' => '测试情况', 'cat' => [ '学生 & "在职"', ], 'number' => "123123123", 'edu' => [ [ 'name' => '<b>中学</b>', 'date' => '2015-2018', ], [ 'name' => '<b>大学</b>', 'date' => '2018-2022', ], ], ];
非常に単純な配列です。実際には特別なことは何もありません。データのネストといくつかの中国語記号と特殊記号があるだけです。通常の JSON エンコードの場合は、 json_encode() を直接使用します。
$json1 = json_encode($data); var_dump($json1); // string(215) "{"id":1,"name":"\u6d4b\u8bd5\u60c5\u51b5","cat":["\u5b66\u751f & \"\u5728\u804c\""],"number":"123123123","edu":[{"name":"<b>\u4e2d\u5b66<\/b>","date":"2015-2018"},{"name":"<b>\u5927\u5b66<\/b>","date":"2018-2022"}]}"
上記のエンコードされた JSON データに問題はありましたか?そう、一目ですべての漢字が \uxxxx 形式に変換されていることがわかる人も多いと思います。これは実際にはデフォルトで、 json_encode() 関数がこれらのマルチバイト文字を Unicode 形式のコンテンツに変換します。この問題は、json_encode() の直後に定数パラメータを追加することで解決でき、中国語の文字が正常に表示されるようになります。
$json1 = json_encode($data, JSON_UNESCAPED_UNICODE); var_dump($json1); // string(179) "{"id":1,"name":"测试情况","cat":["学生 & \"在职\""],"number":"123123123","edu":[{"name":"<b>中学<\/b>","date":"2015-2018"},{"name":"<b>大学<\/b>","date":"2018-2022"}]}"
もちろん、このままでは退屈すぎます。なぜなら、面接中に面接官が、この定数パラメータを使用せずにこの問題を解決できるかどうかを尋ねたことがあるからです。以下のコードを見るのをやめて、独自の解決策を考えていただけますか?
function t($data) { foreach ($data as $k => $d) { if (is_object($d)) { $d = (array) $d; } if (is_array($d)) { $data[$k] = t($d); } else { $data[$k] = urlencode($d); } } return $data; } $newData = t($data); $json1 = json_encode($newData); var_dump(urldecode($json1)); // string(177) "{"id":"1","name":"测试情况","cat":["学生 & "在职""],"number":"123123123","edu":[{"name":"<b>中学</b>","date":"2015-2018"},{"name":"<b>大学</b>","date":"2018-2022"}]}"
実際、これは非常に単純な解決策です。データ内のすべてのフィールドの内容を再帰的に urlencode() エンコードに変換し、json_encode() を使用してエンコードし、完了後に urldecode() を使用してデコードします。 。面白くないですか?実際、これは多くの古いプログラマーのちょっとしたトリックであり、JSON_UNESCAPED_UNICODE 定数は PHP5.4 以降でしか使用できなかったためです。以前は、エンコードされたデータに中国語を直接表示したい場合は、これしかできませんでした。
もちろん、今はPHP8の時代ですので、そんな面倒な操作は必要ありませんが、面接会場によっては経験豊富なプログラマーであるため、意図的にそのような質問をするケースも否定できません。こういうことだということだけは理解していただければ十分ですが、実際のプロジェクト開発においては、PHP5.4以下のバージョンを使っているシステムはほとんどないかもしれません(技術の更新が遅すぎるので、そんな会社に行かなくても大丈夫です) 。
JSON_UNESCAPED_UNICODE に加えて、使用できる定数パラメータが多数あり、このパラメータは並行して操作できます。つまり、複数の定数を使用できます。パラメータは一緒に使用されます。
$json1 = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_NUMERIC_CHECK | JSON_HEX_QUOT); var_dump($json1); // string(230) "{"id":1,"name":"测试情况","cat":["学生 \u0026 \u0022在职\u0022"],"number":123123123,"edu":[{"name":"\u003Cb\u003E中学\u003C\/b\u003E","date":"2015-2018"},{"name":"\u003Cb\u003E大学\u003C\/b\u003E","date":"2018-2022"}]}"
この一連のパラメータは、実際には、& 文字、a8093152e673feb7aba1828c43532094 HTML タグなど、データ内のいくつかの特殊記号用です。もちろん、すべてが表示されていない定数パラメータもありますので、公式マニュアルで指示を確認してください。
さらに、 json_encode() には反復レベルを表す 3 番目のパラメーターもあります。たとえば、上記のデータは 3 つのレベルを持つ多次元配列であるため、通常に解析するには少なくとも 3 を与える必要があります。次のコードでは、1 を指定しただけなので、返される内容は false になります。つまり、エンコードは成功しません。デフォルトでは、このパラメータの値は 512 です。
var_dump(json_encode($data, JSON_UNESCAPED_UNICODE, 1)); // bool(false)
デフォルトでは、 json_encode() はデータのタイプに応じてエンコードするため、配列の場合は、そのエンコードされたコンテンツがは JSON の配列形式です。このとき、JSON_FORCE_OBJECT を追加して、配列をオブジェクトの形式でエンコードさせることもできます。
$data = []; var_dump(json_encode($data)); // string(2) "[]" var_dump(json_encode($data, JSON_FORCE_OBJECT)); // string(2) "{}"
以前、数学関連の関数について話したときに、データ内に NAN のようなデータがある場合、 json_encode() はそれをエンコードできないことを学びました。実際、一部のエンコード不可能な値に対して JSON_PARTIAL_OUTPUT_ON_ERROR を追加できます。置換。以下のコードでは、これを使用して NAN を 0 に置き換えることができます。
$data = NAN; var_dump(json_encode($data)); // bool(false) var_dump(json_encode($data, JSON_PARTIAL_OUTPUT_ON_ERROR)); // 0
オブジェクトの場合、JSON エンコード後のコンテンツはシリアル化と同じであり、オブジェクトの属性のみが存在し、メソッドは存在しません。結局のところ、JSON の最大の用途はデータ送信であり、メソッドはデータ送信には実質的な影響を与えません。プロパティもカプセル化によって異なりますが、パブリックプロパティ、つまりパブリックプロパティのみがエンコードされます。
$data = new class { private $a = 1; protected $b = 2; public $c = 3; public function x(){ } }; var_dump(json_encode($data)); // string(7) "{"c":3}"
从这段测试代码中可以看出,protected 、 private 属性以及那个方法都不会被编码。
对于 JSON 解码来说,其实更简单一些,因为 json_decode() 的常量参数没有那么多。
var_dump(json_decode($json1)); // object(stdClass)#1 (5) { // ["id"]=> // int(1) // ["name"]=> // string(12) "测试情况" // ["cat"]=> // …… // …… var_dump(json_decode($json1, true)); // array(5) { // ["id"]=> // int(1) // ["name"]=> // string(12) "测试情况" // ["cat"]=> // …… // ……
首先还是看下它的第二个参数。这个参数的作用其实从代码中就可以看出来,如果不填这个参数,也就是默认情况下它的值是 false ,那么解码出来的数据是对象格式的。而我们将这具参数设置为 true 的话,那么解码后的结果就会是数组格式的。这个也是大家非常常用的功能,就不多做解释了。
var_dump(json_decode('{"a":1321231231231231231231231231231231231231231231231231231231231231231231233}', true)); // array(1) { // ["a"]=> // float(1.3212312312312E+72) // } var_dump(json_decode('{"a":1321231231231231231231231231231231231231231231231231231231231231231231233}', true, 512, JSON_BIGINT_AS_STRING)); // array(1) { // ["a"]=> // string(73) "1321231231231231231231231231231231231231231231231231231231231231231231233" // }
对于这种非常长的数字格式的数据来说,如果直接 json_decode() 解码的话,它会直接转换成 科学计数法 。我们可以直接使用一个 JSON_BIGINT_AS_STRING 常量参数,将这种数据在解码的时候直接转换成字符串,其实也就是保留了数据的原始样貌。注意,这里 json_decode() 函数的参数因为有那个转换对象为数组的参数存在,所以它有四个参数,第三个参数是迭代深度,第四个就是定义这些格式化常量值的。而且它和 json_encode() 是反过来的,迭代深度参数在前,格式常量参数在后面,这里一定要注意哦!
如果数据是错误的,那么 json_decode() 会返回 NULL 。
var_dump(json_decode("", true)); // NULL var_dump(json_decode("{a:1}", true)); // NULL
上面两段代码中我们都演示了如果编码或解码的数据有问题会出现什么情况,比如 json_encode() 会返回 false ,json_decode() 会返回 NULL 。但是具体的原因呢?
$data = NAN; var_dump(json_encode($data)); // bool(false) var_dump(json_last_error()); // int(7) var_dump(json_last_error_msg()); // string(34) "Inf and NaN cannot be JSON encoded"
没错,json_last_error() 和 json_last_error_msg() 就是返回 JSON 操作时的错误信息的。也就是说,json_encode() 和 json_decode() 在正常情况下是不会报错的,我们如果要获得错误信息,就得使用这两个函数来获取。这一点也是不少新手小同学没有注意过的地方,没错误信息,不抛出异常问题对我们的开发调试其实是非常不友好的。因为很可能找了半天都不知道问题出在哪里。
在 PHP7.3 之后,新增加了一个常量参数,可以让我们的 json_encode() 和 json_decode() 在编解码错误的时候抛出异常,这样我们就可以快速地定位问题了,现在如果大家的系统运行环境是 PHP7.3 以上的话,非常推荐使用这个常量参数让系统来抛出异常。
// php7.3 var_dump(json_encode($data, JSON_THROW_ON_ERROR)); // Fatal error: Uncaught JsonException: Inf and NaN cannot be JSON encoded var_dump(json_decode('', true, 512, JSON_THROW_ON_ERROR)); // PHP Fatal error: Uncaught JsonException: Syntax error
JSON_THROW_ON_ERROR 是对 json_encode() 和 json_decode() 都起效的。同样,只要设定了这个常量参数,我们就可以使用 try...catch 来进行捕获了。
try { var_dump(json_encode($data, JSON_THROW_ON_ERROR)); } catch (JsonException $e) { var_dump($e->getMessage()); // string(34) "Inf and NaN cannot be JSON encoded" }
在之前的文章中,我们学习过 使用Serializable接口来自定义PHP中类的序列化 。也就是说,通过 Serializable 接口我们可以自定义序列化的格式内容。而对于 JSON 来说,同样也提供了一个 JsonSerializable 接口来实现我自定义 JSON 编码时的对象格式内容。
class jsontest implements JsonSerializable { public function __construct($value) {$this->value = $value;} public function jsonSerialize() {return $this->value;} } print "Null -> " . json_encode(new jsontest(null)) . "\n"; print "Array -> " . json_encode(new jsontest(array(1, 2, 3))) . "\n"; print "Assoc. -> " . json_encode(new jsontest(array('a' => 1, 'b' => 3, 'c' => 4))) . "\n"; print "Int -> " . json_encode(new jsontest(5)) . "\n"; print "String -> " . json_encode(new jsontest('Hello, World!')) . "\n"; print "Object -> " . json_encode(new jsontest((object) array('a' => 1, 'b' => 3, 'c' => 4))) . "\n"; // Null -> null // Array -> [1,2,3] // Assoc. -> {"a":1,"b":3,"c":4} // Int -> 5 // String -> "Hello, World!" // Object -> {"a":1,"b":3,"c":4}
这是一个小的示例,只需要实现 JsonSerializable 接口中的 jsonSerialize() 方法并返回内容就可以实现这个 jsontest 对象的 JSON 编码格式的指定。这里我们只是简单地返回了数据的内容,其实和普通的 json_encode() 没什么太大的区别。下面我们通过一个复杂的例子看一下。
class Student implements JsonSerializable { private $id; private $name; private $cat; private $number; private $edu; public function __construct($id, $name, $cat = null, $number = null, $edu = null) { $this->id = $id; $this->name = $name; $this->cat = $cat; $this->number = $number; $this->edu = $edu; } public function jsonSerialize() { if (!$cat) { $this->cat = ['学生']; } if (!$edu) { $this->edu = new stdClass; } $this->number = '学号:' . (!$number ? mt_rand() : $number); if ($this->id == 2) { return [ $this->id, $this->name, $this->cat, $this->number, $this->edu, ]; } return [ 'id' => $this->id, 'name' => $this->name, 'cat' => $this->cat, 'number' => $this->number, 'edu' => $this->edu, ]; } } var_dump(json_encode(new Student(1, '测试一'), JSON_UNESCAPED_UNICODE)); // string(82) "{"id":1,"name":"测试一","cat":["学生"],"number":"学号:14017495","edu":{}}" var_dump(json_encode([new Student(1, '测试一'), new Student(2, '测试二')], JSON_UNESCAPED_UNICODE)); // string(137) "[{"id":1,"name":"测试一","cat":["学生"],"number":"学号:1713936069","edu":{}},[2,"测试二",["学生"],"学号:499173036",{}]]"
在这个例子中,我们在 jsonSerialize() 做了一些操作。如果数据没有传值,比如为 null 的情况下就给一个默认值。然后在 id 为 2 的情况下返回一个普通数组。大家可以看到最后一段注释中的第二条数据的格式。
这个接口是不是很有意思,相信大家可能对上面的 json_encode() 和 json_decode() 非常熟悉了,但这个接口估计不少人真的是没接触过,是不是非常有意思。
果然,什么事情都怕深挖。不学不知道,一学吓一跳,平常天天用得这么简单的 JSON 操作的相关函数其实还有很多好用的功能是我们不知道的。当然,最主要的还是看看文档,弄明白并且记住一些非常好用的常量参数,另外,抛出异常的功能也是这篇文章的重点内容,建议版本达到的朋友最好都能使用 JSON_THROW_ON_ERROR 来让错误及时抛出,及时发现哦!
测试代码:
https://github.com/zhangyue0503/dev-blog/blob/master/php/202012/source/11.深入学习PHP中的JSON相关函数.php
参考文档:
https://www.php.net/manual/zh/book.json.php
本文转载自:https://juejin.cn/post/7001652041814638600
作者:硬核项目经理
推奨学習: 「PHP ビデオ チュートリアル 」
以上がPHP の JSON 関連関数について話しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。