Monkey-JSON

Monkey Archive Forums/Monkey Projects/Monkey-JSON

muddy_shoes(Posted 2011) [#1]
I've put together the beginnings of a JSON library for my own purposes and thought it may be of use to others so I've uploaded it to here: http://code.google.com/p/monkey-json/

I've done very little testing, so caveat programmator. I'm sure I'll find plenty wrong with it as I use it, but feel free to make suggestions or patches or fork it or whatever.


JaviCervera(Posted 2011) [#2]
Have you tried the JSON library that comes with Monkey samples? I have played a bit with it and it works pretty well. What's the point in making another JSON library?


muddy_shoes(Posted 2011) [#3]
To be honest, no I wasn't aware of the json example. It wouldn't occur to me to dig through an examples folder looking for a fully functional data parsing library that you might expect to be part of the standard library set.

However, plenty of languages have multiple implementations of the same functionality, and that includes JSON processing libs (of which Python has four, Java has over a dozen). Now Monkey has two.


c.k.(Posted 2011) [#4]
Yes, but which implementation is the best? That's my only question. :)


muddy_shoes(Posted 2011) [#5]
I've no idea except to say that mine will ultimately be better for me. It looks like the other one is a port from an older blitz language so it's highly likely to be better tested at this point. I'd use that one if I were you.

I'm not in the business of trying to own the Monkey JSON parsing market, there's even less money in that than the Monkey physics library market.

Edit: And it looks like the example code does something with unicode escape sequences, which I haven't started looking at seeing as the Monkey docs are completely silent on the subject of strings and unicode. If that works then the example is definitely the better option... for a few days, anyway.


Warpy(Posted 2011) [#6]
I would guess that my library (the one in the examples) is not the best.


muddy_shoes(Posted 2011) [#7]
There you go, that's settled then. Neither one is the best. Write your own. :)


Nobuyuki(Posted 2011) [#8]
wooooo spooky thread ressurection, just in time for halloween!

muddy_shoes: I needed a good json library to deserialize my levels from another app I wrote, and yours seems to fit the bill really well since it seems to be really easy to get stuff out of it. However, I'm a little bit lost on getting an array of objects into my data structures.

What's the proper way using your json library to take an array of JSONObjects and iterate through them using a For EachIn loop? I see an ObjectEnumerator in there, but I really don't know how to leverage this. Let me get you an example of what one of my data structures looks like, in JSON:

{
 "Background":"0.png",
 "Balls":[
  {"r":12,"x":240,"y":240,"_mass":1},
  {"r":12,"x":448,"y":240,"_mass":1}],
 "Bumpers":[],
 "Holes":[
  {"r":16,"x":112,"y":416},
  {"r":16,"x":400,"y":416}],
}


and so on, and so forth. Here is some sample code I'm using to parse it in:

	Local test:String = LoadString("maps/2.json")
	Local data:JSONDataItem = JSONData.ReadJSON(test)
	Local jsonObject:JSONObject = JSONObject(data)
	Local b:JSONArray = jsonObject.GetItem("Balls")

	For o:JSONObject = EachIn b	'Almost certainly wrong
		'Do another For EachIn in the inner structure and add to our game's true object list
	Next 


any help you can provide would be appreciated. Thanks again for a nice lib :3


muddy_shoes(Posted 2011) [#9]
Is this helpful?:



I'll revisit the lib at some point and see if the recent changes to Monkey can avoid some of the casting that is currently necessary.


Nobuyuki(Posted 2011) [#10]
I didn't think casting was gonna work, but it looks like it did. Nice work dudester, I'll let you know if I run into any other issues down the road! Thanks a bunch. Great library, btw. It is very OO in a way I can easily grasp.


muddy_shoes(Posted 2012) [#11]
I've updated this.

There are a couple of minor utility additions that were handy for me: Clear() methods on JSONObject and JSONArray and GetItem methods on JSONObject that take a default value to return if the requested item doesn't exist.

The bigger change that might make it worth grabbing for anyone using it in anger is that I've done some optimisation, including cleaning out a load of string additions. It's considerably faster now, especially on Android.


Samah(Posted 2012) [#12]
The bigger change that might make it worth grabbing for anyone using it in anger is that I've done some optimisation, including cleaning out a load of string additions.

Feel free to use/clone the StringBuilder class in Diddy. It uses a dynamic Int array and has a ToString method that uses the new String.FromChars().
It depends on the Arrays utility class, but you could roll out your own.

http://code.google.com/p/diddy/source/browse/trunk/src/diddy/stringbuilder.monkey


muddy_shoes(Posted 2012) [#13]
I just re-used the StringBuilder technique that I used in my LZW compression experiment. It uses an array of strings that are then pulled together with

    "".Join(array)


I'll maybe do some tests with the Int array technique but that would seem to require copying the input strings to the Int array, so I'm not sure it would be faster.

I did borrow/steal the ASCII constants from Diddy to save time though. Thanks!


muddy_shoes(Posted 2012) [#14]
Currently testing this. The results so far aren't particularly conclusive. On every platform (that I can test, so no iOS/WP7/XBox included) the addition operation is faster using a string array, as expected, but that's not a big chunk of time. On every other platform than Android, joining an array of strings is faster. On Android though, it's about 50% slower* for collections of short-ish strings, but the advantage disappears once the strings get to 10 chars or so on average.

The thing is that the other platforms are so fast at doing this stuff that the visible difference is negligible but on Android that 50% can be significant if you're writing large chunks of JSON. I'm going to do some more measuring. It might be a case where it's worth doing something specific for Android.

*That's with a slightly optimised Join method. The standard Monkey version is slower again.


muddy_shoes(Posted 2012) [#15]
Okay, so after some poking around I've got a bit more of a grasp of this. It was a useful exercise as it shook out a couple of improvements that made a big difference.

To work out representative tests I did some measuring of my own usage of JSON for saving game data. Overall, the average string length is 5-ish, so I've used test strings of five chars. In terms of numbers of strings being put together, there were three interesting cases that I did timings on over 1000 runs on my ZTE Blade.

First, there are plenty of instances where only one string is added. This may seem odd, but I imagine it's not that uncommon in generalised systems. As it turns out my StringBuilder was a bit dumb about this case so fixing that ended up with:

JSON SB Num strings: 1
Add- avg(ms): 0.007
Combine- avg(ms): 0.004
Avg Total: 0.011

Diddy SB Num strings: 1
Add- avg(ms): 0.027
Combine- avg(ms): 0.028
Avg Total: 0.055


The second case was the most common concatenation of 16 strings. Examining this pointed out that I was joining the whole string array even if only part of it was populated. Changing that to pass the used slice of the array resulted in:

JSON SB Num strings: 16
Add- avg(ms): 0.094
Combine- avg(ms): 0.174
Avg Total: 0.268

Diddy SB Num strings: 16
Add- avg(ms): 0.235
Combine- avg(ms): 0.154
Avg Total: 0.389


The final case is the current largest concatenation of 6000 or so strings:

JSON SB Num strings: 6000
Add- avg(ms): 18.257
Combine- avg(ms): 63.766
Avg Total: 82.022995

Diddy SB Num strings: 6000
Add- avg(ms): 61.58
Combine- avg(ms): 42.134
Avg Total: 103.714005

On this run I also included a second scenario for the JSONSB which allows for the fact that I can generally know how large the builder array needs to be ahead of time and avoid resizing.

JSON SB Num strings with size predict: 6000
Add- avg(ms): 12.586
Combine- avg(ms): 56.68
Avg Total: 69.266

So, for now it seems as if the StringBuilder I've got is the better choice for my usage although there's not a huge amount in it. I'll have to re-run the tests when I eventually get an iOS device just to check if it's still true there.


c.k.(Posted 2012) [#16]
muddy, i'm trying to import the following JSON:

{
	"Simple Tap":			[ [ [1] ] ],
	"Double Tap":			[ [ [1],[2] ] ],
	"Triple Tap":			[ [ [1],[2],[3] ] ],
	"Quad Tap":			[ [ [1],[2],[3],[4] ] ],
	"Quad Tap Double Back":		[ [ [1],[2],[4],[3] ] ],
	"Single Double":		[ [ [1,3],[2] ] ],
	"Triple Back and Forth":	[ [ [1],[2,4],[3] ] ],
	"Three Drum Intervals": 	[ [ [1,3],[2],[4] ] ],
	"Angled Triple": 		[ [ [1],[2] ], [ [3],[] ] ],
	"Four Square Reverse": 		[ [ [1],[2] ], [ [3],[4] ] ],
	"Four Square Rounder": 		[ [ [2],[1] ], [ [3],[4] ] ]
}


I basically want to name the patterns. How can I get this into a useful array/object?

Currently, I'm just doing this:

[monkeycode]
g.dPatterns = [
[ [ [1] ] ],
[ [ [1],[2] ] ],
[ [ [1],[2],[3] ] ],
[ [ [1],[2],[3],[4] ] ],
[ [ [1],[2],[4],[3] ] ],
[ [ [1,3],[2] ] ],
[ [ [1],[2,4],[3] ] ],
[ [ [1,3],[2],[4] ] ],
[ [ [1],[2] ], [ [3], [] ] ],
[ [ [1],[2] ], [ [3],[4] ] ],
[ [ [2],[1] ], [ [3],[4] ] ] ]
[/monkeycode]

but, as you can see, there are no names associated with the patterns.

I want to be able to call up the pattern by number. As the user scrolls thru the available patterns, 0 to array.Length()-1, I can call the display (something like patterns[x][NAME]) and the actual pattern (patterns[x][PATTERN]).

Or maybe there's a better data object for managing data like this (a dictionary?).

Thanks!


muddy_shoes(Posted 2012) [#17]
The JSON library will let your read that and access the pattern arrays by name. You can also parse it out into a StringMap or an array or whatever else you'd like. I can't really follow what the patterns are meant to be to advise how you should store them though.

Extracting to variable size multi-dimensional arrays like in your example definition that isn't straightforward as you need to count out the elements to size your receiving array.


c.k.(Posted 2012) [#18]
I guess I need to know, once I load it in, how can I iterate through the objects or reference them? I'd like to get them in an array so I can loop through them but also reference them with a current_pattern variable.

Print myarray[x][NAME] ' "Single Tap"
current_pattern = myarray[x][PATTERN]

For t:1 to current_pattern.Length() ' or whatever
...

The pattern is just a series of buttons (drums, in this case), displayed in a grid.

Just saw your example iteration above... will attempt to utilize now... :-)


c.k.(Posted 2012) [#19]
I keep getting nulls, so I'm not using this right. :-/

[monkeycode]
Local levelsJSON:String = LoadString("levels.json")
Local patternData:JSONDataItem = JSONData.ReadJSON(levelsJSON)

Local jsonObject:JSONObject = JSONObject(patternData)
Local b:JSONArray = JSONArray(jsonObject.GetItem("Simple Tap"))

For Local di:JSONDataItem = EachIn b
Local do:JSONObject = JSONObject(di)
Print do.ToString()
End
[/monkeycode]

levels.json contains the JSON object I display in #16 above.


muddy_shoes(Posted 2012) [#20]
You get nulls because you're casting "do" to a JSONObject when the element isn't a JSONObject.


c.k.(Posted 2012) [#21]
It looks like it is. X)

EDIT: Is it an IntArray?!

I just copy/pasted the code from your message #9 above, replacing it with my JSON string, and thought it would, POOF!, "just work" (tm).

Sorry, but... where am I going wrong, exactly?

I have been reading up on StringMap. That looks like a real good way to go! I just don't know how to get my JSON source into a StringMap. No doubt your lib can do it!


muddy_shoes(Posted 2012) [#22]
You need to understand your JSON. A JSON Object is defined by curly brackets and contains key:value pairs where the value can be any JSON type. JSON arrays are defined by square brackets containing comma separated lists of any JSON type. This:

{
	"Simple Tap": [ [ [1] ] ]
}


...defines a JSONObject containing one element named "Simple Tap" where the value is a JSONArray that contains a JSONArray that contains a JSONArray that contains an Integer.


c.k.(Posted 2012) [#23]
Yes, and to my program, that array ([ [ [1] ] ]) represents a "board" with one row, with one button, with a tap number of 1.

+---+
| 1 |
+---+

Basically, a 3D array.

"Three Drum Intervals":	[ [ [1,3],[2],[4] ] ]


That is a board with one row and three buttons (the first of which has sequence order of 1 and 3).

+-----+---+---+
| 1,3 | 2 | 4 |
+-----+---+---+

"Four Square Rounder": [ [ [2],[1] ], [ [3],[4] ] ]


And that board has two rows of various buttons.

+---+---+
| 2 | 1 |
+---+---+
| 3 | 4 |
+---+---+

Obviously, I want to import these pattern configurations into a monkey object so I can iterate through them, grab a board pattern and display it, etc.

It looks like I can just use your JSON object to do this. Does it convert those JSON arrays into monkey arrays? It looks like it does.

When I do this:

[monkeycode]
Local levelsJSON:String = LoadString("levels.json")
Local patternData:JSONDataItem = JSONData.ReadJSON(levelsJSON)

Local jsonObject:JSONObject = JSONObject(patternData)
Local b:JSONArray = JSONArray(jsonObject.GetItem("Simple Tap"))
If b = Null Then
Print "It's null"
Else
For Local di:JSONDataItem = EachIn b
Print di.ToString()
End
EndIf
[/monkeycode]

it prints out the array.

Also, how would I iterate through the "root" JSON object? I tried

[monkeycode]
Local b:JSONArray = JSONArray(jsonObject.GetItem(""))
[/monkeycode]

hoping it would grab all the objects for me, but that didn't seem to work. Is there a "root" object? Do I have to explicitly have all this in an object, say, "patterns?"

Thanks, muddy! Sorry to be such a bother... :-/


muddy_shoes(Posted 2012) [#24]
The library doesn't automatically convert JSON arrays to Monkey arrays because the JSON arrays can contain any type while Monkey arrays are fixed type. It's up to you to parse the JSON into whatever Monkey representation you want.

The root object is what you get back from JSONData.ReadJSON. In your case it's a JSONObject, but it could be a JSONArray.

Here, see if this helps:

[monkeycode]
Import json

Function To1DIntArray:Int[] (jsonArr:JSONArray)
Local out:Int[] = New Int[jsonArr.values.Count()]
Local i:Int = 0
For Local jdi:JSONDataItem = EachIn jsonArr
out[i] = jdi.ToInt()
i += 1
End
Return out
End

Function To2DIntArray:Int[][] (jsonArr:JSONArray)
Local out:Int[][] = New Int[jsonArr.values.Count()][]
Local i:Int = 0
For Local jdi:JSONDataItem = EachIn jsonArr
out[i] = To1DIntArray(JSONArray(jdi))
i += 1
End
Return out
End

Function To3DIntArray:Int[][][] (jsonArr:JSONArray)
Local out:Int[][][] = New Int[jsonArr.values.Count()][][]
Local i:Int = 0
For Local jdi:JSONDataItem = EachIn jsonArr
out[i] = To2DIntArray(JSONArray(jdi))
i += 1
End
Return out
End

Function Main()

Local data:JSONDataItem = JSONData.ReadJSON(test)
Local jsonObject:JSONObject = JSONObject(data)

Local patternMap:StringMap<Int[][][] > = New StringMap<Int[][][] >

For Local patternName:String = EachIn jsonObject.Names
Local da:JSONArray = JSONArray(jsonObject.GetItem(patternName))
patternMap.Set(patternName, To3DIntArray(da))
End

For Local patternName:String = EachIn patternMap.Keys()
Print patternName
End

Local pattern:Int[][][] = patternMap.Get("Angled Triple")
Print pattern[0][0][0]
Print pattern[0][1][0]
Print pattern[1][0][0]

End

Global test:String = "{
~qSimple Tap~q: [ [ [1] ] ],
~qDouble Tap~q: [ [ [1],[2] ] ],
~qTriple Tap~q: [ [ [1],[2],[3] ] ],
~qQuad Tap~q: [ [ [1],[2],[3],[4] ] ],
~qQuad Tap Double Back~q: [ [ [1],[2],[4],[3] ] ],
~qSingle Double~q: [ [ [1,3],[2] ] ],
~qTriple Back and Forth~q: [ [ [1],[2,4],[3] ] ],
~qThree Drum Intervals~q: [ [ [1,3],[2],[4] ] ],
~qAngled Triple~q: [ [ [1],[2] ], [ [3],[] ] ],
~qFour Square Reverse~q: [ [ [1],[2] ], [ [3],[4] ] ],
~qFour Square Rounder~q: [ [ [2],[1] ], [ [3],[4] ] ]
} "
[/monkeycode]


c.k.(Posted 2012) [#25]
muddy, I'm getting an error that says

"Error : Identifier 'Names' cannot be used in this way."

on the line

For Local patternName:String = EachIn jsonObject.Names

I couldn't run your code to test because I don't know how to make a string span multiple lines, and docs don't tell me. In other languages, there are ways to input a raw string like that ("""), but monkey apparently doesn't like it formatted like above.


muddy_shoes(Posted 2012) [#26]
That code is directly pasted from my editor. It works as is. If you're getting "Error : Identifier 'Names' cannot be used in this way." then you've clearly pasted parts of it into some other code that has "Strict" set. That's fine, but you can't expect me to debug your changes at a distance.

The error is just because "Names" is a method. Just add brackets.


c.k.(Posted 2012) [#27]
:D Thanks, muddy. I appreciate your time.


c.k.(Posted 2012) [#28]
I'm loading in the JSON objects just fine now. Thanks, muddy!

Problem: they're coming in alphabetized! WTF?! That's not right.

I couldn't determine if it was the maps.keys basic functionality doing that, or if the JSON mod sorted them. (Searching for sort everywhere found nothing.)

Is there a parameter I can set that says, "Do not alphabetize the names?" I have the objects listed in "level #" order. I could always put another parameter in the JSON, if that's the better way to do it, but I'd rather it not alphabetize unless I specify I need that.

Thanks!


muddy_shoes(Posted 2012) [#29]
If you're referring to the order in which the object element names are enumerated then it's a side-effect of the way that Monkey maps work. You should generally not assume that file order will be preserved when reading in data formats like JSON or XML.


c.k.(Posted 2012) [#30]
OK. That's what I was afraid of. Time for a "level" parameter. :-/


Nobuyuki(Posted 2012) [#31]
hey hey, it's been a while, and I might be using old code, but I just wanted to let you know that stuff exploded on me when I started using bools in my JSON file. The reason has to do with Class JSONBool.ToString() and JSONTokenizer.NextToken(). In JSONBool.ToString is apparently used internally as well as externally, and it changes the case of valid bools in a JSON file. The tokenizer doesn't have a case to check for this change. To fix the problem, these lines must be changed to the following:

tokenizer.monkey, line 141:
[monkeycode] Case "t" , "T"[/monkeycode]

tokenizer.monkey, line 145:
[monkeycode] Case "f" , "F"[/monkeycode]


In addition, method JSONObject.ToJSONString() has a mistake on line 600 -- a Node object doesn't contain the ToString() member, so you need to change where it says v.Key.ToString() to string(v.Key) if you want this to compile. It probably wouldn't have come up if it weren't for the fact it's not used anywhere in the library....


muddy_shoes(Posted 2012) [#32]
That does look like you're using older code. Could you get the latest version and see if that fixes your problem? I don't use bools a lot, but they do exist in my data files and it all seems fine.

ToString is different from ToJSONString where the JSON representation is at odds with Monkey/standard representations. Bools should be written out in lower case to JSON, so if you've still got a situation where that's not happening could you give a code example?


Nobuyuki(Posted 2012) [#33]
Hello,

Both problems manifest when I try this code:

[monkeycode]
oList = JSONArray(jsonObject.GetItem("Magnets"))
if oList <> Null Then
For Local i:JSONDataItem = EachIn oList
' Both errors should manifest on the following line without the fixes
Local x:JSONObject = JSONObject(JSONData.ReadJSON (i.ToJSONString))
Local o:= New Magnet( float(x.GetItem("x")),float(x.GetItem("y")),float(x.GetItem("r")),float(x.GetItem("_energy")))
o.ignoreCueball = bool(x.GetItem("_ignoreCueball"))
Magnets.AddLast(o)
Next
End If
[/monkeycode]

example JSON file is here:

{"Background":"44.png","Balls":[{"r":12,"x":128,"y":224,"_mass":1},{"r":12,"x":488,"y":224,"_mass":1},{"r":12,"x":488,"y":198,"_mass":1},{"r":12,"x":488,"y":172,"_mass":1}],"Bumpers":[],"Fields":[],"Holes":[{"r":16,"x":688,"y":224},{"r":16,"x":192,"y":64},{"r":16,"x":192,"y":400}],"Lines":[{"x":96,"x2":176,"y":224,"y2":32},{"x":704,"x2":592,"y":224,"y2":432},{"x":592,"x2":176,"y":432,"y2":432},{"x":176,"x2":96,"y":432,"y2":224}],"Magnets":[{"r":114,"x":688,"y":224,"_energy":1,"_ignoreCueball":true},{"r":102,"x":192,"y":64,"_energy":-1.5,"_ignoreCueball":false},{"r":97,"x":193,"y":401,"_energy":-1.5,"_ignoreCueball":false}],"Pegs":[]}


The problem appears to be how JSONBools are cast -- before fixing the ToJSONString code, I fed a more convoluted series of casts to ReadJSON() which somewhere along the way probably implicitly relied on JSONBool's ToString method, which return capitalized values (something maybe like encapsulating an abstract JSONDataItem in a string() cast and not caring what kind of subclass it was). Monkey reported it as a downcast error at runtime, but it only tripped on Bools -- all other values returned something ReadJSON didn't choke on.


EDIT: whatever new code you uploaded to the project site today looks vastly different from what code I have for the parser now. There's a big char enum, apparent unicode support, and a lot of other stuff I haven't seen before! I'm not sure I'm ready to integrate it into a live project yet, lol, but I might make a backup and give it a shot and see if anything breaks.....


muddy_shoes(Posted 2012) [#34]
This runs fine with my up-to-date version of the library:



So I can only suggest that you update your module. However, this line:

[monkeycode]Local x:JSONObject = JSONObject(JSONData.ReadJSON (i.ToJSONString))[/monkeycode]

Is unnecessarily recreating and then reprocessing the JSON. You only need to do this:

[monkeycode]Local x:JSONObject = JSONObject(i)[/monkeycode]


c.k.(Posted 2012) [#35]
Damien! Help, please...

I need to convert the value part of the "Game" key into game:StringMap<String>.

[{"Game":{"name":"My Game","secure_word":"","secure_status":"0","user_id":"9999","short_description":"Do the best you can!","description":"This game fun game win good like.","game_type":"arcade","version":"1.0","levels":"11","platform":"HTML5, Android, iOS","play_url":"","website_url":"","created":"2012-08-03 02:38:32","updated":"2012-08-07 22:39:16","players_count":"1","scores_count":"3","locked":"0","status":"1"}}]


I've tried all kinds of iterations of code without success. I keep getting "can't use Names on null" or somesuch.

The brackets in the string make me think I might need to get a JSONArray first, but I'm not really sure. So, now it's time to ask for help. :-)

Thank you!


c.k.(Posted 2012) [#36]
OK, I've got some code that seems to work. Am I close?! :-)

[monkeycode]
' result is the raw string
Local data:JSONDataItem = JSONData.ReadJSON(result)
Local jsonObject:JSONObject = JSONObject(data)
' since it's a JSON object with one k:v set:
Local jobj:JSONObject = JSONObject(jsonObject.GetItem("Game"))

game.Clear()

For Local a:String = EachIn jobj.Names()
Local da:String = jobj.GetItem(a)
game.Set(a, da)
End
[/monkeycode]


c.k.(Posted 2012) [#37]
I figured it out! Score.

Edit: If there's an error somewhere along the way, how do I detect it and figure out what it is?

Thanks!


muddy_shoes(Posted 2012) [#38]
If the JSON is invalid in some way then the library prints some information on what is wrong and where in the string the issue occurred. You seem to be having problems with knowing when you'll get a JSONObject or a JSONArray though and the library can't do much with that.


c.k.(Posted 2012) [#39]
I'm getting this from Scoreoid:

[{"Score":{"id":"3212190","game_id":"747","player_id":"306171","score":"10","created":"2012-08-07 22:50:24","difficulty":"0","platform":"HTML5"}},{"Score":{"id":"3257579","game_id":"747","player_id":"306171","score":"6","created":"2012-08-10 03:59:27","difficulty":"0","platform":"HTML5"}},{"Score":{"id":"3265083","game_id":"747","player_id":"306171","score":"5","created":"2012-08-10 15:38:17","difficulty":"0","platform":"console"}}]


I'm using this to parse it into a scores object:

[monkeycode]
result = "{" + result[2 .. - 1] + "}"
Local data:JSONDataItem = JSONData.ReadJSON(result)
Local jsonObject:JSONObject = JSONObject(data)

scores.Clear()

For Local a:String = EachIn jsonObject.Names()
Print a
Local da:JSONObject = JSONObject(jsonObject.GetItem(a))
Local scoreMap:StringMap<String> = New StringMap<String>
For Local b:String = EachIn da.Names()
Local db:String = da.GetItem(b)
scoreMap.Set(b, db)
Next
scores.Set(a, scoreMap)
Next

[/monkeycode]

But it seems to be grabbing only the first "Score" object. How can I get it to iterate through all score objects?


muddy_shoes(Posted 2012) [#40]
The original JSON is this:



Which is a JSONArray containing several JSONObjects, each of which contains a named element "Score", which is another JSONObject.

You're then messing about with the string and turning it into this:



Which is a single JSONObject containing one "Score" object followed by a bunch of stuff that gets ignored because the root object is complete.

I can only assume that your string hacking is motivated by not understanding something about the difference between JSONObjects and JSONArrays or just not being comfortable with classes/casting in general. Forgive me if that's not the case, but it I'd rather ensure that you understand these things than just provide a code solution. I'll post again in a bit with an attempt at explaining.


c.k.(Posted 2012) [#41]
Yes, I don't know how to iterate through JSON Arrays using your module, so I strip the array brackets and try to use the simple JSON object. This worked for another object I'm receiving, but not this scores object.

So, after stripping the array brackets, I realize it's multiple objects without a root, so I tried at one point to put them in a root object called "Scores":

{ "Scores": json_object_from_hacked_string }

but that didn't work out for me, either, primarily because I didn't know how to iterate through the "score" objects.

I'd rather understand these things as well, so thank you for your help.


muddy_shoes(Posted 2012) [#42]
Right, if you look at the monkey-JSON project page there is some example code. The first few lines are:

[monkeycode]
'Read JSON file'
Local test:String = LoadString("jsontest.json")
Local data:JSONDataItem = JSONData.ReadJSON(test)

'In this case we know that the root is a JSON object'
'If we didn't, the type can be discovered through the'
'dataType Field or through direct type testing.'

Local jsonObject:JSONObject = JSONObject(data)
[/monkeycode]

Note the comment after the ReadJSON line. What it is talking about is that JSON can be put together in multiple ways and parsing it often has an ambiguous resulting type. In this case, the root element could be a JSONArray or a JSONObject. As the library has to handle either eventuality the ReadJSON call doesn't return either of those specific types. Instead it returns a JSONDataItem.

JSONDataItem is the inherited class of every type of JSON data. JSONArray and JSONObject are both descendants of JSONDataItem as are JSONFloat, JSONString and the other "primitive" data types.

So, given that, when you call ReadJSON you need to either know what type you're going to get and cast it to the sub-class appropriately or use the dataType field in the returned object to understand what it is. In general, for games (and in your case too) you should know the expected file construction.

Coming back to your example, the root element is a JSONArray:

[monkeycode]
Local data:JSONDataItem = JSONData.ReadJSON(result)
Local scoresArr:JSONArray = JSONArray(data)
[/monkeycode]

Now you have a JSONArray, you can iterate over it like a normal Monkey array. Again, because the elements in the array could be any JSON type, you'll be dealing with JSONDataItems:

[monkeycode]
For Local di:JSONDataItem = EachIn scoresArr
Print di
End
[/monkeycode]

Result:


If you want to extract the named score elements then you'd need cast to the JSONObject and use GetItem:

[monkeycode]
For Local di:JSONDataItem = EachIn scoresArr
Local jo:JSONObject = JSONObject(di)
Local scoreObject:JSONObject = JSONObject(jo.GetItem("Score"))
Print scoreObject
End
[/monkeycode]

Result:



c.k.(Posted 2012) [#43]
A-ha! No doubt I've got it now. :-)

Thank you for that explanation. Very clear and precise. I think the casting from JSONDataItem to whatever-it-is will help the most.

Also, eagerly anticipating the next pointless diversion.


Nobuyuki(Posted 2012) [#44]
hey muddy, I haven't looked into it yet, but can this lib also write to /construct a JSON string as well? I'm thinking I want to store my state settings in a JSON file, and haven't got around to coming up with a template file for that. I'm kinda hoping I can just hack one in in an ad-hoc way using Monkey-JSON, if it lets you write arbitrarily complex JSONObjects in a simple enough way.

Any examples for doing this on some of the normal compound types? (If Monkey-JSON can do this at all)

Edit: I saw the AddPrim code on the code page. This looks like it could work. I will test direct casting for Bools on this lib later, since I had some casting issues with bools before (something like Null vs. 1 for false and true, I forgot exactly what it was, but it's probably something that I'm not accounting for).


muddy_shoes(Posted 2012) [#45]
Yes, you just create and populate the required JSON dataitem heirarchy and then call ToJSONString or the WriteJSON function to get the JSON string. The example on the project page includes a section that does this:

[monkeycode]
Local root:JSONObject = New JSONObject()
root.AddPrim("name",name)
root.AddPrim("width",width)
root.AddPrim("height",height)
root.AddPrim("isBounded",isBounded)

'JSON objects and arrays can contain objects and arrays'
Local playerJSON:JSONObject = New JSONObject()
playerJSON.AddPrim("posX",player.position.x)
playerJSON.AddPrim("posY",player.position.y)
root.AddItem("player",playerJSON)

'Writing the JSON'
Local jsonString:String = JSONData.WriteJSON(root)
[/monkeycode]


Nobuyuki(Posted 2013) [#46]
EDIT: Please disregard this post. It was probably a coding error on my part which I mistook for a bug in monkey-JSON. Several clean wipes of my state data seems to have fixed whatever was broken in my project's loading.


CopperCircle(Posted 2013) [#47]
Hi, just found this great lib, will Monkey JSON work with keys that are not in quotes, my JavaScript output only puts quotes around string values not keys?

Thanks.


muddy_shoes(Posted 2013) [#48]
JSON specifies that keys are strings and that strings are double-quote delimited:

http://json.org
http://stackoverflow.com/questions/2067974/in-json-why-is-each-name-quoted

I'll see about having quote-less mode, but it's not standard JSON at that point.


CopperCircle(Posted 2013) [#49]
Thanks, as keys are always strings they shouldn't need the quotes, a quote-less mode would be great.


muddy_shoes(Posted 2013) [#50]
I'd recommend you look at generating proper JSON via a stringifier rather than just spitting out the JS object literals. The point of a standard is to be standard after all.


AndroidAndy(Posted 2013) [#51]
I would also recommend sticking with the standard too. I was working on consuming some home-grown JSON from a Java server app the other day and the standard libraries for JSON parsing puked with the keys not quoted. The home-grown JSON had only talked to other "homies" and so never encountered a problem until it went uptown and met with those that had more "refined" JSON taste.


CopperCircle(Posted 2013) [#52]
I don't have control of the javascript lib creating the JSON and it seems it is non-standard, so guess I will have to parse the output or look to change Monkey-JSON.

Thanks.


muddy_shoes(Posted 2013) [#53]
Just out of curiosity what's the name of the library you're using that only generates unquoted string ids?


CopperCircle(Posted 2013) [#54]
It is a custom one that is part of a CMS I have to connect to, I have asked the developer to change it to standard output. Thanks.