parsing JSON messages from Web service

Hi, I have donwloaded Unity for evaluation and am trying to build POC to identify any issues.

I have hit my first problem, I am calling a Web URL that returns a response in JSON format. Normally I would expect to use browser JavaScript code similar to the following to process the response:

var JSONFile = "someVar = { 'color' : 'blue' }";  // example of what is received from the server.
eval(JSONFile); // Execute the javascript code contained in JSONFile.

print(someVar.color); // Outputs 'blue'

When I try this in Unity the print statement fails because ‘color’ is not an attribute of someVar.

I have read in the forums that eval is supported in Unity 2 but should not be used?

I tried the following test in Unity to see if a dynamic object would work locally:

var myFirstJSON = { "firstName" : "John",
                    "lastName"  : "Doe",
                    "age"       : 23 };

document.writeln(myFirstJSON.firstName);  // Outputs John
document.writeln(myFirstJSON.lastName);   // Outputs Doe
document.writeln(myFirstJSON.age);        // Outputs 23

When I run this in Unity I get the following error:

error BCE0019: ‘firstName’ is not a member of ‘Boo.Lang.Hash’.

I have seen one other post covering JSON support, the recommendation there was to use C# and .Net to do low level processing.

So I guess my first questions is, should I be able to use eval to parse a JSON payload and return me an object structure that I can work with in Unity JavaScript?

If not then my second question is what is the recommended way to pass data from Web calls into Unity scripts and parse them, is it XML?

On the Web Site it states:

Are there any examples/tutorials covering this aspect of scripting?

Any thoughts appreciated.

Thanks in advance

No…the thing is, Unity’s Javascript isn’t really Javascript, and it doesn’t use dynamic objects like that. Instead you would use classes, like

function Start () {
	var data = new SomeData();
	data.firstName = "John";
	data.lastName = "Doe";
	data.age = 23;
	print (data.firstName);
}

class SomeData {
	var firstName : String;
	var lastName : String;
	var age : int;
}

You can use dynamic typing instead:

class SomeData {
	var firstName;
	var lastName;
	var age;
}

Although that’s slower. You can use eval to construct that code on the fly…it works fine, but the main drawback is that since Unity uses compiled code, any time you eval something you get a pause while it’s compiled at run time.

One way is to use the WWW class:

var someData = new WWW(url);
yield someData;
print (someData.data);

Assuming “url” is a string with an actual URL in it. You can use XML to parse the result if you want; there’s some .NET functions for that I believe, though I’ve never used them.

–Eric

Eric

Hi, thanks for the feedback, you confirmed what I suspected re dynamic objects.

I absolutlely get the benefits for precompiled VS dynamic JavaScript, but in the context of Marshalling/Unmarshalling data over the WEB I think JSON would be a lot less overhead that parsing XML documents.

In another post there was a suggestion to try and import the c# JSON libraries from www.json.org, I think I will give that a go and post the results here.

Andy

After getting to grips of the basics of C# and Boo, I now see that the eval() is parsing the JSON payload being loaded from the Web service, but the payload is treated as a set of key value pairs and added to a Hashtable.

Below is a code snippet that will parse the JSON response from a URL and create a Hastable of key value pairs.

function Start () {
     // Start a download of the given URL (string defined elsewhere)
     var www : WWW = new WWW (url);
    
    // Wait for download to complete
    yield www;
    
    // print JSON response
    print([url]www.data[/url]); 
    
    // parse JSON rsponse into Hashtable
    var myHash: Boo.Lang.Hash = eval([url]www.data[/url]);
    
    print(myHash.Count);

    for (key in myHash.Keys) {
     Debug.Log ("key:"+key+", value: "+ myHash[key]);
    }
}

This is a bit basic, I don’t know how tree structures and arrays in JSON will be handled. So a bit more investigation to be done yet.

hi, what did you end up with?
Did you try to use the json.org code?

It’s not really finished yet but may be of help.

var server			:String="http://192.168.167.193:1111/services/";
var serviceApi		:String="api.service.php";
var serviceLogin	:String="login.service.php";
var session			:String="abd2d043c894014ed2ebbda118f73a52";
var userid 			:Int=1;
private var lastResult:Array=new Array();

function Start () {
	var loginForm = new WWWForm();
	loginForm.AddField("username","admin");
	loginForm.AddField("password","admin");
	var loginRequest = WWW(server + serviceLogin, loginForm);
	yield loginRequest;
	var loginResponse: Boo.Lang.Hash = eval(loginRequest.data);
	readResponse(loginResponse,"");
	
	var serviceForm = new WWWForm();
	serviceForm.AddField("json","{\"auth\":{\"session\":\"" + session + "\",\"userid\":" + userid + "},"
		+ "\"services\":["
		+ "{\"name\":\"auth\",\"param\":{\"method\":\"refresh\"}},"
		+ "{\"name\":\"friendlist\",\"param\":{\"method\":\"group.get\"}},"
		+ "{\"name\":\"friendlist\",\"param\":{\"method\":\"friend.get\",\"groupid\":\"3\"}},"
		+ "{\"name\":\"friendlist\",\"param\":{\"method\":\"friend.search\",\"name\":\"ser\"}},"
		+ "{\"name\":\"friendlist\",\"param\":{\"method\":\"group.new\",\"name\":\"Group By Service 2\"}}"
		+ "]}");
    var serviceRequests = WWW(server + serviceApi, serviceForm);
    yield serviceRequests;   
    var serviceResponse: Boo.Lang.Hash = eval(serviceRequests.data);
    readResponse(serviceResponse,"");
}

function readResponse(response,pathString){
	switch (typeof(response).ToString()){
		case "System.String":
			saveInfo(pathString,response);
			break;
		case "System.Single":
			saveInfo(pathString,response);
			break;
		case "System.Int32":
			saveInfo(pathString,response);
			break;
		case "Boo.Lang.Hash":
			if (pathString.length>0) pathString+=".";
			for (key in response.Keys) {
				value=response[key];
				readResponse(value,pathString+key);
			}	
			break;
		case "Boo.Lang.Hash[]":
			if (pathString.length>0) pathString+=".";
			for (key=0;key<response.length;key++){
				value=response[key];
				readResponse(value,pathString+key);
			}
			break;
		case "System.String[]":
			if (pathString.length>0) pathString+=".";
			for (key=0;key<response.length;key++){
				value=response[key];
				readResponse(value,pathString+key);
			}
			break;
		default:
			Debug.LogWarning("unknow typeof response: "+typeof(response)+ " @ "+pathString,this);
			break;		
	}
}

function saveInfo(key,value){
	if (key=="status.session") session=value;
	Debug.Log ("service key: "+key+", value: "+ value +", typeof: "+typeof(value), this);
}

Using saveInfo() is a mess though. Better using the recursive function readResponse() to return a usable object. Will be working on it next week or so.