Notifications
Clear all

[Closed] JSON and maxscript?

I’ve condensed what I’ve learned into on giant chunk of heavily commented code, below.

The basic idea is that we have MXS wrapping dotNet objects, these objects hold and manipulate Json data.

Whats really going on is MXS wraps dotNetObject wrapping LINQ objects Wrapping Json data -but thinking about it to hard complicates usage.

All we care about are 4 basic dotNet objects : JObject, JArray, Jproperty and JValue, and the properties and methods for their use. Briefly:

[I]Newtonsoft.Json.Linq.JObject[/I] contains {< anything >} as its [I].value[/I] property, plus has methods and properties for manipulation.

[I]Newtonsoft.Json.Linq.JArray[/I] contains[ any array] as its [I].value[/I] property, plus has methods and properties for manipulation.

[I]...JProperty[/I] contains a single  "<Key>":<value> pair, plus has methods and properties for manipulation."<key>" is the [I].name [/I]property, <value> is  its [I].value.value [/I] property 
(that's not a typo, it is .value.vlaue...see the code comments)

[I]...JValue[/I] contains a single json value < string, number, boolean, JOBject or JArray), as its [I].value[/I] property plus has methods and properties for manipulation.

they are described breifly in the comments below, and with technical thouroughness in the JSON.NET documentation
http://james.newtonking.com/projects/json/help/

/* 
First, some max script to read json files as strings.
*/

testFilePath="C:/path/to/my/jsonData.json"
/*
note the slash " / " instead of windows back slash.
this is just personal choice, to avoid using the double back slash 
necessary to correctly escape the path delimiters
"C:\\path\	o\\my\\jsonData.json" is equally valid.
*/

fn getJsonFileAsString filePath=(
	local  jsonString=""
	fs=openFile filePath
	while not eof fs do(
		jsonString += readchar fs
	)
	close fs
	return jsonString
)

jsonString= getJsonFileAsString testFilePath

/*
Now we're ready for some action.
download the JSON.NET library from  http://json.codeplex.com/ 
and copy 
	..\Json45r11\Bin\Net40\Newtonsoft.Json.dll
	..\Json45r11\Bin\Net40Newtonsoft.Json.xml (not sure if this is necessary)
into 
	C:\Program Files\Autodesk\3ds Max 2013\
this will put the library where Max can see it.
*/

--use dotNet.loadAssembly to load the Newtonsoft.Json library:

json = dotNet.loadAssembly  "Newtonsoft.Json.dll"

--#> dotNetObject:System.Reflection.RuntimeAssembly

/*

a look at  http://json.codeplex.com/documentation  reveals that most of the functions 
are for loading JSON into a pre-defined C# object 
whose keys and structure match *exactly* the JSON you are reading.
This is not a very flexible approach. 

We want a dotNetObject that we can fully query and manipulate.
JSON.NET utilizes LINQ objects for this. 

the class Newtonsoft.Json.Linq.JObject wraps json data in a LINQ object.

But using MaxScript wrapping DotNet Wrapping LINQ Wrapping JSON gets confusing fast!
It's challenging to excavate the acutal JSON data form all these wrappers.
Lets see if we can dig out useful json data...

*/

--first, instantiate a dotNetObject using the class Newtonsoft.Json.Linq.JObject. This creates an empty JObject.
myJObject=dotNetObject "Newtonsoft.Json.Linq.JObject"
--#> dotNetObject:Newtonsoft.Json.Linq.JObject

--next, use the Newtonsoft.Json.Linq.JObject.parse method to read our json string into the JObject we just made
myJObject=myJObject.parse jsonString
--#> dotNetObject:Newtonsoft.Json.Linq.JObject
--the method myJObject.parse has populated our JObject with the data in the string.

--get the first json object in myJObject
myFirstItem=myJObject.First
--#> dotNetObject:Newtonsoft.Json.Linq.JValue

--get the value of myFirstItem
myFirstItem.Value
--#>dotNetObject:Newtonsoft.Json.Linq.JValue

/*
hey, that's not a json value!
It's a dotNetObject of the Class JValue. It holds a JSON value, 
along with some useful properties and methods for manipulating and searching json data.
*/

myFirstItem.Value.Value
--#> "something" 

