Short answer

  • json_encode is a built-in PHP function that turns a PHP value, most often an array or an object, into a JSON string.
  • Its basic form is json_encode($value). It takes an optional flags argument that changes the output, and an optional depth argument that limits how deeply nested the value may be.
  • On success it returns a JSON encoded string. On failure it returns false, and you can find out why with json_last_error() or json_last_error_msg().
  • A sequential PHP array becomes a JSON array in square brackets. An associative array, with string keys, becomes a JSON object in curly braces. A non-sequential numeric array also becomes a JSON object, because its keys are not a continuous sequence starting at zero.
  • Common uses are returning data from an API, passing data from PHP to JavaScript, and storing a structured value as text.

What json_encode Does

PHP works with its own values: arrays, objects, strings, integers, floats, booleans, and null. Other systems do not speak PHP. They speak text formats, and JSON, short for JavaScript Object Notation, is the text format most web systems use to exchange structured data. json_encode is the bridge between the two. It takes a PHP value and produces the JSON text that represents that value.

$user = [
    'name' => 'Ada',
    'age' => 36,
    'active' => true,
];

echo json_encode($user);
// {"name":"Ada","age":36,"active":true}

The output is compact, with no spaces and no line breaks. That compact form is the default, and it is what you want when a machine will read the result.

Two points about the definition matter from the start. json_encode produces a string: it does not send or print anything by itself, or change the original value. And it is part of PHP itself, listed in the manual as available in PHP 5 from version 5.2.0, and in PHP 7 and PHP 8, so no extra library is needed.

Developers reach for it to return data from an API, to pass data from PHP to JavaScript on the same page, to store a structured value such as user settings in a single database column or cache entry, and to exchange data between services written in different languages. In each case both sides must agree on details such as whether a value is a number or a string, and whether an empty collection is an array or an object. Those details are where real bugs live, and much of this article is about them.

Basic Syntax and What It Returns

The PHP manual gives the function this signature:

json_encode(mixed $value, int $flags = 0, int $depth = 512): string|false

The first argument, $value, is required, and can be any type except a resource: a special handle to an outside thing, such as an open file or a database connection, which has no JSON representation. The second argument, $flags, is an optional bitmask of constants that modify the output, defaulting to 0. The third argument, $depth, sets the maximum nesting depth, must be greater than zero, and defaults to 512, which is generous for ordinary data and still protects against runaway recursion.

The return type tells the whole error story in one line: string|false. On success, json_encode returns a JSON encoded string. On failure, it returns false. That is why the correct check after encoding is a strict comparison with false:

$json = json_encode($data);

if ($json === false) {
    echo 'Encoding failed: ' . json_last_error_msg();
}

Do not test the result with a loose check such as if (!$json), because a valid JSON string can be falsy in a loose test. For example, encoding the integer 0 produces "0", which is falsy in a loose test. A strict === false check avoids that trap entirely.

One more return value detail: the manual notes that json_encode generates a simple value, neither an object nor an array, when given a string, an integer, a float, or a boolean, and warns that some decoders may not accept such values. Wrap a lone scalar in an array or an object when the consumer expects a structured response, and keep your response shape consistent.

What Each PHP Type Becomes

JSON has only six kinds of value: objects, arrays, strings, numbers, booleans, and null. PHP has more types than that, so json_encode maps each PHP type onto a JSON type:

PHP valueJSON resultExample inputExample output
StringJSON string, double quoted'Hello'"Hello"
IntegerJSON number4242
FloatJSON number12.512.5
Boolean truetruetruetrue
Boolean falsefalsefalsefalse
Nullnullnullnull
Sequential arrayJSON array['red', 'green']["red","green"]
Associative arrayJSON object['a' => 1]{"a":1}
ObjectJSON objectAn object with public properties{"name":"Ada"}
ResourceEncoding failsAn open file handleReturns false

Strings deserve a closer look, because json_encode escapes some characters by default. The manual’s own example encodes an array containing <foo>, 'bar', "baz", &blong&, and é. The default output is:

["<foo>","'bar'","\"baz\"","&blong&","\u00e9"]

Double quotes inside a string are escaped with a backslash, because a JSON string itself uses double quotes. Non-ASCII characters are escaped as \u sequences by default, so é becomes \u00e9. Escaping does not change the data, only how the text looks.

