Question

Loop Through Dynamic JSON Keys in PHP with foreach

phpjson

Question

How can I parse this JSON file in PHP and loop through its unknown top-level names and nested fields?

{
  "John": {
    "status": "Wait"
  },
  "Jennifer": {
    "status": "Active"
  },
  "James": {
    "status": "Active",
    "age": 56,
    "count": 10,
    "progress": 0.0029857,
    "bad": 0
  }
}

I can access known values like this:

<?php

$string = file_get_contents('/home/michael/test.json');
$json_a = json_decode($string, true);

echo $json_a['John']['status'];
echo $json_a['Jennifer']['status'];

However, I do not know the names (John, Jennifer, and so on) or every nested key (age, count, and so on) in advance. How can I use foreach to iterate through all names, keys, and values?

Short Answer

You will learn how PHP represents decoded JSON, how to choose arrays or objects with json_decode(), and how nested foreach loops handle JSON with dynamic keys. You will also learn how to safely read optional fields such as age when they may not exist for every record.

Concept

JSON objects contain named properties. In the example, the outer JSON object has dynamic property names such as John, Jennifer, and James. Each name points to another JSON object containing details.

When you call json_decode($json, true), PHP converts JSON objects into associative arrays:

$data = json_decode($json, true);

The resulting shape is conceptually:

[
    'John' => ['status' => 'Wait'],
    'Jennifer' => ['status' => 'Active'],
    'James' => [
        'status' => 'Active',
        'age' => 56,
        'count' => 10,
        'progress' => 0.0029857,
        'bad' => 0,
    ],
]

An associative array stores values under keys, rather than only numeric positions. foreach is useful because it gives you both the current key and its value without knowing the keys beforehand.

This matters in real programs because API responses, configuration files, imported data, and user-generated JSON often contain data whose keys or optional fields cannot be hard-coded reliably.

Mental Model

Think of the decoded JSON as a filing cabinet.

  • The outer cabinet has drawers labelled John, Jennifer, and James.
  • Each drawer contains a folder of labelled facts, such as status, age, and count.
  • You do not need to know every drawer label first: foreach opens each drawer in turn.
  • A second foreach reads every labelled fact inside that drawer.

The variable before => receives the label (key), and the variable after => receives what is stored under that label (value).

Take Quiz

Syntax and Examples

Use json_decode() with true when you want associative arrays, then use nested foreach loops.

<?php

$json = file_get_contents('/home/michael/test.json');
$people = json_decode($json, true, 512, JSON_THROW_ON_ERROR);

foreach ($people as $name => $details) {
    echo "$name\n";

    foreach ($details as $field => $value) {
        echo "  $field: $value\n";
    }
}

Possible output:

John
  status: Wait
Jennifer
  status: Active
James
  status: Active
  age: 56
  count: 10
  progress: 0.0029857
  bad: 0

The outer loop assigns:

  • $name: a dynamic top-level key, such as James
  • $details: that person's nested associative array

The inner loop assigns:

  • $field: a nested key, such as progress
  • $value: the corresponding value, such as 0.0029857

If you only need one known nested field, such as status, use a single loop:

foreach ($people as $name => $details) {
    $status = $details['status'] ?? 'Unknown';
    echo "$name: $status\n";
}

The ?? operator provides a fallback if a record does not contain status.

Step by Step Execution

Consider this smaller JSON document:

$json = '{
  "John": {"status": "Wait"},
  "James": {"status": "Active", "age": 56}
}';

$people = json_decode($json, true, 512, JSON_THROW_ON_ERROR);

foreach ($people as $name => $details) {
    echo "$name\n";

    foreach ($details as $field => $value) {
        echo "$field = $value\n";
    }
}

Execution trace:

  1. json_decode(..., true) converts the outer JSON object into an associative array.
  2. On the first outer-loop iteration, $name is John and $details is ['status' => 'Wait'].
  3. The inner loop runs once: $field is status and $value is Wait.
  4. On the second outer-loop iteration, $name is James and $details is ['status' => 'Active', 'age' => 56].
  5. The inner loop runs twice, once for status and once for age.
  6. The loops finish after every top-level key and every nested field has been visited.

Real World Use Cases

Dynamic JSON keys appear in many practical situations:

  • Configuration files: Feature flags may be grouped by an unknown environment or service name.
  • API responses: A reporting API may return account IDs as object keys, with metrics inside each account.
  • Import scripts: A JSON export may use product SKUs, usernames, or dates as keys.
  • Monitoring data: Server names can be keys, while CPU, memory, and status values are nested fields.
  • Translation files: Language codes such as en, fr, and de can map to nested message collections.

For example, an API might return server health by host name:

foreach ($servers as $hostname => $metrics) {
    $cpu = $metrics['cpu'] ?? null;
    echo "$hostname uses $cpu% CPU\n";
}
Take Quiz

Real Codebase Usage

In production PHP code, developers usually combine iteration with validation and safe access.

Validate file and JSON input

<?php

$path = '/home/michael/test.json';

if (!is_readable($path)) {
    throw new RuntimeException("Cannot read JSON file: $path");
}

$contents = file_get_contents($path);
$people = json_decode($contents, true, 512, JSON_THROW_ON_ERROR);

if (!is_array($people)) {
    throw new UnexpectedValueException('Expected the JSON root to be an object.');
}

