$value ) { if ( 'meta_data' === $key && is_array( $value ) ) { $value = self::normalize_meta_data( $value ); } $payload[ $key ] = is_array( $value ) ? self::normalize( $value ) : $value; } return $payload; } /** * Shape-tolerant reader for structured meta that may be stored in either * form: the historical JSON-encoded string, or (after a typed client push * lands through wc/v3) a native PHP array. Server-side consumers of * `_woocommerce_pos_data`-style meta must read through this — a bare * `json_decode( $raw )` fatals on PHP 8 the moment the storage holds an * array. * * @param mixed $raw Meta value as returned by get_meta(). * * @return array|null Decoded associative array, or null when the value is * neither a JSON object/array string nor an array. */ public static function decode_to_array( $raw ): ?array { if ( is_array( $raw ) ) { return $raw; } if ( is_object( $raw ) ) { $raw = wp_json_encode( $raw ); } if ( ! is_string( $raw ) || '' === $raw ) { return null; } $decoded = json_decode( $raw, true ); if ( JSON_ERROR_NONE !== json_last_error() || ! is_array( $decoded ) ) { return null; } return $decoded; } /** * Normalize structured meta values and derived display fields. * * @param array $meta_data Serialized REST meta entries. * * @return array */ private static function normalize_meta_data( array $meta_data ): array { foreach ( $meta_data as $index => $entry ) { // Top-level entity meta reaches the filters as live WC_Meta_Data objects // (they only become arrays at JSON-encode time); convert a copy to the // exact shape it would serialize to, and only swap it in when normalization // actually happens — untouched entries keep their original form so // revision hashes of scalar-only records are unchanged. $is_meta_object = $entry instanceof \WC_Meta_Data; if ( $is_meta_object ) { $entry = json_decode( wp_json_encode( $entry ), true ); } if ( ! is_array( $entry ) ) { continue; } $entry_changed = false; if ( isset( $entry['value'] ) && is_string( $entry['value'] ) ) { $raw = $entry['value']; $trimmed = trim( $raw ); $opening = substr( $trimmed, 0, 1 ); if ( '{' === $opening || '[' === $opening ) { $decoded = json_decode( $raw ); if ( JSON_ERROR_NONE === json_last_error() && ( is_array( $decoded ) || $decoded instanceof \stdClass ) ) { $entry['value'] = self::preserve_json_object_shape( $decoded ); $entry_changed = true; } } } foreach ( array( 'display_key', 'display_value' ) as $display_field ) { if ( array_key_exists( $display_field, $entry ) && ! is_string( $entry[ $display_field ] ) ) { unset( $entry[ $display_field ] ); $entry_changed = true; } } if ( ! $is_meta_object || $entry_changed ) { $meta_data[ $index ] = $entry; } } return $meta_data; } /** * Convert JSON objects to arrays unless doing so would change their wire shape. * * @param mixed $value Decoded JSON value. * * @return mixed */ private static function preserve_json_object_shape( $value ) { if ( is_array( $value ) ) { return array_map( array( __CLASS__, __FUNCTION__ ), $value ); } if ( ! $value instanceof \stdClass ) { return $value; } /** * Decoded object properties. * * @var array $properties */ $properties = array_map( array( __CLASS__, __FUNCTION__ ), get_object_vars( $value ) ); return array() === $properties || array_values( $properties ) === $properties ? (object) $properties : $properties; } }