Numbers also deserve a note. PHP distinguishes integers from floats, and json_encode preserves that distinction. But PHP values that arrive as strings stay strings, even when they look like numbers. If a database returns the age 36 as the string '36', json_encode produces "36", with quotes, not 36. Cast values to the right PHP type before encoding. The flag JSON_NUMERIC_CHECK can convert numeric strings automatically, but it is a blunt tool with real risks, explained in the flags section.

Booleans and null map directly: PHP true becomes JSON true, PHP false becomes JSON false, and PHP null becomes JSON null, always in lowercase, as JSON requires.

Arrays Versus Objects: the Rule That Causes Most Bugs

PHP has a single array type that plays two roles: an ordered list and a keyed map. JSON has two separate types: an array, written in square brackets, and an object, written in curly braces. json_encode must decide, for each PHP array, which JSON type to produce, and its rule is based entirely on the keys: a PHP array becomes a JSON array only when its keys are exactly 0, 1, 2, and so on, with no gaps and no string keys. Anything else becomes a JSON object. The manual states the rule this way: when encoding an array, if the keys are not a continuous numeric sequence starting from zero, all keys are encoded as strings, and the result is a JSON object.

$sequential = ['foo', 'bar', 'baz', 'blong'];
echo json_encode($sequential);
// ["foo","bar","baz","blong"]

The keys here are 0, 1, 2, and 3, a continuous sequence from zero, so the result is a JSON array.

$nonsequential = [1 => 'foo', 2 => 'bar', 3 => 'baz', 4 => 'blong'];
echo json_encode($nonsequential);
// {"1":"foo","2":"bar","3":"baz","4":"blong"}

The keys start at 1, not zero, so the result is a JSON object with string keys. The values are identical to the previous example, but the JSON shape is different, and a client that expects an array will fail or misbehave.

Removing an element creates the same trap:

$sequential = ['foo', 'bar', 'baz', 'blong'];
unset($sequential[1]);
echo json_encode($sequential);
// {"0":"foo","2":"baz","3":"blong"}

After unsetting index 1, the keys are 0, 2, and 3. There is a gap, so the result is an object, not an array. This is the most common source of “my API suddenly returns an object instead of an array” bugs, and it usually appears after filtering or deleting items from a list. The standard fix is to reindex before encoding:

$filtered = array_values($sequential);
echo json_encode($filtered);
// ["foo","baz","blong"]

array_values discards the old keys and rebuilds a continuous sequence from zero, so the result is a JSON array again. If you build a list by filtering, appending, or removing items, make array_values part of the routine before encoding.

An empty PHP array encodes as an empty JSON array:

echo json_encode([]);
// []

If the consumer expects an empty object instead, cast the value, as shown in the objects section. Do not guess: an empty array and an empty object are different JSON values, and a strict client will treat them differently.

Nested arrays follow the same rule at every level. Each nested array is judged on its own keys, independently of its parent. A list of user records is an outer sequential array containing inner associative arrays:

$users = [
    ['name' => 'Ada', 'age' => 36],
    ['name' => 'Ben', 'age' => 29],
];
echo json_encode($users);
// [{"name":"Ada","age":36},{"name":"Ben","age":29}]

The outer array has keys 0 and 1, so it becomes a JSON array, while each inner array has string keys, so each becomes a JSON object. When a nested list inside a record has a gap, only that nested level changes shape. When an API response looks wrong, check each level’s keys separately, starting from the level that changed shape.

Associative arrays always become JSON objects, whether or not you use JSON_FORCE_OBJECT. The flag only changes arrays that would otherwise become JSON arrays, forcing every list in the value, at every depth, into object form. Prefer targeted fixes: use array_values for lists that must stay lists, and cast a specific empty array to an object when only that one value must be an object.

Objects, Public Properties, and JsonSerializable

When the value being encoded is an object, json_encode includes, by default, only publicly visible properties. Private and protected properties are left out. That is deliberate: an object’s internal state is not part of its public representation, and exposing it in an API response could leak details the class author never intended to share.

class User {
    public string $name = 'Ada';
    public int $age = 36;
    private string $passwordHash = 'not-shown';
}

echo json_encode(new User());
// {"name":"Ada","age":36}

