[SOLVED] decoding JSON

Pfap

Member
Does anybody have any input on decoding the below JSON into gamemaker maps and lists?
I had the input being received fine until, I added the array under the "avatar" key.

Here is the JSON:
Code:
[{"friend":"e8553","wins":0,"level":0,"name":"srtht","avatar":[0,0,0,0,0,0,0,0,0,0,"srtht",89,1],"losses":0},{"friend":"d21fc","wins":0,"level":0,"name":"sfdgsg","avatar":[0,0,0,0,0,0,0,0,0,0,"sfdgsg",89,1],"losses":0},{"friend":"bb0ca","wins":0,"level":0,"name":"strhr","avatar":[0,0,0,0,0,0,0,0,0,0,"strhr",89,1],"losses":0},{"friend":"b90ac","wins":0,"level":0,"name":"sdfgsdf","avatar":[0,0,0,0,0,0,0,0,0,0,"sdfgsdf",89,1],"losses":0}]
And here is the code and how I think it should work... the manual is rather cryptic to me when it comes to nested lists. If I can't figure out how to parse nested lists I can always flatten it out, but it would be convenient to grab lists.

Code:
var response = ds_map_find_value(async_load, "result");
  show_debug_message(response);
 
 

  var working_data = json_decode(response);
  show_debug_message(working_data);
 
 

 //before adding the internal lists this would return a list with the maps
var list = ds_map_find_first(working_data);
show_debug_message(list);//default


//size was the amount of internal maps or objects
var size = ds_list_size(list);
show_debug_message(size);



//repeat for each object in the JSON
var pos = 0;
repeat(size){
 


 var currentMap = ds_list_find_value(list,list_pos);
 
 
 inst.friend = ds_map_find_value(currentMap,"friend");
 inst.name = ds_map_find_value(currentMap,"name");
 inst.wins = ds_map_find_value(currentMap,"wins");
 inst.levels = ds_map_find_value(currentMap,"level");
 inst.losses = ds_map_find_value(currentMap,"losses");
 
 
 
//I am expecting a list here
 var avatar_list = ds_map_find_value(currentMap,"avatar");


pos += 1;

}

I am not entirely sure what the issue is and the console output is as expected.

Gamemaker console output for show_debug_message(response):
Code:
[{"friend":"e8553","wins":0,"level":0,"name":"srtht","avatar":[0,0,0,0,0,0,0,0,0,0,"srtht",89,1],"losses":0},{"friend":"d21fc","wins":0,"level":0,"name":"sfdgsg","avatar":[0,0,0,0,0,0,0,0,0,0,"sfdgsg",89,1],"losses":0},{"friend":"bb0ca","wins":0,"level":0,"name":"strhr","avatar":[0,0,0,0,0,0,0,0,0,0,"strhr",89,1],"losses":0},{"friend":"b90ac","wins":0,"level":0,"name":"sdfgsdf","avatar":[0,0,0,0,0,0,0,0,0,0,"sdfgsdf",89,1],"losses":0}]
So, I have an array or list with 4 objects or maps.
How can I get at the internal lists under the "avatar" key?



Here is the note from the manual section on json_decode that I am having trouble deciphering:
NOTE: When decoding arrays, there is a map with the key "default" ONLY when an array is the top level structure, and ONLY for that top-level array. Internal lists decode directly to ds_lists without being enclosed in a ds_map.
In my code I am trying to access the list from within the maps, but the above note states that internal lists decode without being enclosed in a map? If so, how can I access them? Or will I just need to restructure how I send out my data?
 

Danei

Member
Code:
var list = ds_map_find_first(working_data);
I don't think this is actually returning a list; it's returning the string "default" because that's the first (and only) key in the outermost map. The function only returns the key, you still need to access the map using that key.

try changing it to
Code:
var list = working_data[? ds_map_find_first(working_data)];
Of course, since you know that the outermost map will only have one key, you can simplify that to

Code:
var list = working_data[? "default"];
That note in the manual just means that the outermost list, i.e. the top level structure in the JSON, gets enclosed in a map under the key "default" (GMS likes to have a map as the top level structure, so this only happens when it's a list), but those internal lists don't get extra maps wrapped around them.

Then, for this part,

Code:
var currentMap = ds_list_find_value(list,list_pos);
I don't see where you're setting list_pos. Did you mean to use pos there?


And finally, inside your repeat loop you're setting avatar_list to the internal list, but then immediately overriding it in the next iteration of the loop, so if you want to keep track of all 4 of them, you'll need to do something else.
 
Last edited:

Pfap

Member
Code:
var list = ds_map_find_first(working_data);
I don't think this is actually returning a list; it's returning the string "default" because that's the first (and only) key in the outermost map. The function only returns the key, you still need to access the map using that key.

try changing it to
Code:
var list = working_data[? ds_map_find_first(working_data)];
Of course, since you know that the outermost map will only have one key, you can simplify that to

Code:
var list = working_data[? "default"];
That note in the manual just means that the outermost list, i.e. the top level structure in the JSON, gets enclosed in a map under the key "default" (GMS likes to have a map as the top level structure, so this only happens when it's a list), but those internal lists don't get extra maps wrapped around them.

Then, for this part,

Code:
var currentMap = ds_list_find_value(list,list_pos);
I don't see where you're setting list_pos. Did you mean to use pos there?


And finally, inside your repeat loop you're setting avatar_list to the internal list, but then immediately overriding it in the next iteration of the loop, so if you want to keep track of all 4 of them, you'll need to do something else.


Thank you. I have some really rough code at the moment and I'm just working on accessing the JSON as native gamemaker. So, some of the issues you have pointed out are just for testing and or bad copying and pasting when formatting my question like with the list_pos typo.

Currently your answer helped me get further, but I seem to only be able to access one of the objects and have not tried accessing a list within an object successfully yet.

Code:
var response = ds_map_find_value(async_load, "result");
  show_debug_message(response);
 
 
  var working_data = json_decode(response);

 
show_debug_message(ds_map_find_first(working_data));//default
var map = working_data[? ds_map_find_first(working_data)];//find the value under the "default" key ds_map_find(working_data, "default");


show_debug_message("JSON below");
show_debug_message(json_encode(map));
The above outputs:

Code:
[{"friend":"e8553","wins":0,"level":0,"name":"srtht","trex":[0,0,0,0,0,0,0,0,0,0,"srtht",89,1],"losses":0},{"friend":"de200","wins":0,"level":0,"name":"serysn","trex":[0,0,0,0,0,0,0,0,0,0,"serysn",89,1],"losses":0},{"friend":"d21fc","wins":0,"level":0,"name":"sfdgsg","trex":[0,0,0,0,0,0,0,0,0,0,"sfdgsg",89,1],"losses":0},{"friend":"c652b","wins":0,"level":0,"name":"sdfsf","trex":[0,0,0,0,0,0,0,0,0,0,"sdfsf",89,1],"losses":0}]
default
JSON below
{ "friend": "d21fc", "wins": 0.000000, "level": 0.000000, "name": "sfdgsg", "trex": [ 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, "sfdgsg", 89.000000, 1.000000 ], "losses": 0.000000 }
This is good, because I have one of the objects; but, how can I get the others?

I tried using:
Code:
var map1 = working_data[? ds_map_find_next(working_data, "default)];// used default instead of ds_map_find_first(working_data);

Does gamemaker just throw out the other objects?
 

chamaeleon

Member
Code:
var json_orig = "[{'friend':'e8553','wins':0,'level':0,'name':'srtht','avatar':[0,0,0,0,0,0,0,0,0,0,'srtht',89,1],'losses':0},{'friend':'d21fc','wins':0,'level':0,'name':'sfdgsg','avatar':[0,0,0,0,0,0,0,0,0,0,'sfdgsg',89,1],'losses':0},{'friend':'bb0ca','wins':0,'level':0,'name':'strhr','avatar':[0,0,0,0,0,0,0,0,0,0,'strhr',89,1],'losses':0},{'friend':'b90ac','wins':0,'level':0,'name':'sdfgsdf','avatar':[0,0,0,0,0,0,0,0,0,0,'sdfgsdf',89,1],'losses':0}]";

var data = json_decode(json_orig);
data = data[? "default"];

for (var i = 0; i < ds_list_size(data); i++)
{
    show_debug_message(json_encode(data[| i]));
}
Code:
{ "friend": "e8553", "wins": 0.000000, "avatar": [ 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, "srtht", 89.000000, 1.000000 ], "level": 0.000000, "name": "srtht", "losses": 0.000000 }
{ "friend": "d21fc", "wins": 0.000000, "avatar": [ 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, "sfdgsg", 89.000000, 1.000000 ], "level": 0.000000, "name": "sfdgsg", "losses": 0.000000 }
{ "friend": "bb0ca", "wins": 0.000000, "avatar": [ 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, "strhr", 89.000000, 1.000000 ], "level": 0.000000, "name": "strhr", "losses": 0.000000 }
{ "friend": "b90ac", "wins": 0.000000, "avatar": [ 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, "sdfgsdf", 89.000000, 1.000000 ], "level": 0.000000, "name": "sdfgsdf", "losses": 0.000000 }
Once you pull out the default entry (which is there because only ds_map can be a top-level container for GMS when it comes to JSON), you can iterate over the ds_list.
 

Danei

Member
Yeah, your issue now is that you're not accounting for the list.
working_data[? "default"] should be pointing to the list containing the 4 maps.
 

Pfap

Member
How do I get at the individual entries though. I think I am confused, because I automatically think things enclosed in {} are maps and things enclosed in [] are lists. I'll think about it some more and I'm probably just being dense, but I played around with the example that @chamaeleon gave and this is what I got.
Code:
var json_orig = "[{'friend':'e8553','wins':0,'level':0,'name':'srtht','avatar':[0,0,0,0,0,0,0,0,0,0,'srtht',89,1],'losses':0},{'friend':'d21fc','wins':0,'level':0,'name':'sfdgsg','avatar':[0,0,0,0,0,0,0,0,0,0,'sfdgsg',89,1],'losses':0},{'friend':'bb0ca','wins':0,'level':0,'name':'strhr','avatar':[0,0,0,0,0,0,0,0,0,0,'strhr',89,1],'losses':0},{'friend':'b90ac','wins':0,'level':0,'name':'sdfgsdf','avatar':[0,0,0,0,0,0,0,0,0,0,'sdfgsdf',89,1],'losses':0}]";
var data = json_decode(json_orig);
data = data[? "default"];
show_debug_message(ds_list_size(data));
for (var i = 0; i < ds_list_size(data); i++)
{
    show_debug_message(json_encode(data[| i]));
}
var list1 = data[| 0];
show_debug_message(json_encode(list1));
show_debug_message(ds_list_find_value(list1,0));
The show_debug_message(ds_list_find_value(list1,0)); returns 0.
 

FrostyCat

Member
How do I get at the individual entries though. I think I am confused, because I automatically think things enclosed in {} are maps and things enclosed in [] are lists. I'll think about it some more and I'm probably just being dense, but I played around with the example that @chamaeleon gave and this is what I got.
Code:
var json_orig = "[{'friend':'e8553','wins':0,'level':0,'name':'srtht','avatar':[0,0,0,0,0,0,0,0,0,0,'srtht',89,1],'losses':0},{'friend':'d21fc','wins':0,'level':0,'name':'sfdgsg','avatar':[0,0,0,0,0,0,0,0,0,0,'sfdgsg',89,1],'losses':0},{'friend':'bb0ca','wins':0,'level':0,'name':'strhr','avatar':[0,0,0,0,0,0,0,0,0,0,'strhr',89,1],'losses':0},{'friend':'b90ac','wins':0,'level':0,'name':'sdfgsdf','avatar':[0,0,0,0,0,0,0,0,0,0,'sdfgsdf',89,1],'losses':0}]";
var data = json_decode(json_orig);
data = data[? "default"];
show_debug_message(ds_list_size(data));
for (var i = 0; i < ds_list_size(data); i++)
{
    show_debug_message(json_encode(data[| i]));
}
var list1 = data[| 0];
show_debug_message(json_encode(list1));
show_debug_message(ds_list_find_value(list1,0));
The show_debug_message(ds_list_find_value(list1,0)); returns 0.
Which should not be a surprise to you. The 0 is a map ID.

First, don't use single quotes to delimit your strings in JSON, it is non-standard behaviour. Some parsers forgive this, but many don't. Non-standard behaviour should always be avoided if you don't want to be fenced in by your own choice.

Second, use a JSON formatter/viewer and start looking at the nested structure layer by layer. It's easy to see that for the vth avatar entry for the nth friend, the path to it is "default" > n > "avatar" > v. You can either do this traditionally:
Code:
var data = json_decode(json_orig),
    current = data[? "default"];
current = current[| 0]; // n = 0
current = current[? "avatar"];
current = current[| 10]; // v = 10, current = "srtht"
ds_map_destroy(data);
Or use the helper script on that post and do it in one go:
Code:
var data = json_decode(json_orig),
    current = json_get(data, 0, "avatar" 10); // current = "srtht"
ds_map_destroy(data);
 

chamaeleon

Member
First, don't use single quotes to delimit your strings in JSON, it is non-standard behaviour. Some parsers forgive this, but many don't. Non-standard behaviour should always be avoided if you don't want to be fenced in by your own choice.
I switched to single quotes for the purpose of putting the json string directly in a string without having to escape every double quote (didn't want to use a file). Don't blame @Pfap for that. Why I didn't just use search and replace to add a backslash instead, I don't know. Mea culpa.
 

Pfap

Member
Which should not be a surprise to you. The 0 is a map ID.

First, don't use single quotes to delimit your strings in JSON, it is non-standard behaviour. Some parsers forgive this, but many don't. Non-standard behaviour should always be avoided if you don't want to be fenced in by your own choice.

Second, use a JSON formatter/viewer and start looking at the nested structure layer by layer. It's easy to see that for the vth avatar entry for the nth friend, the path to it is "default" > n > "avatar" > v. You can either do this traditionally:
Code:
var data = json_decode(json_orig),
    current = data[? "default"];
current = current[| 0]; // n = 0
current = current[? "avatar"];
current = current[| 10]; // v = 10, current = "srtht"
ds_map_destroy(data);
Or use the helper script on that post and do it in one go:
Code:
var data = json_decode(json_orig),
    current = json_get(data, 0, "avatar" 10); // current = "srtht"
ds_map_destroy(data);
Thanks! I'm definitely going to drink that water :)
 

Pfap

Member
I switched to single quotes for the purpose of putting the json string directly in a string without having to escape every double quote (didn't want to use a file). Don't blame @Pfap for that. Why I didn't just use search and replace to add a backslash instead, I don't know. Mea culpa.
I was surprised to see those too, as Gms2 doesn't allow single quotes. I thought it was just some trick with JSON, but I noticed that using json_decode in gamemaker puts them in double quotes anyways. I still feel slightly confused, because Gamemaker handles things differently based on what it is receiving. An array of objects was pretty easy to grab, but an array of objects that have arrays in them doesn't feel quite as intuitive to me personally.
 

chamaeleon

Member
I was surprised to see those too, as Gms2 doesn't allow single quotes. I thought it was just some trick with JSON, but I noticed that using json_decode in gamemaker puts them in double quotes anyways. I still feel slightly confused, because Gamemaker handles things differently based on what it is receiving. An array of objects was pretty easy to grab, but an array of objects that have arrays in them doesn't feel quite as intuitive to me personally.
Given (only two entries to keep the size down)
Code:
[
   {
      "friend":"e8553",
      "wins":0,
      "level":0,
      "name":"srtht",
      "avatar":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "srtht", 89, 1],
      "losses":0
   },
   {
      "friend":"d21fc",
      "wins":0,
      "level":0,
      "name":"sfdgsg",
      "avatar":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "sfdgsg", 89, 1],
      "losses":0
   }
]
Then
Code:
var data = json_decode(json_string);
data = data[? "default"];
will result in data containing the whole structure, and because your JSON is a list (as stored in the parsed result under a ds_map key "default", because of GMS peculiarities), you can then simply use the ds_list functions or the ds_list accessor to get to each value in the list.
Code:
var first_val = data[| 0];
var second_val = data[| 1];
first_val and second_val now contain the ds_map ids of the two ds_map that were stored, and you can then look at their individual content
Code:
show_debug_message(first_val[? "friend"]);
show_debug_message(second_val[? "name"]);
You can then get the ds_list for avatar and do something with that.
Code:
var avatar = first_val[? "avatar"];
for (i = 0; i < ds_list_size(avatar); i++)
{
    show_debug_message(avatar[| i]);
}
All above code not tested, sorry, just typing away..

And of course, with a larger set of items in the top level array, you'd presumably iterate over it rather than picking out two entries like I did using specific numbers (0 and 1) until you either have processed all of them or you find a particular one you want, or whatever.
 

FrostyCat

Member
I was surprised to see those too, as Gms2 doesn't allow single quotes. I thought it was just some trick with JSON, but I noticed that using json_decode in gamemaker puts them in double quotes anyways.
Not entirely true. GMS 2 has a "legacy format string" syntax that will allow single quotes to denote the outermost string, and allow double quotes to be used for strings internal to the JSON structure. Example:
Code:
var json = @'{ "foo": "bar", "baz": [1, 2, "foobar"] }';
 

Pfap

Member
Given (only two entries to keep the size down)
Code:
[
   {
      "friend":"e8553",
      "wins":0,
      "level":0,
      "name":"srtht",
      "avatar":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "srtht", 89, 1],
      "losses":0
   },
   {
      "friend":"d21fc",
      "wins":0,
      "level":0,
      "name":"sfdgsg",
      "avatar":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "sfdgsg", 89, 1],
      "losses":0
   }
]
Then
Code:
var data = json_decode(json_string);
data = data[? "default"];
will result in data containing the whole structure, and because your JSON is a list (as stored in the parsed result under a ds_map key "default", because of GMS peculiarities), you can then simply use the ds_list functions or the ds_list accessor to get to each value in the list.
Code:
var first_val = data[| 0];
var second_val = data[| 1];
first_val and second_val now contain the ds_map ids of the two ds_map that were stored, and you can then look at their individual content
Code:
show_debug_message(first_val[? "friend"]);
show_debug_message(second_val[? "name"]);
You can then get the ds_list for avatar and do something with that.
Code:
var avatar = first_val[? "avatar"];
for (i = 0; i < ds_list_size(avatar); i++)
{
    show_debug_message(avatar[| i]);
}
All above code not tested, sorry, just typing away..

And of course, with a larger set of items in the top level array, you'd presumably iterate over it rather than picking out two entries like I did using specific numbers (0 and 1) until you either have processed all of them or you find a particular one you want, or whatever.

Now that everybody has cleared everything up for me I'm having trouble remembering why I needed to create this post in the first place...
But, my problem was that when the JSON being decoded is an array at the top then "default" is a list and not a key within a map. So, I had code that worked with an array of objects.
After I added the array under the "avatar" key, then "default" became a key to a map and everything "broke".


Edit:
Or else I am crazy and have no clue what was going on previously, but I did have working code with an array of objects...
 

chamaeleon

Member
Now that everybody has cleared everything up for me I'm having trouble remembering why I needed to create this post in the first place...
But, my problem was that when the JSON being decoded is an array at the top then "default" is a list and not a key within a map. So, I had code that worked with an array of objects.
After I added the array under the "avatar" key, then "default" became a key to a map and everything "broke".


Edit:
Or else I am crazy and have no clue what was going on previously, but I did have working code with an array of objects...
When you parse a JSON array, GMS gives you a ds_map instead of a ds_list as one might have expected, with a single key, "default", that holds the value of interest. The value of "default" in your case is a ds_list with the content you want. If you json_decode() a map instead of an array, you do not get a "default" key that holds a ds_map. In other words, you need to know what kind of json structure you're feeding json_decode() (presumably you do, because I can't imagine you'd want to feed random data to it..) to know whether you need to fetch the value of "default" or use the return value of json_decode() as-is. I can't imagine you adding an array under "avatar" to affect this at all.

I can envision a scenario where maybe your first attempt was a single map that parsed fine without a "default" key generated, and then you went on to have multiple map entries within a list, and maybe you added avatar at the same time, and it broke, and it seemed to you that it was because you added the avatar list, when it was really because the top level became a list instead of a map in your input string.
 
Top