3

I have the following two sets of code that need to be decoded.

$test = 
'{
    "username":"sophia",
    "event":{
        "failure":"unreset",
        "answers":{
            "question1":"{\"answer\":\"{\\\"pass\\\":true,\\\"mark\\\":9,\\\"totalmark\\\":9,\\\"value\\\":\\\"get full attention|establish classroom rules|teach key words & concepts|use visual aids|demonstrate|check understanding|introduce point system|give handouts|play\\\"}\",\"state\":\"{\\\"result\\\":{\\\"pass\\\":true,\\\"mark\\\":9,\\\"totalmark\\\":9,\\\"value\\\":\\\"get full attention|establish classroom rules|teach key words & concepts|use visual aids|demonstrate|check understanding|introduce point system|give handouts|play\\\"}}\"}"
        }
    },
     "event_source":"server"
}';

For the first one, I can't decode it at all although it is valid. It seems that the "question1" part is encoded twice and I don't know what's wrong with it.

$test = 
'{
    "username":"lon",
    "event":{
        "saved_response":"{\"parts\": [{\"text\": \"Passion for teaching means loving your job. Doing with all your heart. Teachers who are passionate can inspire pupils to love learning. Passionate teachers create an effective learning environment and increase learning potential of\\nstudents.\"}]}"
    },
    "event_source":"server"
}';

$jarray = json_decode($test, true);
$jevent = json_decode($jarray['event']['saved_response'], true);

For the second one, I can decode it once, but the output of var_dump($jevent) is NULL.

Can anyone kindly explain to me why these two errors occur? I have checked How to deal with backslashes in json strings php and I'm really confused right now. Thanks.

