Why json_decode() Returns null — and How to Fix It in WordPress

When you pass an array through json_encode() on the PHP side and then parse it with JSON.parse() or receive it via jQuery’s $.ajax, you may find that json_decode() returns null — or the JavaScript side receives unexpected string values instead of integers. The root cause is almost always a type mismatch in the PHP array.

Problem: json_decode() returns null on a string that looks like valid JSON, and json_last_error() reports an encoding or syntax error — making the decoded data unusable.

Solution: The most common causes are a UTF-8 BOM at the start of the string, mixed quotation marks from incorrect escaping, or a non-UTF-8 encoding. Use json_last_error_msg() to identify the exact issue, then sanitise the input with mb_convert_encoding() and trim() before calling json_decode().

Use var_dump() to inspect the array before encoding it. If the IDs are stored as strings instead of integers, the dump will look like this:

array(2) { [0]=> string(3) "212" [1]=> string(3) "211" }

But it should look like this:

array(2) { [0]=> int(212) [1]=> int(211) }

This problem often appears when you build a post-ID array using 'fields' => 'ids' combined with string concatenation (.=). The problematic pattern looks like this:

<?php
$args = [
    'post_type'      => 'custom_post_type',
    'posts_per_page' => -1,
    'fields'         => 'ids',
    'no_found_rows'  => true,
];

$custom_post_type = new WP_Query( $args );

if ( $custom_post_type->have_posts() ) {
    $custom_post_type_ids = [];
    while ( $custom_post_type->have_posts() ) {
        $custom_post_type->the_post();
        $custom_post_type_ids[] .= $post; // .= coerces $post to string
    }
    wp_reset_postdata();
}

The correct fix is to use 'fields' => 'id=>parent' together with wp_list_pluck(), which preserves integer types and avoids the manual loop entirely:

<?php
$args = [
    'post_type'      => 'custom_post_type',
    'posts_per_page' => -1,
    'fields'         => 'id=>parent',
    'no_found_rows'  => true,
];

$custom_post_type = new WP_Query( $args );

if ( $custom_post_type->have_posts() ) {
    $custom_post_type_ids = wp_list_pluck( $custom_post_type->posts, 'ID' );
    // $custom_post_type_ids is now an array of integers — safe to json_encode()
}

You could also cast each value with (int), but wp_list_pluck() is the cleaner, idiomatic WordPress solution.

NOTE: If json_decode() still returns null after fixing the types, check for encoding issues — any non-UTF-8 character in a string value will cause the entire JSON payload to be invalid. Run the output through json_last_error_msg() to get a specific diagnostic.