The private property does not appear in the output. If you need a different representation, the manual points to the JsonSerializable interface. A class that implements JsonSerializable defines a jsonSerialize method that returns the value json_encode should use:

class User implements JsonSerializable {
    public function __construct(
        private string $name = 'Ada',
        private int $age = 36,
    ) {}

    public function jsonSerialize(): array {
        return ['name' => $this->name, 'age' => $this->age];
    }
}

echo json_encode(new User());
// {"name":"Ada","age":36}

This pattern is the clean way to control an API representation. It keeps the encoding logic inside the class and lets you rename fields and omit sensitive fields. For simple data transfer, a plain associative array is often simpler. Reach for JsonSerializable when the object has behavior or private state that an array cannot express.

Casting an array to an object with (object) produces a stdClass instance, and json_encode treats it as an object even when the array is empty. That is the precise tool for the empty object case, and the safest way to produce an empty object for one field without forcing every other list in the response into object form.

echo json_encode((object) []);
// {}

Worked Example: Building an API Response

Suppose a script returns a single article summary from a blog API:

$article = [
    'title' => 'What Is json_encode?',
    'word_count' => 3120,
    'tags' => ['PHP', 'JSON', 'API'],
    'comments_open' => true,
    'author' => [
        'name' => 'Emma Hudson',
        'role' => 'Author',
    ],
];

$json = json_encode($article, JSON_THROW_ON_ERROR);
header('Content-Type: application/json; charset=UTF-8');
echo $json;

The response body is:

{"title":"What Is json_encode?","word_count":3120,"tags":["PHP","JSON","API"],"comments_open":true,"author":{"name":"Emma Hudson","role":"Author"}}

The tags list is sequential, so it becomes a JSON array. The author record is associative, so it becomes a JSON object. The integer stays an integer, and the boolean stays a boolean. The content type header tells the client that the body is JSON in UTF-8, which is part of a correct API response. And JSON_THROW_ON_ERROR means any encoding failure raises an exception instead of returning false.

Common Flags and When to Use Them

Flags are combined with the bitwise OR operator, a single vertical bar, as in JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES. Each flag changes one aspect of the output, and combining them is the normal pattern.

FlagWhat it doesUse it when
JSON_PRETTY_PRINTAdds whitespace and line breaks for readable outputDebugging, documentation, stored config files
JSON_UNESCAPED_SLASHESLeaves forward slashes unescapedURLs in output should stay readable
JSON_UNESCAPED_UNICODELeaves non-ASCII characters unescapedOutput should show real characters, not \u sequences
JSON_HEX_TAGEncodes < and > as \u003C and \u003EJSON embedded in HTML
JSON_HEX_AMPEncodes & as \u0026JSON embedded in HTML
JSON_HEX_APOSEncodes ' as \u0027JSON embedded in HTML
JSON_HEX_QUOTEncodes " as \u0022JSON embedded in an HTML attribute
JSON_FORCE_OBJECTEncodes every array as an objectA consumer requires object form, used with care
JSON_NUMERIC_CHECKConverts numeric strings to numbersNumbers arrive as strings, used with care
JSON_PRESERVE_ZERO_FRACTIONKeeps .0 on whole floatsA client must distinguish 12.0 from 12
JSON_PARTIAL_OUTPUT_ON_ERRORReturns partial output instead of falseBest effort output is acceptable and the failure is logged
JSON_INVALID_UTF8_IGNOREDrops invalid UTF-8 sequences instead of failingLossy output is acceptable, added in PHP 7.2
JSON_INVALID_UTF8_SUBSTITUTEReplaces invalid UTF-8 instead of failingLossy output is acceptable, added in PHP 7.2
JSON_UNESCAPED_LINE_TERMINATORSLeaves line separator characters unescapedAdded in PHP 7.1
JSON_THROW_ON_ERRORThrows an exception instead of returning falseCode uses exceptions, added in PHP 7.3

The slash and unicode flags are about readability. By default, json_encode escapes forward slashes, so a URL appears as http:\/\/example.com\/page, and escapes non-ASCII characters as \u sequences. Both escaped forms parse back to the original characters, so they are correct, but hard to read in logs. JSON_UNESCAPED_SLASHES and JSON_UNESCAPED_UNICODE remove those escapes and are safe for a pure JSON response, where the content type already declares UTF-8. If the JSON is embedded directly in an HTML page, use the hex flags instead, because a string containing </script> could otherwise close the script block early.