Cassie Liu
  • 195
  • 2
  • 17
  • Actually on [an online PHP decoder](http://php.fnlist.com/php/json_decode) both get decoded successfully, and nested JSON parts are also decoded allright. By the way, if you have any control of the JSON creation, you should prevent JSON data to be re-encoded as part of another JSON – Kaddath Jun 11 '19 at 07:37
  • I used [json_last_error_msg()](https://www.php.net/manual/en/function.json-last-error-msg.php) and got an error: "Control character error, possibly incorrectly encoded". – KIKO Software Jun 11 '19 at 07:39
  • Your `$test` string results in invalid JSON. But if taken as literal text (for instance, read from a file or a network operation), it's valid JSON. Where and how do you receive this string? (The difference is the backslashes: You need to escape them in your `$test` string literal.) – T.J. Crowder Jun 11 '19 at 08:01
  • Try my answer may help you. – stefo91 Jun 11 '19 at 08:04
  • @T.J.Crowder I got this from a tracking log of which records are generated by user events on a website. – Cassie Liu Jun 11 '19 at 08:04
  • @CassieLiu - But by not escaping the backslashes in that string literal, you've changed what the JSON is. – T.J. Crowder Jun 11 '19 at 08:05
  • @T.J.Crowder I copied the JSON directly from the log and didn't change it. I'm confused why it is like this. – Cassie Liu Jun 11 '19 at 08:11
  • 1
    @CassieLiu - Backslashes in a string literal escape the next char. If you take text containing backslashes from a log and paste it between `'` characters, you are **not** creating a string with that text in it. This text: `testing\fun` has a backslash in it. If you paste it into a string literal (`'testing\fun'`), the resulting string does **not** have a backslash in it (it has a formfeed in it because of the escape sequence `\f`). To take literal text and dump it in a string literal, you have to duplicate all the slashes. The text `testing\fun` becomes the string literal `'testing\\fun'`. – T.J. Crowder Jun 11 '19 at 08:14
  • @T.J.Crowder Thanks for your explanation! Does it mean that I have to duplicate all the slashes in `$test`? I'm still a little bit puzzled because I decoded another JSON before. The "event" part is like this. `"event":"{\"type\": \"onCaptionSeek\", \"id\": \"9e91\", \"new_time\": 14.29, \"old_time\": 3.74}",` I directly pasted the JSON between `'` and successfully decoded it twice. Why can it be successful? – Cassie Liu Jun 11 '19 at 08:31
  • 1
    @CassieLiu - If you paste JSON into a string literal, yes, you do; if you read the JSON from a file or network socket, etc., no you don't. The JSON is still odd, but it parses. `question1` is double-encoded, as you noted, but to make things even worse two of the properties within it (`answer` and `state`) are **also** double-encoded (quadruple-encoded in total). – T.J. Crowder Jun 11 '19 at 08:33
  • I don't think chat is useful when the discussion is directly relevant to understanding the question. Toward that end: Is it your goal to have a string literal containing JSON and decode it? Or is it your goal to get this JSON from somewhere already as a string (by reading a file, a network request, a DB call, ...) and parse it? – T.J. Crowder Jun 11 '19 at 08:48
  • @T.J.Crowder My ultimate goal is to get this JSON from somewhere and parse it. I do the copy and paste just for testing my codes. – Cassie Liu Jun 11 '19 at 08:52
  • @CassieLiu - When you do that (the copy and paste), be sure to duplicate the backslashes because otherwise, the data isn't the same. – T.J. Crowder Jun 11 '19 at 09:07

3 Answers3

2

Your $test string in the question isn't an accurate reflection of the data you're trying to parse. You've said you copied and pasted it from a log, but when you paste text including backslashes into a string literal, those backslashes are no longer backslashes, they're escape characters. You have to escape them with another backslash. That is, if you have testing\foo in text, and you drop it into a string literal, you need to duplicate that backslash to have the literal create a string with the same text in it: 'testing\\foo'.

So to accurately reflect the text you've copied from the log, the $test literal should be:

$test = 
'{
    "username":"sophia",
    "event":{
        "failure":"unreset",
        "answers":{
            "question1":"{\\"answer\\":\\"{\\\\\\"pass\\\\\\":true,\\\\\\"mark\\\\\\":9,\\\\\\"totalmark\\\\\\":9,\\\\\\"value\\\\\\":\\\\\\"get full attention|establish classroom rules|teach key words & concepts|use visual aids|demonstrate|check understanding|introduce point system|give handouts|play\\\\\\"}\\",\\"state\\":\\"{\\\\\\"result\\\\\\":{\\\\\\"pass\\\\\\":true,\\\\\\"mark\\\\\\":9,\\\\\\"totalmark\\\\\\":9,\\\\\\"value\\\\\\":\\\\\\"get full attention|establish classroom rules|teach key words & concepts|use visual aids|demonstrate|check understanding|introduce point system|give handouts|play\\\\\\"}}\\"}"
        }
    },
     "event_source":"server"
}';

As you said, question1 is double-encoded. Even worse, when you unencode it, you find it has properties called answer and state that are double-encoded again.

To unencode all of it, you have to first unencode the main bit, then question1, and then its answer and state:

$obj = json_decode($test);
$obj->event->answers->question1 = json_decode($obj->event->answers->question1);
$obj->event->answers->question1->answer = json_decode($obj->event->answers->question1->answer);
$obj->event->answers->question1->state = json_decode($obj->event->answers->question1->state);

If you do that on the text you dumped to the log, it will work. The result (from var_dump) is:

object(stdClass)#1 (3) {
  ["username"]=>
  string(6) "sophia"
  ["event"]=>
  object(stdClass)#2 (2) {
    ["failure"]=>
    string(7) "unreset"
    ["answers"]=>
    object(stdClass)#3 (1) {
      ["question1"]=>
      object(stdClass)#4 (2) {
        ["answer"]=>
        object(stdClass)#5 (4) {
          ["pass"]=>
          bool(true)
          ["mark"]=>
          int(9)
          ["totalmark"]=>
          int(9)
          ["value"]=>
          string(161) "get full attention|establish classroom rules|teach key words & concepts|use visual aids|demonstrate|check understanding|introduce point system|give handouts|play"
        }
        ["state"]=>
        object(stdClass)#6 (1) {
          ["result"]=>
          object(stdClass)#7 (4) {
            ["pass"]=>
            bool(true)
            ["mark"]=>
            int(9)
            ["totalmark"]=>
            int(9)
            ["value"]=>
            string(161) "get full attention|establish classroom rules|teach key words & concepts|use visual aids|demonstrate|check understanding|introduce point system|give handouts|play"
          }
        }
      }
    }
  }
  ["event_source"]=>
  string(6) "server"
}

Live Copy

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
1

You have to remove new lines from the second json.

Try this:

trim(preg_replace('/\s+/', ' ',$jarray['event']['saved_response'])) - Multiple spaces and newlines are replaced with a single space.

$test = 
'{
    "username":"lon",
    "event":{
        "saved_response":"{\"parts\": [{\"text\": \"Passion for teaching means loving your job. Doing with all your heart. Teachers who are passionate can inspire pupils to love learning. Passionate teachers create an effective learning environment and increase learning potential of\\nstudents.\"}]}"
    },
    "event_source":"server"
}';

$jarray = json_decode($test, true);
$jevent = json_decode( trim(preg_replace('/\s+/', ' ',$jarray['event']['saved_response'])), true);
var_dump($jarray);
var_dump($jevent);

https://3v4l.org/fnc1V

As an alternative you can double escape:

$test = 
'{
    "username":"lon",
    "event":{
        "saved_response":"{\\"parts\\": [{\\"text\\": \\"Passion for teaching means loving your job. Doing with all your heart. Teachers who are passionate can inspire pupils to love learning. Passionate teachers create an effective learning environment and increase learning potential of\\\\nstudents.\\"}]}"
    },
    "event_source":"server"
}';

$jarray = json_decode($test, true);
$jevent = json_decode( $jarray['event']['saved_response'], true);
var_dump($jarray);
var_dump($jevent);

https://3v4l.org/9k3t1

How do I handle newlines in JSON?

Andrei Lupuleasa
  • 2,677
  • 3
  • 14
  • 32
0

This will decode your broken json string.

$test = 
'{
    "username":"lon",
    "event":{
        "saved_response":"{\"parts\": [{\"text\": \"Passion for teaching means loving your job. Doing with all your heart. Teachers who are passionate can inspire pupils to love learning. Passionate teachers create an effective learning environment and increase learning potential of\\nstudents.\"}]}"
    },
    "event_source":"server"
}';

var_dump(json_decode(preg_replace('/[\x00-\x1F\x80-\xFF]/', '', json_decode($test, true)['event']['saved_response']), true)['parts'][0]['text']);
stefo91
  • 618
  • 6
  • 16