131

I have a simple php structure with 3 nested arrays.

I do not use particular objects and I build myself the arrays with 2 nested loops.

Here is a sample of the var_dump of the array I want to convert to Json.

array (size=2)
  'tram B' => 
    array (size=2)
      0 => 
        array (size=3)
          'name' => string 'Ile Verte' (length=9)
          'distance' => int 298
          'stationID' => int 762
      1 => 
        array (size=3)
          'name' => string 'La Tronche Hôpital' (length=18)
          'distance' => int 425
          'stationID' => int 771
  16 => 
    array (size=4)
      0 => 
        array (size=3)
          'name' => string 'Bastille' (length=8)
          'distance' => int 531
          'stationID' => int 397
      1 => 
        array (size=3)
          'name' => string 'Xavier Jouvin' (length=13)
          'distance' => int 589
          'stationID' => int 438

In another script I have a similar structure and json_encode works fine. So I don't understand why json_encode won't work here.

Edit : there seems to be a problem with the encoding. When mb_detect_encoding returns ASCII, the json_encode works but when it returns UTF8, it doesn't work anymore.

Edit2 : json_last_error() returns JSON_ERROR_UTF8 which means : Malformed UTF-8 characters, possibly incorrectly encoded.

Programmer
  • 121,791
  • 22
  • 236
  • 328
