Pixelastic

You can clip our wings but we will always remember what it was like to fly.

Fix the floating issue with json_decode in PHP 5.3

When dealing with online API that are handling a lot of items (like Twitter or Facebook), you'd better be aware of a PHP limitation with json_decode.

json_decode is supposed to take a JSON string and return a PHP array or a PHP object from it.

Unfortunatly if one of the key is an int and is bigger than the max int value, it will be cast as float instead. And you'll lose precision in the process.

In my case it resulted in a complete unability to find a user in the database as the id didn't match anything. This was quite hard to find as I couldn't reproduce it on my local machine.

Know your system

My local system was a 64bits machine while the production servers were 32bits. And of course, max int precision is far bigger on 64bits machines so the error didn't pop in my tests.

If you're running PHP 5.4, the fix is easy. Just add the JSON_BIGINT_AS_STRING bitmask as 4th option like this

$decoded = json_decode($encoded, true, null, JSON_BIGINT_AS_STRING);

If you're running a 32bits machine with PHP 5.3 like me, it's a little more tricky.

The regexp

My solution is to parse the original JSON string and add quotes around ints so json_decode will keep them as string.

My first attempt was naive

preg_replace('/":([0-9]+),/', '":$1,', $encoded)

This will find any int between ": (marking the end of a key) and , (marking the start of the next key), and replace it with the same string but enclosed in quotes.

I soon found out that this did not cover all the cases, especially if the int key was the last of the JSON, it won't be followed by a , but by a } instead.

So, I adapted it a little bit :

preg_replace('/":([0-9]+)(,|})/', '":"$1"$2', $encoded)

Ok, it worked better. Any int key in the JSON, anywhere will be enclosed in quotes.

It was a little overkill so I decided to limit it to keys of at least 10 digits

preg_replace('/":([0-9]{10,})(,|})/', '":"$1"$2', $encoded)

Better. But still not perfect.

As I soon discovered, sometimes Facebook returned JSON containing JSON itself (in Request data or Credit order_info for example).

The previous approach would add quotes around ints in JSON escaped string and would thus corrupt the whole key.

This time, I added a final touch. I only added quotes around int that were not in an escaped JSON string themselves, by checking that the closing quote of the key wasn't escaped.

preg_replace('/([^\\\])":([0-9]{10,})(,|})/', '$1":"$2"$3', $encoded)

Here it is, the final fix. I might have forgotten some corner cases, but at least it works for my current application.

Hope it helps !


Comments

I believe this regex would work. It qualifies the http://www.json.org/ spec for numbers.

/:\s*(\-?\d+(\.\d+)?([e|E][\-|\+]\d+)?)/

preg_replace('/:\s*(\-?\d+(\.\d+)?([e|E][\-|\+]\d+)?)/', ': "$1"', $encoded);
Ryan Son 3/4/12
Ryan S
@Ryan : Thanks for the tip. I didn't think of checking the official spec for a more robust regexp. I'll definitely try this one. Thanks.
Timon 4/4/12
Tim
@Ryan : After a few tests, it seems that the regexp you submitted will break json containing json strings itself (like some API responses). I've reverted to my previous implementation for the time being.
Timon 5/4/12
Tim
um, hey, did that really work for 32bit 5.3 ? I think there are "" missing around $2 in your examples, now you're just matching something and replacing it with the same value
Jan Batoraon 8/6/12
Jan Batora
@Jan : You are absolutly right, it seems some of the "" where missing in the replacement regexps. I updated to post to with the fixes. Thanks for spotting it.
Timon 25/6/12
Tim
Nice idea. But there is an problem with the regex. It doesnt work if there is a space before or after ':' (the separator between a key and its value). Having such spaces doesn't invalidate the json and the facebook api sometimes returns json with such spaces.
So here's a fix for it:
preg_replace('/([^\\\])" *: *([0-9]{10,})(,|})/', '$1":"$2"$3', $encoded);
Jitheshon 20/3/13
Jithesh
@Jithesh : Damn Facebook API! I'm not currently working with this API anymore (and hope I'll never have too again), but this could really come in handy. Thanks for the fix.
Timon 18/6/13
Tim
... and 113 spam blocked

Adding a comment

Leave this field empty, it is only here to defeat spam bots
Will not be published