JSON_THROW_ON_ERROR makes invalid JSON fail immediately with a JsonException, instead of silently returning null.

Use guard clauses for unexpected records

External data can be incomplete or malformed. Skip entries that do not have the expected array structure.

foreach ($people as $name => $details) {
    if (!is_array($details)) {
        continue;
    }

    $status = $details['status'] ?? 'Unknown';
    echo "$name: $status\n";
}

Transform records into a predictable list

Dynamic-key JSON is sometimes awkward for later processing. A common pattern is to make the key into a regular field.

$rows = [];

foreach ($people as $name => $details) {
    if (!is_array($details)) {
        continue;
    }

    $rows[] = [
        'name' => $name,
        'status' => $details['status'] ?? 'Unknown',
        'age' => $details['age'] ?? null,
    ];
}

Now every item has the same top-level shape, which is convenient for templates, database inserts, filtering, and tests.

Common Mistakes

Leaving array keys unquoted

This is incorrect:

// Broken: status is treated as a constant in older PHP versions.
echo $json_a['John'][status];

Use quotes for string keys:

echo $json_a['John']['status'];

Assuming every field exists

John does not have an age field. This can produce an undefined array key warning:

// Broken for people without an age.
echo $details['age'];

Use a fallback or check for the key:

echo $details['age'] ?? 'Not provided';

Using object syntax after decoding into arrays

With the second argument set to true, the result uses arrays. This is incorrect:

// Broken: $details is an array, not an object.
echo $details->status;

Use array access:

echo $details['status'];

Forgetting that JSON decoding can fail

Invalid JSON returns null by default, which can lead to later errors. Prefer:

$people = json_decode($json, true, 512, JSON_THROW_ON_ERROR);

Handle JsonException at the boundary of your application if invalid user or remote input is possible.

Confusing a JSON object with a JSON array

Your example begins with {}, so it is a JSON object and its values have named keys. A JSON array begins with [] and usually has numeric indexes after decoding.

Comparisons

Approachjson_decode() callAccess syntaxBest when
Associative arraysjson_decode($json, true)$person['status']You want array functions and clear dynamic-key iteration.
PHP objectsjson_decode($json)$person->statusProperty names are known and valid PHP property names.
Nested foreachOne loop per nesting levelforeach ($items as $key => $value)You need every dynamic key and nested value.
Direct access$people['John']['status']Known keys onlyThe specific key is guaranteed and intentionally targeted.

When decoding as objects, iteration is still possible:

$people = json_decode($json, false, 512, JSON_THROW_ON_ERROR);

foreach ($people as $name => $details) {
    echo $name . ': ' . $details->status . "\n";
}

For this type of data, associative arrays are often simpler because fields may be optional and array access works naturally with ??.

Take Quiz

Cheat Sheet

// Decode a JSON object into associative arrays
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);

// Loop through dynamic top-level keys
foreach ($data as $key => $value) {
    // $key: for example, "James"
    // $value: nested array
}

// Loop through nested dynamic keys
foreach ($data as $name => $details) {
    foreach ($details as $field => $value) {
        echo "$name - $field: $value\n";
    }
}

// Safely read an optional field
$status = $details['status'] ?? 'Unknown';

// Check a field explicitly
if (array_key_exists('age', $details)) {
    echo $details['age'];
}

Rules to remember:

  • Use true in json_decode() for associative arrays.
  • Quote string array keys: ['status'], not [status].
  • Use => in foreach when you need both key and value.
  • Use ?? for fields that may be missing.
  • Use JSON_THROW_ON_ERROR when decoding data you need to trust.

FAQ

How do I loop through unknown JSON keys in PHP?

Decode the JSON into an associative array and use foreach ($data as $key => $value). The $key variable receives each unknown property name.

Why does json_decode($json, true) use square brackets?

The true argument tells PHP to convert JSON objects to associative arrays. PHP arrays use square-bracket access, such as $person['status'].

How do I access a nested JSON value in PHP?

For known keys, use nested array access: $data['James']['status']. For unknown keys, iterate with nested foreach loops.

What happens if a JSON field is missing?

Direct access can raise an undefined array key warning. Use $details['age'] ?? null or another appropriate default value.

Should I use json_last_error() or JSON_THROW_ON_ERROR?

For modern PHP, JSON_THROW_ON_ERROR is usually clearer because malformed JSON raises a JsonException at the decoding line.

Can I decode JSON into objects instead of arrays?

Yes. Omit the true argument and use object access such as $person->status. Do not mix object access with array access.

Does foreach preserve the names from the JSON file?

Yes. For a JSON object decoded into an associative array, foreach ($data as $name => $details) gives each original property name in $name.

Take Quiz

Mini Project

Description

Build a small PHP command-line report that reads a people-status JSON file. The file uses unknown names as keys, and each person may contain different fields. The report demonstrates decoding JSON, iterating dynamic keys, and safely reading optional values.

Goal

Print one readable summary line for every person and calculate how many people have an Active status.

Requirements

  • Read JSON from a file named people.json.
  • Decode the JSON as associative arrays.
  • Print each person's name and status.
  • Print age when it is available.
  • Treat a missing status as Unknown.
  • Display the total number of active people.
Take Quiz

Keep learning

Related questions

View more