How to deal with JSON that is encoded twice?

Viewed 955

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.

3 Answers

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

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?

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']);
Related