JSON_NUMERIC_CHECK needs a warning. It converts strings that look like numbers into numbers, which sounds helpful when a database returns numbers as strings. But it applies to every numeric string in the value, including ones that must stay strings. A phone number, a postal code with leading zeros, or an identifier such as '007' would lose its leading zeros or its string type, and a code such as '1e3' would become the number 1000. The manual’s own example shows '+123123' becoming 123123 and '1.2e3' becoming 1200. Prefer casting the specific fields you know are numbers, and leave JSON_NUMERIC_CHECK out unless you have checked every field in the value.

JSON_PRESERVE_ZERO_FRACTION solves a narrow but real problem. The manual’s example shows that json_encode(12.0) produces "12", while json_encode(12.0, JSON_PRESERVE_ZERO_FRACTION) produces "12.0". Some clients treat 12 and 12.0 as different types, so if your API contract promises floats, this flag keeps that promise for whole number values. The manual also notes that float encoding depends on the serialize_precision setting, so check that setting first if float output looks surprising.

JSON_PARTIAL_OUTPUT_ON_ERROR is a trade off. Normally, one bad value fails the whole encoding and json_encode returns false. With this flag, json_encode returns what it could encode instead. That can suit logging or best effort exports, but it silently changes data, so never use it for an API response where the client expects complete and accurate data, and log the error whenever you use it.

Strings, UTF-8, and Numbers

All string data passed to json_encode must be UTF-8 encoded. That requirement, stated in the manual, is the root cause of the most common json_encode failure: data from a database, a file, or an external API may arrive in another encoding, or contain bytes that are not valid UTF-8 at all. When that happens, json_encode returns false with a UTF-8 error. Fix the encoding where the data enters your system, rather than patching it at encoding time. The invalid UTF-8 flags in the table lose data by design, so use them only where lossy output is acceptable, such as display or logging, never for data you must preserve exactly.

Inside a valid UTF-8 string, json_encode handles the escaping for you: double quotes, backslashes, and control characters are escaped. Do not escape anything by hand, and do not build JSON by concatenating strings and variables. Hand built JSON breaks the moment a value contains a quote, a backslash, or a line break, and it opens injection holes when the value comes from user input. Always build the PHP value first, then encode it once.

Integers encode exactly, within the range PHP integers support. Floats are subtler, because most decimal fractions cannot be represented exactly in binary floating point, and the manual notes that float encoding depends on the serialize_precision setting, so check that setting first if float output looks surprising. Very large integers are a related edge case: some clients parse JSON numbers as floating point values and lose precision, so send an oversized identifier as a string, as a contract decision made before encoding.

Error Cases and How to Check for Them

json_encode fails cleanly: it returns false, and it records the reason where you can read it, with json_last_error() for an integer error code and json_last_error_msg() for a human readable message. The manual’s own note says that in the event of a failure to encode, json_last_error() can be used to determine the exact nature of the error. Use it immediately after the encoding call, before any other JSON function runs, because a later JSON call overwrites the recorded error.

The causes of failure, in the order you are most likely to meet them, are invalid UTF-8 in a string, a resource value somewhere in the data, recursion (a value that refers back to itself), a depth limit exceeded, a value with no JSON representation such as INF or NAN, and an object whose serialization fails. Fix each cause at its source: convert encodings, remove resources, break cycles, replace INF or NAN before encoding, and investigate depth errors before raising the limit. The classic check pattern logs the error and returns a generic message to the client, never the raw error details:

$json = json_encode($data);

if ($json === false) {
    $code = json_last_error();
    $message = json_last_error_msg();
    error_log("JSON encoding failed ($code): $message");
    echo json_encode(['error' => 'Could not encode response']);
    exit;
}

echo $json;

Note the strict comparison, the immediate reading of the error, and the logging. In production code, return a generic message to the client, not the raw error details or the raw data, because error text can reveal internal structure that an attacker can use.

The modern alternative, available since PHP 7.3, is JSON_THROW_ON_ERROR, under which a failure throws a JsonException instead of returning false:

try {
    $json = json_encode($data, JSON_THROW_ON_ERROR);
    echo $json;
} catch (JsonException $e) {
    error_log('JSON encoding failed: ' . $e->getMessage());
    http_response_code(500);
    echo json_encode(['error' => 'Internal server error']);
}

Choose one pattern per codebase and use it consistently. The exception pattern fits code that already uses try and catch blocks. The false check pattern fits simpler scripts and code that must run on PHP versions before 7.3. Skipping the check entirely is the real error, because an unchecked false gets echoed as an empty response, and the client receives an empty body with a success status, which is the hardest failure to debug.

json_encode Versus json_decode and serialize

json_encode turns a PHP value into a JSON string. json_decode does the reverse: it turns a JSON string back into a PHP value, as an object by default, or as an associative array when its second argument is true. A round trip through both should return a value equal to the original, except for type details such as object versus array shape and string versus number types. When a round trip changes a value, the cause is almost always a type detail at one end, not a bug in either function.

PHP’s serialize is different. It produces a PHP specific text representation that preserves PHP types and object classes, and it can only be read back by PHP. It suits storing PHP values for PHP to read later, such as cached objects, but it must never be used for data exchanged with other languages, and never for unserializing untrusted input, because unserializing attacker controlled data is a known security risk. For interchange, use json_encode. Do not mix the two: a JSON string is not a serialized value, and a serialized value is not JSON.

Finally, json_encode produces JSON text. It does not make a value safe for HTML, for SQL, or for a shell command, each of which has its own escaping rules: use the hex flags for HTML, prepared statements for database queries, and proper shell escaping for shell commands.

Frequently Asked Questions

What is json_encode in PHP? It is a built-in PHP function that converts a PHP value, such as an array or an object, into a JSON formatted string, used for API responses, for passing data to JavaScript, and for storing structured data as text.

What does json_encode return? A JSON encoded string on success, or false on failure. Check with a strict comparison, $json === false, and read the reason with json_last_error() or json_last_error_msg().

Why does my PHP array encode as a JSON object instead of an array? Because its keys are not a continuous numeric sequence starting at zero. A gap after removing an element, a starting index other than zero, or any string key makes json_encode produce an object. Reindex the list with array_values before encoding if the consumer expects an array.

How do I make json_encode output readable? Pass JSON_PRETTY_PRINT as the flags argument. It adds line breaks and indentation. Use it for debugging, documentation, and files humans read, and keep production API output compact.

How do I stop json_encode escaping slashes and unicode characters? Pass JSON_UNESCAPED_SLASHES to leave forward slashes as they are, and JSON_UNESCAPED_UNICODE to leave non-ASCII characters as they are. Both change only the text representation. The parsed data is identical either way.

How do I check for json_encode errors? Check for false with a strict comparison, then read json_last_error_msg() immediately, before any other JSON call. On PHP 7.3 and later, you can instead pass JSON_THROW_ON_ERROR and catch the resulting JsonException.

What happens to private properties when an object is encoded? They are left out. By default, only publicly visible properties are included. To control the output, implement JsonSerializable and return the representation you want from its jsonSerialize method.

What is the difference between json_encode and json_decode? json_encode turns a PHP value into a JSON string. json_decode turns a JSON string back into a PHP value. A round trip should return an equal value apart from the type details covered in this article.

Why does json_encode return false for my data? The most common reasons are invalid UTF-8 in a string, a resource in the value, a circular reference, a depth limit exceeded, or a value with no JSON representation such as INF or NAN. Read json_last_error_msg() immediately after the call to identify which one applies, then fix the data at its source.


Sources & references

  • PHP Manual, "json_encode: Returns the JSON representation of a value" (PHP 5 >= 5.2.0, PHP 7, PHP 8). Function description, parameters, return values, changelog, and examples 1 to 5, opened and read directly 26 Oct 2026. https://www.php.net/manual/en/function.json-encode.php
  • PHP Manual, "Predefined Constants" for JSON, listing the JSON flag and error constants, opened 26 Oct 2026. https://www.php.net/manual/en/json.constants.php
  • Literal phrase search for "what is json_encode", read 26 Oct 2026, used to establish the dominant search intent and the competing page formats. No competing page is cited as a factual source for PHP behavior; all PHP behavior claims rest on the PHP Manual entries above.

Emma Hudson

Editor of Amberaisle — clear, thoughtful explainers for curious readers. More about the editor →