Matthieu Riegler
  • 31,918
  • 20
  • 95
  • 134
  • PHP manual says `This function only works with UTF-8 encoded data.` so there shouldn't be any problem with encoding. – MahanGM Oct 14 '13 at 18:37
  • 14
    Try to use `utf8_encode()` on your `name` array fields before you hand the string to `json_encode()`. – MahanGM Oct 14 '13 at 18:43
  • Thx ! I just came myself to this solution which solved my problem. – Matthieu Riegler Oct 14 '13 at 18:44
  • Yeah, saw the answer. Good luck. – MahanGM Oct 14 '13 at 18:44
  • 3
    Use [`JSON_PARTIAL_OUTPUT_ON_ERROR` option](http://php.net/manual/en/function.json-encode.php#refsect1-function.json-encode-parameters) to see the problem (eg. the field with UTF8 will be null). – Peter Krauss May 29 '16 at 18:30
  • So it sounds like it should be returning false in the case of bad input. From the docs: `Returns a JSON encoded string on success or false on failure.` Nobody here has answered why empty string is returned instead of false. – Phil Feb 23 '21 at 11:18

14 Answers14

297

Well after 2 hours of digging (cf Edits)

I found out following :

  • In my case it's a encoding problem
  • mb_detect_encoding returns probably a faulty response, some strings were probably not UTF-8
  • using utf8_encode() on those string solved my problem, but see note below

Here is a recursive function that can force convert to UTF-8 all the strings contained in an array:

function utf8ize($d) {
    if (is_array($d)) {
        foreach ($d as $k => $v) {
            $d[$k] = utf8ize($v);
        }
    } else if (is_string ($d)) {
        return utf8_encode($d);
    }
    return $d;
}

Use it simply like this:

echo json_encode(utf8ize($data));

Note: utf8_encode() encodes ISO-8859-1 string to UTF-8 as per the docs so if you are unsure of the input encoding iconv() or mb_convert_encoding() may be better options as noted in comments and other solutions.

Gazzer
  • 4,524
  • 10
  • 39
  • 49
Matthieu Riegler
  • 31,918
  • 20
  • 95
  • 134
  • What about passing it on ? :) – Matthieu Riegler Dec 29 '15 at 15:09
  • I should add that this problem showed up for me right after dumping a MySQL DB from a Windows server and moving it to an Ubuntu server. Must have been something in the dump. – AJB Apr 23 '16 at 02:06
  • Use [`JSON_PARTIAL_OUTPUT_ON_ERROR` option](http://php.net/manual/en/function.json-encode.php#refsect1-function.json-encode-parameters) to see the problem (eg. the field with UTF8 will be null). – Peter Krauss May 29 '16 at 18:33
  • 3
    If reading from the database just use, $conn->set_charset("utf8"); – Andrew Briggs Feb 17 '17 at 23:38
  • 1
    PHP developers should embed this utf8 option in the command's parameters! – Heitor Aug 05 '17 at 08:22
  • 1
    Thank you for this. And if you need foreign characters change: `return utf8_encode($d);` to: `return iconv(mb_detect_encoding($d), "UTF-8", $d);` – dbx Feb 22 '18 at 21:30
  • As of PHP 8.2.0, `utf8_encode()` is deprecated. Replacing `utf8_encode($d)` with `iconv('ISO-8859-1', 'UTF-8', $d)` seems to be a good workaround. @dbx's use of `mb_detect_encoding($d)` may be needed if `'ISO-8859-1'` doesn't work. – VeryColdAir May 15 '23 at 14:44
40

Matthieu Riegler presented really good solution however I had to slightly modify it to handle objects too:

function utf8ize($d) {
    if (is_array($d)) 
        foreach ($d as $k => $v) 
            $d[$k] = utf8ize($v);

     else if(is_object($d))
        foreach ($d as $k => $v) 
            $d->$k = utf8ize($v);

     else 
        return utf8_encode($d);

    return $d;
}

One more note: json_last_error() may be helpful in debugging json_encode()/json_encode() functions.

Adam Bubela
  • 9,433
  • 4
  • 27
  • 31
  • Shouldn't it be `elseif` instead of `else if`? (i.e. no blank). – Uwe Keim Nov 24 '16 at 14:00
  • 2
    @UweKeim according to the PHP documentation "elseif and else if will only be considered exactly the same when using curly brackets" which means they are equivalents as long as you don't use the colon notation e.g. `if(): elseif:` – Adam Bubela Nov 24 '16 at 16:30
  • You should insert a `else if(is_int($d)||is_bool($d)) return $d;` before the last else, because of: `{"success":true, "message":"Ⲃⲟⲟⲉⲁⲛ ⲁⲛⲇ Ⲓⲛϯⲉⲉꞅ"}` – David Refoua Apr 21 '17 at 15:31
  • Just like @paul-peelen recommended to @matthieu-riegler: Change the the last `else` for `else if(is_string ($d))`; otherwise you will be changing everything to strings (e.g. `INT` will become a `STRING`). – Bruno Serrano Sep 02 '17 at 13:44
  • Also the json_last_error_msg() function will return a more user-friendly message as to why this is failing. – John Langford Jan 03 '18 at 23:36
  • that final `return $d;` will never be called. – Kae Verens Dec 09 '21 at 15:43
39

For me, the answer to this problem was setting charset=utf8 in my PDO connection.

$dbo = new PDO('mysql:host=localhost;dbname=yourdb;charset=utf8', $username, $password);
Dharman
  • 30,962
  • 25
  • 85
  • 135
fayd
  • 391
  • 3
  • 3
10

Adam Bubela also presented really good solution who helped me solved my problem, and here is the simplified function :

function utf8ize($d)
{ 
    if (is_array($d) || is_object($d))
        foreach ($d as &$v) $v = utf8ize($v);
    else
        return utf8_encode($d);

    return $d;
}
Alex
  • 537
  • 5
  • 17
7

I have exactly the same problem on PHP 5.6. I use Open Server + Nginx on Windows 7. All charsets are set to UTF-8. In theory, according the official documentation, flag

JSON_UNESCAPED_UNICODE

should solve this. Unfortunately this is not my case. I do not know, why. All snippets above do not solve my problem, thus I have found my own implementation. I believe it could help someone. At least, Russian letters pass the test.

function utf8ize($d) {
    if (is_array($d) || is_object($d)) {
        foreach ($d as &$v) $v = utf8ize($v);
    } else {
        $enc   = mb_detect_encoding($d);

        $value = iconv($enc, 'UTF-8', $d);
        return $value;
    }

    return $d;
}
6

This accepted answer works. But in case you are getting your data from MySQL (as I was) there is an easier way.

Once you open you database, before you query you can set the character set using mysqli as follows:

/* change character set to utf8 | Procedural*/
if (!mysqli_set_charset($link, "utf8")) {
    printf("Error loading character set utf8: %s\n", mysqli_error($link));
    exit();
}

OR

/* change character set to utf8 | Object Oriented*/
if (!$mysqli->set_charset("utf8")) {
        printf("Error loading character set utf8: %s\n", $mysqli->error);
        exit();
 }

LINK: http://php.net/manual/en/mysqli.set-charset.php

Kholofelo
  • 694
  • 9
  • 15
  • Note: $mysqli is the name of the variable that you populated when you created your mysql connection. Your variable name may vary. Example $mysqli = new mysqli($host, $user, $password, $db, $port); In addition, as mentioned in the comments above, recommend using utf8mb4 rather than utf8 for newer versions of MySql. – Brian Swift Jan 05 '22 at 23:15
5

I ran into this issue on a server running an older version of PHP (5.2). I was using the JSON_FORCE_OBJECT flag, and apparently that isn't supported until 5.3

So if you're using that flag be sure to check your version!

A workaround appears to be just casting to an object before encoding, like:

json_encode((object)$myvar);
Shane N
  • 1,742
  • 2
  • 17
  • 24
4

I was getting data from ob_get_clean() and had the same problem, but above solutions don't work for me. In my case solution was this, maybe it will help somebody.

$var = mb_convert_encoding($var, 'UTF-8');
zdeniiik
  • 222
  • 3
  • 14
3

The return of mb_detect_encoding may not be correct:

$data = iconv('UTF-8', 'ISO-8859-1', 'La Tronche Hôpital');
var_dump(
    mb_detect_encoding($data),
    mb_detect_encoding($data, array('ISO-8859-1', 'UTF-8'))
);

Depending on the default detection order, the above may return different results, so the encoding is falsely being reported as UTF-8. (Here's a larger example.)

It is likely that your data is not encoded as UTF-8, so json_encode is returning false. You should look at converting your strings to UTF-8 before JSON-encoding:

$fromEncoding = 'ISO-8859-1'; // This depends on the data

array_walk_recursive($array, function (&$value, $key, $fromEncoding) {
    if (is_string($value)) {
        $value = iconv($fromEncoding, 'UTF-8', $value);
    }
}, $fromEncoding);
cmbuckley
  • 40,217
  • 9
  • 77
  • 91
2

Since the output is going to end up as JSON (string) anyway, here is a function that can handle all kind of variable type:

    function utf8ize($arg)
    {
        if (is_array($arg))
            foreach ($arg as $k => $v)
                $arg[$k] = utf8ize($v);
         else if(is_object($arg))
            return utf8ize((array) $arg);
         else
            return utf8_encode(strval($arg));
        return $arg;
    }

This answer is improvement from Matthieu Riegler and Adam Bubela's answer, but this one can handle all type of variable / data type (e.g.: resource like file handle/stream/phpgd/sql connection)

I'm using cast as array when the argument is object in order to prevent error if the object property is read only or private/protected.

Also, on else case, I strval it to handle other data type like resource

I'm using this function to 'force' a variable into string (e.g.: when var_dump-ing or logging to text file)

Kristian
  • 2,456
  • 8
  • 23
  • 23
1

I have improved Adam Bubela's answer. I just hate it when blocks are not closed by { and }. It's cleaner and you don't introduce bugs or maybe it's that I did use Perl in the past :)

<?php

class App_Updater_String_Util {    
    /**
     * Usage: App_Updater_String_Util::utf8_encode( $data );
     *
     * @param mixed $d
     * @return mixed
     * @see http://stackoverflow.com/questions/19361282/why-would-json-encode-returns-an-empty-string
     */
    public static function utf8_encode($d) {
        if (is_array($d)) {
            foreach ($d as $k => $v) {
                $d[$k] = self::utf8_encode($v);
            }
        } elseif (is_object($d)) {
            foreach ($d as $k => $v) {
                $d->$k = self::utf8_encode($v);
            }
        } elseif (is_scalar($d)) {
            $d = utf8_encode($d);
        }

        return $d;
    }
}

?>
Svetoslav Marinov
  • 1,498
  • 14
  • 11
1

using utf8_encode() on those string solved my problem.

mobizen
  • 483
  • 8
  • 20
0

If you get this data from a database, use mysqli_set_charset($connection, "utf8"); in connection when get the params from database

barbsan
  • 3,418
  • 11
  • 21
  • 28
0

In my case the reason for the API returning an empty JSON was that the properties of the object were set to private.

frankfurt-laravel
  • 3,731
  • 2
  • 20
  • 29