diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-20 14:50:13 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-20 15:08:49 +0900 |
| commit | 6c30a3e065b4d7f597331b0b79a226f8e3480976 (patch) | |
| tree | 4ccff02af5cf3c692faf0d7f744b4c51d1268430 /crates | |
| parent | dc43cb1c663378130d727d9cee414a6a37433047 (diff) | |
| download | php-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.rs | 41 |
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( |