/*
sweet! first object's value is revelaed to max script. how about the key?
the creator of JSON.NET himself posted this C# function to generate a list of keys:
	IList<string> keys = parent.Properties().Select(p => p.Name).ToList();
It won't work in Max script, of course. But it implies that keys are the .Name property of myFirstItem.value's .Parent property:
*/

myFirstItem.Value.Parent.Name
--#> "key"
-- same as :
myFirstItem.Name
--#> "key"
/*
cool, we can read the first ojects name and key.
what if we want to refer to the key and get the value?
*/
myJObject.item["key"].value

/*
THE JSON.NET OBJECT ZOO

so we have a small zoo of dotNetObjects to hold and manipulate json data.
The four basic types 

JObject : JSON.NET class; a dotNetObject that represents a json object -anything between {} . Not actual JSON data, 
	but a container with its own properties and methods.

JArray : JSON.NET class; a dotNetObject that represents a json Array -anythign between []. Specifically, an array of 
	Jobjects, JArrays, JProperties, Jvalues.

JProperty: JSON.NET class; a dotNetObject that represents a Key:Value Pair.

JValue: JSON.NET class; a dotNetObject container for a value in JSON (string, integer, date, etc.) 
	*Not actual JSON data*, but a container with its own props and methods.
	MOST CONFUSINGLY: JValue.value might retrun a simple  value (string, number, boolean)  OR any of the above objects.

lets see what these dotNetObjects can do for us...
*/

myJObject=myJObject.parse jsonString
--#> dotNetObject:Newtonsoft.Json.Linq.JObject 
--returns a JObject : holds json data

myFirstItem=myJObject.First 
--#> dotNetObject:Newtonsoft.Json.Linq.JProperty
--returns a JProperty : a key:value pair

myFirstItem.name
--#>"key"
--the key of the JProperty

myFirstItem.value --JValue object.
--#>dotNetObject:Newtonsoft.Json.Linq.JValue
--returns a JValue : the value portion of a key value pair.
--note this is not the actual value, but a dotNetObject for holding and manipulating the value

--reading a JSON value:
myFirstItem.value.value 
--#>"myValue"
--JValue.value property; retruns the actual Json VALUE

--writing a JSON value:
myFirstItem.value.value = "newValue"
--#>"newValue"
--we set a new value for "key" 


/*
Json arrays...
*/

myJArray=myJObject.Item["myJsonArray"]
--#>dotNetObject:Newtonsoft.Json.Linq.JArray
myJArray.item(0)
--#>dotNetObject:Newtonsoft.Json.Linq.JObject

/*
so myJArray.item[0] appears to contain a Json object
JArray.item() access is a dotnet function / method, using zero indexing,
so the usual mxs Array access  with brackets and 1 indexing - "myArray[1]"-  won't work.
the fact that we have to use () to access JArray elements creates some scoping issues in mxs.
lets take a look:
*/

--make a simple json array key:value pair, wrapped in a json object as a string
myJarray_string= "{myArray:[\"foo\",\"bar\", \"baz\"]}"
--#> "{myArray:["foo","bar", "baz"]}"

--instantiate a JObject, parse the string into the JObject
myJarray = dotNetObject "Newtonsoft.Json.Linq.JObject"
--#>dotNetObject:Newtonsoft.Json.Linq.JObject
myJarray = myJarray.Parse myJarray_string
--#>dotNetObject:Newtonsoft.Json.Linq.JObject


myJarray.item["myArray"]
--#>dotNetObject:Newtonsoft.Json.Linq.JArray 
--the value of key "myArray" is a JArray object

myJarray.item["myArray"].item(0)
--#>dotNetObject:Newtonsoft.Json.Linq.JValue
-- myArray[1] is a JValue object, so we * should * be able to use .value to see the value...

myJarray.item["myArray"].item(0).value
--#>-- Error occurred in anonymous codeblock; filename: C:\Program Files\Autodesk\3ds Max 2013\scripts\; position: 633; line: 19
--#>-- Unknown property: "value" in 0
/*
uh oh, somethign hinky is going on. mxs is looking for "0.value" instead of "item(0).value".
it seems the () cause a scoping problem. 
since <myJarray.item["myArray"].item(0) > really represents a single variable
we can fix this by wrapping the whole thing in another set of ():
*/
(myJarray.item["myArray"].item(0)).value
--#>"foo"

