json_decode() en PHP: deserializar JSON con todas las opciones y manejo de errores

json_decode() convierte una cadena JSON en un valor PHP. Parece simple, pero sus parámetros y el manejo de errores tienen matices que conviene conocer antes de usarla en producción con datos de APIs externas.

Sintaxis y el segundo parámetro clave

<?php
$json = '{"nombre":"Ana García","edad":28,"activa":true,"tags":["php","backend"]}';

// Por defecto: devuelve stdClass
$obj = json_decode($json);
echo $obj->nombre; // Ana García
echo $obj->edad;   // 28

// Con true como segundo argumento: devuelve array asociativo
$arr = json_decode($json, true);
echo $arr['nombre']; // Ana García
echo $arr['edad'];   // 28
?>

Elige true casi siempre: los arrays son más predecibles que los objetos stdClass, se validan fácilmente con isset() y no dan errores de propiedad indefinida. Usa el modo objeto cuando la librería que consumes lo espere explícitamente.

Controlar la profundidad máxima

El tercer parámetro de json_decode() es el límite de profundidad de anidamiento. Por defecto es 512. Si el JSON supera esa profundidad, la función devuelve null y establece un error:

<?php
$jsonProfundo = str_repeat('{"a":', 10) . '1' . str_repeat('}', 10); // profundidad 10

// Con límite 5: falla
$resultado = json_decode($jsonProfundo, true, 5);
var_dump($resultado); // NULL  ? error de profundidad

// Con límite suficiente: funciona
$resultado = json_decode($jsonProfundo, true, 15);
var_dump($resultado['a']['a']['a']); // array
?>

Manejo de errores con json_last_error()

<?php
$jsonRoto = '{"nombre": "Ana", "edad": 28,}'; // coma final inválida

$datos = json_decode($jsonRoto, true);

if ($datos === null && json_last_error() !== JSON_ERROR_NONE) {
    echo 'Error JSON: ' . json_last_error_msg();
    // Error JSON: Syntax error
}

// Constantes de error más comunes:
// JSON_ERROR_NONE          = 0   (sin error)
// JSON_ERROR_DEPTH         = 1   (profundidad máxima superada)
// JSON_ERROR_STATE_MISMATCH = 2  (JSON malformado)
// JSON_ERROR_CTRL_CHAR     = 3   (carácter de control inesperado)
// JSON_ERROR_SYNTAX        = 4   (error de sintaxis)
// JSON_ERROR_UTF8          = 5   (carácter UTF-8 malformado)
?>

JSON_THROW_ON_ERROR: la opción correcta desde PHP 7.3

El patrón con json_last_error() es verboso y fácil de olvidar. JSON_THROW_ON_ERROR como cuarto argumento convierte cualquier fallo en una JsonException:

<?php
function decodeSeguro(string $json): array
{
    try {
        $datos = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
        return $datos;
    } catch (JsonException $e) {
        throw new RuntimeException(
            'JSON inválido: ' . $e->getMessage(),
            previous: $e
        );
    }
}

$resultado = decodeSeguro('{"clave":"valor"}');
// ['clave' => 'valor']

decodeSeguro('{clave:valor}');
// RuntimeException: JSON inválido: Syntax error
?>

Ejemplo real: API de GitHub

<?php
$ctx = stream_context_create(['http' => [
    'header' => 'User-Agent: PHP-ejemplo/1.0',
]]);

$body = file_get_contents('https://api.github.com/repos/php/php-src', false, $ctx);
if ($body === false) {
    throw new RuntimeException('No se pudo conectar a GitHub');
}

$repo = json_decode($body, true, 512, JSON_THROW_ON_ERROR);

echo 'Repositorio: ' . $repo['full_name'] . "n";
echo 'Estrellas:   ' . $repo['stargazers_count'] . "n";
echo 'Forks:       ' . $repo['forks_count'] . "n";
?>

Ejemplo real: API meteorológica Open-Meteo

<?php
$url = 'https://api.open-meteo.com/v1/forecast?latitude=40.42&longitude=-3.70¤t_weather=true';
$respuesta = file_get_contents($url);
$datos     = json_decode($respuesta, true, 512, JSON_THROW_ON_ERROR);

$actual = $datos['current_weather'];
echo 'Temperatura en Madrid: ' . $actual['temperature'] . ' °C' . "n";
echo 'Viento: '                . $actual['windspeed']   . ' km/h' . "n";
?>

Ejemplo real: API de CoinGecko

<?php
$url   = 'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=eur';
$body  = file_get_contents($url);
$datos = json_decode($body, true, 512, JSON_THROW_ON_ERROR);

echo 'Bitcoin: ' . number_format($datos['bitcoin']['eur'], 2, ',', '.') . ' €';
?>

La documentación oficial de json_decode() incluye todos los flags disponibles y los detalles sobre el comportamiento con JSON muy grandes o con profundidades extremas.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP