json_decode + ajax + private properties = DISASTER [error: JSON_ERROR_CTRL_CHAR]

Go To StackoverFlow.com

2

Goal:

  1. json_encode a PHP object that has private properties
  2. Send the encoded object as a datastring through Low Level AJAX using jQuery
  3. json_decode the PHP object in the AJAX URL to which the request is sent
  4. win

Problem:

On step 3, json_last_error is returning 3 (JSON_ERROR_CTRL_CHAR Control character error, possibly incorrectly encoded)

The Class:

class Stream {
    private $limit;
    private $type;
    private $sort;
    private $offset=0;
    private $userID;
    private $catID;
    private $content = array();
    private $num_posts;

    function __construct(){
        $a = func_get_args();
        $i = func_num_args();
        if (method_exists($this,$f='__construct'.$i)) {
            call_user_func_array(array($this,$f),$a);
        }
    }

    function __construct5($limit, $type, $sort, $userID, $catID){
        $this->limit = $limit;
        $this->type = $type;
        $this->sort = $sort;
        $this->userID = $userID;
        $this->catID = $catID;
        //$this->num_posts = $this->retrieveTotal();

        //$this->setContent(); 
    }

    function __get($name) {
        if(isset($this->$name)){
            return $this->$name;
        }
    }

        public function encodeJSON(){
        foreach ($this as $key => $value){
            if($key != 'content'){
                $json->$key = $value;
            }
        }
        return json_encode($json);
    }

    public function decodeJSON($json_str){
        $json = json_decode($json_str, true);
        echo '<br>error: '.json_last_error();

        foreach ($json as $key => $value){
            $this->$key = $value;
        }
    }
}

//create the object to be encoded
$strm = new Stream(5, 'toc', ' ', 1, ' ');

/*this test works

$d=$strm->encodeJSON();

$st = new Stream();
$st->decodeJSON($d);
*/

The AJAX function:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
    //load more posts
    $("#active").live("click", function() {
    var stream= '<?= $strm->encodeJSON();?>'; 
        var dataString = 'stream='+stream;
        var request = $.ajax({  
            type: "POST",  
            url: "ajax/loadmore.php",  
            data: dataString,
            beforeSend:function(data){
                $('.load-more').html('<img src="ajax/loader.gif" alt="Loading..." />');
            },
            success: function(html) {
                $('#active').remove();
                $('#stream').append(html);
            }
        });

        //ajax error reporting
        request.fail(function(jqXHR, textStatus) {
            $('#active').html(textStatus);
        });
    });
</script>

<a class='load-more' id='active'>load more posts</a>

The AJAX request (loadmore.php):

require_once'../../classes/stream.class.php';
$strm = new Stream();
$strm->decodeJSON($_POST['stream']);

What I have tried:

This snippet of code

$d=$strm->encodeJSON();

$st = new Stream();
$st->decodeJSON($d);

works fine. That would lead me to believe that AJAX is interfering with the decoding.

I have also tried changing $json = json_decode($json_str, true); to $json = json_decode(utf8_encode($json_str), true); and nothing changes.

NOTE: suggesting that I make the class properties public is NOT a solution

EDIT: when I echo the string, { "limit": "5", "type": "toc", "sort": " ", "offset": "0", "userID": "3", "catID": " ", "num_posts": "2" } being sent to decodeJSON it tests as valid

This screenshot shows the arg $json_str that is being sent to decodeJSON($json_str) and the error code. json_str param and error

2012-04-04 06:12
by Carrie Kendall


1

After much trial and error I figured out the issue. When I was instantiating the Stream objectthat was to be encoded, instead of using 1 i was using $userID which was being cast as a string and messing with the URI encoding

stream=%7B%22limit%22%3A%225%22%2C%22type%22%3A%22toc%22%2C%22sort%22%3A%22%20%22%2C%22offset%22%3A%220%22%2C%22userID%22%3A%223%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%22%2C%22catID%22%3A%22%20%22%2C%22num_posts%22%3A%222%22%7D

I'm not really sure WHY it behaves that way, but the solution is casting the $userID as an integer. so:

$strm = new Stream(5, 'toc', ' ', (int)$userID, ' ');

The URI encoding changes to:

stream=%7B%22limit%22%3A%225%22%2C%22type%22%3A%22toc%22%2C%22sort%22%3A%22%20%22%2C%22offset %22%3A%220%22%2C%22userID%22%3A%223%22%2C%22catID%22%3A%22%20%22%2C%22num_posts%22%3A%222%22%7D

and json_decode return an array.

2012-04-04 14:12
by Carrie Kendall


3

Reason for JSON_ERROR_CTRL_CHAR

The reason it's returning JSON_ERROR_CTRL_CHAR is not because of character encoding (ie, utf8 or iso). It encounters this error when data has been incorrectly encoded and the resulting string is not valid JSON. If you're mixing single quotes ' and double quotes " it may interfere with the encoding process as well, it is important to be consistent.

With that being said,
It most likely returns the error because you're not sending any actual data in the first place. You're sending an empty string through the request. Well, not really, you're actually sending the string

<?= $strm->encodeJSON();?>

which then becomes

json_encode("<?= $strm->encodeJSON();?>");

in your loadmore.php. But it would be empty anyway if you used the correct php tag <?php ?> because you're not echoing anything.

Change

var stream= '<?= $strm->encodeJSON();?>';