--although it maight be neater to just sue a variable
item1=myJarray.item["myArray"].item(0)
item1.value


/*
exposing more JSON.NET properties:
below is a function to expose the values of properties of JSON.NET JObjects and JObject subclasses
to use: 
	1.) instantiate a JObject
		myJObject=dotNetObject
		--#>"Newtonsoft.Json.Linq.JObject"
	2.) populate it by parseing  a JSON string
		myJObject=o.parse < a Json String >
	3.) pass *as a string*  when calling the function
		exposeProps "myJObject" 
		(NOT: exposeProps myJObject)
*/

fn exposeProps objString =(

	obj=execute(objString)--evaluate string as actual object
	for propName in (getPropNames obj) do (
		p=propName as string
		format "% % : %
" objString p (execute(objString+"."+p))
	)
)


/* 
recursing unknown JSON
the above is all well and good if we know the JSON data we want to mess with.
but what if the JSON data is variable, or unkown?
below is a fucntion to recurse an unknown json object and pretty print its contents.
*/

--tab fucntions to format output
tab=""
fn tabIn=(tab += "	")
fn tabOut=(	try(tab = replace  tab  1 1 "")catch())

--recursing unkown JSON
fn recurseJData jdata=(
	tabIn()
	--format "<%>" data
	local data=jdata
	local typeIndex=data.type.value__
	local dType=JTokenTypes[typeIndex+1]

	case data.hasValues of (
		true:(
			--data contains values
			--action: deteemine if JProperty, object or array

				case data.type.value__ of (
					
					4:(--data is type JProperty ( a key:value pair )
						--action: print key, recurse value
						--you could begin storing the keys in a Maxscript array or whatever
	
						local key=data.name
						local val=data.value
						format "
%%:"tab key
						recurseJData val
					
					)
					
					1: (
						--Data is a Json Object
						--tabIn()
						format "
%{" tab
						local oc=0
						local objData=data.first
						for oc=1 to data.count do(
							if objData == undefined do(format "  ERROR: obj:  %" objdata; continue)
							recurseJData(objData)
							--format "%NEXT:%" tab childData.next
							objData=objData.next
							
						)
						oc=0
						format "
%}" tab
						--tabOut()
					)
					
					2: (--Data is a Json Array
						--tabIn()
						format "
%[" tab
						local jArray=data					
						for i=1 to jArray.count do(
							
							element=jArray.item(i-1)
							--if element == undefined do(format " !!!%" elementData; continue)
							recurseJData(element)
							
						)
						--tabOut()
						format "
%]" tab
						
						
					)										
				)
			
		)
		
		false:(
			--data IS a value
			--action: print value
			case data.type.value__ of (
				6:(--integer
					format " %"data.value
					)
				7:(--float
					format " %"data.value
					)
				8:(--string
					format "\"%\""data.value 
					)
				9:(--boolean
					format " %"data.value
					)
				default: (
					format "% WARNING: unexpected JTokenType (%-%) in {%}" tab typeIndex dType currentItem
				)	
				
			)
			
		)
	)
	tabOut()
)


/*

and finaly, once we are done with or JSON object and wish to write it to a file,
we can use the JObject.toString() method to gnerate a string fo json
and then use mxs format <string> to: <stream> to write the json file
*/
fn writeJsonObjectToFile jsonObject filePath =(
	format " SAVE: %
" filePath 
	local jsonString=jsonObject.toString()
	print jsonString--just to verify in listener
	--open filepath as stream, use a writable mode
	fs= openFile filePath mode:"wt" --open specified json file
	format "%" jsonString to:fs--write string to the file
	close fs
	format " SAVED: %
" filePath --save
	)

Some of this is doubtless sloppy code, but I did get it working for my purposes.

What I’s really love to make is some kind of dynamic, recursive native mxs object for json data, so lines like :
myJObject.item[“key”].value.value=“foo” could be replaced by a simpler
myJObject.key=“foo”

structs , which I’m admittedly not too farmiliar with, seem too static and non recursive form the examples…

[update 3.20.2013] found some scoping issues with JArray syntax, updated code and comments.

Page 2 / 2