aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-20 14:50:13 +0900
committernsfisis <nsfisis@gmail.com>2026-06-20 15:08:49 +0900
commit6c30a3e065b4d7f597331b0b79a226f8e3480976 (patch)
tree4ccff02af5cf3c692faf0d7f744b4c51d1268430 /crates
parentdc43cb1c663378130d727d9cee414a6a37433047 (diff)
downloadphp-shirabe-6c30a3e065b4d7f597331b0b79a226f8e3480976.tar.gz
php-shirabe-6c30a3e065b4d7f597331b0b79a226f8e3480976.tar.zst
php-shirabe-6c30a3e065b4d7f597331b0b79a226f8e3480976.zip
feat(php-shim): implement two-argument json_decode
Was a todo!() that panicked for every command reading composer.json (validate, status, show, update, ...). Parses via serde_json (built with preserve_order, so insertion order is kept) and converts to PhpMixed. Mirrors PHP's flagless json_decode: returns null rather than erroring on malformed input, and decodes objects to ArrayObject when assoc is false, to an associative array when true. Out-of-i64 and fractional numbers become floats, matching PHP's default non-bigint behaviour. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates')
-rw-r--r--crates/shirabe-php-shim/src/lib.rs41
1 files changed, 39 insertions, 2 deletions
diff --git a/crates/shirabe-php-shim/src/lib.rs b/crates/shirabe-php-shim/src/lib.rs
index 362e20f..381a56a 100644
--- a/crates/shirabe-php-shim/src/lib.rs
+++ b/crates/shirabe-php-shim/src/lib.rs
@@ -1916,8 +1916,45 @@ pub fn sys_get_temp_dir() -> String {
std::env::temp_dir().to_string_lossy().into_owned()
}
-pub fn json_decode(_s: &str, _assoc: bool) -> anyhow::Result<PhpMixed> {
- todo!()
+// PHP's two-argument `json_decode`: without JSON_THROW_ON_ERROR it never throws,
+// returning null on malformed input. With `assoc` false, JSON objects decode to
+// stdClass-equivalent ArrayObject values; with `assoc` true, to associative arrays.
+pub fn json_decode(s: &str, assoc: bool) -> anyhow::Result<PhpMixed> {
+ match serde_json::from_str::<serde_json::Value>(s) {
+ Ok(value) => Ok(json_value_to_php_mixed(value, assoc)),
+ Err(_) => Ok(PhpMixed::Null),
+ }
+}
+
+fn json_value_to_php_mixed(value: serde_json::Value, assoc: bool) -> PhpMixed {
+ match value {
+ serde_json::Value::Null => PhpMixed::Null,
+ serde_json::Value::Bool(b) => PhpMixed::Bool(b),
+ serde_json::Value::Number(n) => match n.as_i64() {
+ Some(i) => PhpMixed::Int(i),
+ // Integers beyond i64 and any fractional/exponent number decode to float,
+ // matching PHP's default (non-bigint) behaviour.
+ None => PhpMixed::Float(n.as_f64().unwrap_or(0.0)),
+ },
+ serde_json::Value::String(s) => PhpMixed::String(s),
+ serde_json::Value::Array(items) => PhpMixed::List(
+ items
+ .into_iter()
+ .map(|item| Box::new(json_value_to_php_mixed(item, assoc)))
+ .collect(),
+ ),
+ serde_json::Value::Object(entries) => {
+ let data: IndexMap<String, Box<PhpMixed>> = entries
+ .into_iter()
+ .map(|(k, v)| (k, Box::new(json_value_to_php_mixed(v, assoc))))
+ .collect();
+ if assoc {
+ PhpMixed::Array(data)
+ } else {
+ PhpMixed::Object(ArrayObject { data })
+ }
+ }
+ }
}
pub fn http_build_query_mixed(