to a proper php tag, and also make sure you actually output something, otherwise stream will just be an empty string.

var stream= '<?php echo $strm->encodeJSON(); ?>';

And it should work properly.

Miscellaneous

encodeURIComponent

Generally, when sending data through AJAX you should encodeURIComponent() it to escape any special characters.

data: "stream="+encodeURIComponent(stream),
$json = json_decode(urldecode($json_str), true);

.live is depracated

The .live function is deprecated as of 1.7, you should now use the function .on to attach event handlers. In your case, you would be better off just using the shorthand function .click() .

links must have href

<a> tags must have a href attribute, otherwise it won't be a link and you might as well be using a <div> if you're not gonna use it. At a minimum you should have href="#" and then add an event.preventDefault().

File names

When you name your files, try to stay away from using periods stream.class.php, they're accidents waiting to happen. Some systems (especially older), won't interpret them properly and you're going to have a hard time renaming all of them. Standard convention suggests using underscore or hyphens for stable file systems.


This works on my end (with all files in the same folder)

HTML default (index.php)

<?php
    include_once("stream.php");
?>

<!DOCTYPE html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
    //load more posts
    $(document).ready(function(){
        $("#active").click(function(e) {
            e.preventDefault();

            var stream = '<?php echo $strm->encodeJSON();?>'; 
            var dataString = stream;
            var request = $.ajax({  
                type: "POST",  
                url: "loadmore.php",  
                data: "stream="+encodeURIComponent(stream),
                dataType: "html",
                beforeSend:function(data){
                    $('.load-more').html('<img src="ajax-loader(1).gif" alt="Loading..." />');
                },
                success: function(html) {
                    $('#active').remove();
                    $('#stream').append(html);
                }
            });

            //ajax error reporting
            request.fail(function(jqXHR, textStatus) {
                $('#active').html(textStatus);
            });
        });
    });
</script>
</head>

<body>
    <a href="#" class='load-more' id='active'>load more posts</a>
    <div id="stream"></div>
</body>
</html>

stream.php

<?php

class Stream {
    private $limit;
    private $type;
    private $sort;
    private $offset=0;
    private $userID;
    private $catID;
    private $content = array();
    private $num_posts;

    function __construct(){
        $a = func_get_args();
        $i = func_num_args();
        if (method_exists($this,$f='__construct'.$i)) {
            call_user_func_array(array($this,$f),$a);
        }
    }

    function __construct5($limit, $type, $sort, $userID, $catID){
        $this->limit = $limit;
        $this->type = $type;
        $this->sort = $sort;
        $this->userID = $userID;
        $this->catID = $catID;
        //$this->num_posts = $this->retrieveTotal();

        //$this->setContent(); 
    }

    function __get($name) {
        if(isset($this->$name)){
            return $this->$name;
        }
    }

        public function encodeJSON(){
        foreach ($this as $key => $value){
            if($key != 'content'){
                $json->$key = $value;
            }
        }
        return json_encode($json);
    }

    public function decodeJSON($json_str){
        $json = json_decode(urldecode($json_str), true);

        foreach ($json as $key => $value){
            $this->$key = $value;
        }
    }
}

//create the object to be encoded
$strm = new Stream(5, 'toc', ' ', 1, ' ');

?>

loadmore.php

<?php

include_once('stream.php');

$strm = new Stream();

$strm->decodeJSON($_POST['stream']);

//Output a private property
echo $strm->__get("type");

?>
2012-04-04 08:33
by ShadowScripter
<?= is just a one liner echo using short tag syntax. the information pertaining to the the problem is irrelevant until 'Miscellaneous.' I added encodeURIComponent and urldecode and the error doesn't change. Also, I updated my question to show a screenshot of the echoed $json_str being passed into decodeJSON. testing it returns valid - Carrie Kendall 2012-04-04 12:39
Hm, didn't know about that! I've never used it before, appreciate the link. Anyway. From my end, I'm not getting any errors using the example I provided. Perhaps you should start isolating your code and work from there - ShadowScripter 2012-04-04 12:56
found the error.. super odd.. userID was being cast as a string and not encoding properly. i will post an answer soon. thanks for digging through my code and trying to help. + - Carrie Kendall 2012-04-04 13:51
@CarrieKendall Eh? Then why didn't it error on my side? : - ShadowScripter 2012-04-04 14:16
because i am an idiot and didn't think to include that in my snippets. it was pretty much undetectable because of the restructuring of the example cod - Carrie Kendall 2012-04-04 14:18
@CarrieKendall Instead of having to put an (int) caster every time, you could just add a default value to it like you did with $offset. Give it a -1 or something, to indicate a default value. I'm assuming that, since it's an ID, it will never go below 0, so -1 is a safe default and will be evaluated as an int, as long as you enter int values - ShadowScripter 2012-04-04 14:24
yeah, i already implemented that. i did that in the answer for better explanation - Carrie Kendall 2012-04-04 14:27
@CarrieKendall Alright then. Guess there's not much more I can help you with. Don't forget to mark yours as answered. Happy coding - ShadowScripter 2012-04-04 14:28
thanks for your help - Carrie Kendall 2012-04-04 14:34


0

JSON only support UTF-8 for strings. So you must encode all strings in UTF-8 before doing json_encode. What seems to happend here (I don't understand all your code) is that you have a string with no-UTF8 that looks to json_encode like "control characters".

2012-04-04 08:21
by Tei
Ads