[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.