JavaScript and Dict

edsonedge's icon

Hi there

I am wondering if anyone can give me a simple example of how I can get data from a dictionary into a Js object to work with it

Lets say a function that takes each key and posts its data to the max "terminal"

Hidde's icon

You can use getkeys() and get(). I attached an example!

keys_from_dict.js
js
edsonedge's icon

Thanks a lot :)

edsonedge's icon

Hi again

I am wondering where the 'get' in: d.get(keys[i]
... is coming from ?

I am trying to find a way to throw the whole value list of the d object out of the 0 output of the max js object , but I only get one set of numbers (the last one, out of 200)

var d = new Dict("funkyDict");

function post_to_max(){
    var keys = d.getkeys();
    for(var i = 0; i < keys.length; i++){
        post(d.get(keys[i]), "\n"); // This will post the value
        outlet(0, d.get(keys[i])); // This will post the value
    }
}

furiousgreencloud's icon

This is all very nice, get and set. But is there any way to make max Dicts into first class objects in javascript.

If you are aware of json use in web/javascript. There is no need for gets and setters, because JSON is the syntax of javascript objects.

so

var me = {"username" : "furiousgreenloud", age : 41"};

means you can use your me.username, DIRECTLY in your javascript, plus all the iteration and the rest of the language is just there!

I would expect a new Dict("me") to return an actual javascript associative array (which is an object), or as least, there to be a new Dict("me").obj() method. Or at very least new Dict.borrow("me") and then new Dict.return("me") methods?!!

much love & respect.

Mattijs's icon
lightspeed.johnny's icon

Hi Furious, did you come across my wiki post?

This is exactly what I want to do, however I still don't understand how to convert a javascript object to a dict.

Well, the jobj_to_dict() method does that, but what I don't understand is how to output the dict object from the js object.

This is what I want to do:

var jsdata = dict_to_jsobj(inputDict);
var myNewDict = jsobj_to_dict(jsdata);

outlet(0,myNewDict);

This obviously doesn't work - because the outlet is actually a jsobj with a reference to the dict object.

So how do I actually send a dict to an output? Or is this not possible? I have created this wonderful new dict object (myNewDict) - how do I now use this in my patcher?

Floating Point's icon

I think it depends whether you are using max 7 or max 6.
I haven't started using max 7 yet, but I believe you can output a dict object from a js object somehow now. In max 6, you can only output a stringified version of the object like so:

function bang()
{
    output=JSON.stringify(myDictObject); //also the args: , null, "\t"
    outlet (0,  output);
}


lightspeed.johnny's icon

I think it depends whether you are using max 7 or max 6.
I haven’t started using max 7 yet, but I believe you can output a dict object from a js object somehow now. In max 6, you can only output a stringified version of the object like so:

Interesting... I haven't tried this yet, but if you output a dict as string, then it behaves as a dict object to other Max objects?

I am using Max 7, and it is not obvious that there is a way to output a dict from js through an outlet, thought from what I have gathered through reading the forum, the Max 7 documentation has not caught up yet to the actual product.

So far, the only way I have been successful at exporting to a dict is creating a "named" dict object in js - where the named dict object is a dict that is also in my patcher. I don't like this way, but it is working.

Mattijs's icon

Hi, I attached a working example.

js-dict-test.zip
zip
Floating Point's icon

thanks mattijs, your example works in max 6 of course; i was thinking of nested dictionaries not working in max 6, but now possibly working max 7....
so the crux of the problem for johnny was formatting the output correctly:

    var newDict = jsobj_to_dict(data);
    outlet(0, "dictionary", newDict.name);

lightspeed.johnny's icon

Here is a project with very simple nested dictionaries - and MATTIJS's example of dict_to_jsobj seems to be able to parse this just fine. I also tried parsing the json by reading in the json file and parsing it using JSON.parse() - which has been part of the javascript library since like 2009 or so...

As outlined in this post here:

The JSON.parse() method works when running inside of Max but not when the project is exported as a Max Application... weird...

This is all with Max 7 on Mac OS 10.10.1

js-parse-test-2.zip
zip
lightspeed.johnny's icon

I think I'm about to give up on Max 7 for a while... The project included ^^^^^^ is now, no longer working for me.

dict_to_jsobj() was working just fine... and now, it can not handle nested dicts... This is so crazy. It was working and then it just stopped.

lightspeed.johnny's icon

I think I might have figured out what is going on... When you use a dict - it seems as though Max saves the contents of this dict to a file based on the name of the dict (and that name is not patch specific). Here is the weird thing. If you close, say patch 1 that uses a dict with a file named dict1 - and open a new patch that also uses a dict named dict1, it will load patch 1's file, even if the parsing of dict1 in this new patch failed.

So it seems, what I had perceived as Max 7 successfully parsing nested dicts, was indeed Max picking up a previously cached version of a dict with the same name, that had been parsed by JSON.parse() method, and not the nested dict_to_jsobj() method.

I guess the good thing is that I am learning how NOT to do things in Max (like use javascript and json) - and just hardcode everything in static tables. Ugh.

Mattijs's icon

Nested dicts work fine, but the problem you are experiencing is that Max (6 or 7) doesn't support arrays of dicts.

Unfortunately this is the only missing link, otherwise dict would be a complete way to interface with javascript objects / JSON.

If you would re-format your json file like so, everything should work:

{
    "colorTable":{
        "0" : {
            "ColorName": "off",
            "Color": "",
            "HexColor": "000000",
            "Red": "0.0",
            "Green": "0.0",
            "Blue": "0.0"
        },
        "1": {
            "ColorName": "light-white",
            "Color": "",
            "HexColor": "FFFFFF",
            "Red": "1.0000",
            "Green": "1.0000",
            "Blue": "1.0000"
        },
        etc...
    }
}
lightspeed.johnny's icon

Nested dicts work fine, but the problem you are experiencing is that Max (6 or 7) doesn’t support arrays of dicts.

That's a fairly significant limitation (IMO), but thanks for the heads up. The weird thing is that if you import the json file with arrays of dicts into a dict and use dict.view to display the file, it displays just fine.

If you would re-format your json file like so, everything should work:

That would work, and so would doing the parsing and flattening of the array of dicts using and external process, like node.js - but that introduces a manual step to the process. The original .json file is not hand-made, it is exported from a spreadsheet.

Mattijs's icon

I agree that it is a big limitation. But I didn't know that dict.view does support arrays now. That bodes well for the future :) I do think that the issue is on Cycling's radar.

Max Patch
Copy patch and select New From Clipboard in Max.

At the moment, getting to the content of the sub-dicts with Max objects doesn't seem to be possible, see my quick attempt (needs to be appended to your example patch):

Not sure what your final goal is, but if it is to output single colors from the colorTable in your example, it should be possible to write a recursive method that converts the JSON-parsed jsobj containing the array into a nested dict that max understands like in my previous post. No need for node.js I'd say?

lightspeed.johnny's icon

JSON-parsed jsobj

It is totally possible with JSON.parse() if that is what you mean. But one problem with that is that for some reason JSON.parse() is not recognized when a patch is exported as an application. It is almost like the js runtime that Max uses when running patches internally in Max is different than the runtime that they use when exporting a patch as an Application. Or maybe it's just a bug.

I think JSON.parse() is the route I am going to take for now, and just add shim for JSON.parse() if it is not detected.

Floating Point's icon

i posted a solution to a similar problem i had sometime earlier-- (sorry, forgot i did otherwise would have directed it to you earlier). you might find it useful to adapt to your situation...

edit: actually upon reading your latest post it's not going to help you; misunderstood your issue

Emmanuel Jourdan's icon

@MATTIJS Max 7 support arrays of dictionary. Check out the arrays tab in dict's helpfile.

Mattijs's icon

It does!!! I can't believe I missed this one! Thanks so much guys, my prayers have been answered!

Ok everyone, please forget everything I said about not using arrays.

@EMMANUEL: do you happen to know if the new gettype and getsize messages have a javascript equivalent? If so, I can adjust the dict_to_jsobj and inverse scripts to support arrays and that should fix @LIGHTSPEED.JOHNNY's problem.

Emmanuel Jourdan's icon

Not currently. I'll add a feature request for them.

Mattijs's icon

Great!

do.while's icon

@MATTIJS
i dont know if that would help , but if u are ok to retrieve "key" in order to check its "legacy" you can ask

(yourKey instandeof Dict)

which will say "true" if it as a dictionary

Jan M's icon

@MATTIJS, sorry there is still no PM here...

After I wrote a small JS-Object-Printer (Max 7 module style) for my own debugging purposes I realized that you posted something similar in the WIKI. This one is aiming a bit more generally to print properties of JS objects (not only JSON objs), it takes case of the JavaScript oddity that some data types can be objects or primitives (but still behaving as object) and therefore will return different values when using typeof.

It also detects Max specific objects (such as Task, Maxobj etc.) and I build in a protection for endless iterations that might occur with some of these objects (i.e. when passing a reference to jsthis or patcher).

I wanted to add/comment this to your original post. Unfortunately only the author seems to be able to edit it...

I refactored your code (plus my printer function) into a Max 7 module called "dictTools" for easier use with require(). Here it is, just just in case you might like to add this to your WIKI post.

Cheers,

Jan

PS: I did not test it but i saw that in the jsobj_to_dict you use typeof. That might create trouble, I believe:

if (!(typeof value === "string" || typeof value === "number")) {
 ...
}

If String, Number or Booleans are created with the constructor (var bla = new String("foobar")) valueof will return Object and bla.constructor.name will be "String"....

dictTools.js
js
Jan M's icon

I just tested it: jsobj_to_dict(jsObj) crashes Max when jsObj contains a non primitive data type.

this should fix it:

// convert Objects to primitive data types, Boolean to int
if(typeof value === "object") {
  switch (value.constructor.name) {
    case "String" :
      value = value.toString();
    break;
    case "Number" :
      value = value.valueOf();
    break;
   case "Boolean" :
      value = (value) ?  1 : 0;
   break;
  }
}
// convert primitive boolean to int    
if(typeof value === "boolean") {
  value = (value) ?  1 : 0;
}
    

if (!(typeof value === "string" || typeof value === "number")) {
 ...
}

Jan M's icon

@DO…WHILE : I observed that (yourKey instandeof Dict) only seems to work for dicts inside dicts but not for arrays of dicts. The array keys in such a case are numeric (as expected) but the related values are not instances of Dict but strangely enough of Number. I'd say it's a bug / incomplete JS implementation...

Maybe someone can proof me wrong ;)

Cheers

Mattijs's icon

Hi Jan, Great work!

It seems weird that you can't edit the wiki page I made. I can edit the pages of others as long as I am logged in.

It would be ideal if you could edit the wiki page yourself as you see fit, but if you can't I can change it to include your modifications in the following days.

Jan M's icon

Hi Mattijs,

indeed I can only edit pages that I created myself... that's not the idea of a WIKI, isn't it :) I'll check with support/Lilli.

lightspeed.johnny's icon

Ok - found something else I can't figure out how to do with the dict object... Arrays of arrays - is this possible using the dict methods?

Here is a simple example:

var myDict = new Dict("myDict");
myDict.parse('{ "points":[]}');
// ok now myDict has a single key called "points" which is an empty array - now lets fill it with some arrays of ints...

// this is how you do it in js
var obj = {};
obj.points = [];

for (var i = 0; i < numpoints; i++)
{
   // assume we get data in vars x,y,rgb
   obj.points.push([x,y,rgb]);
}

// now we have something (obj) that looks like this:
// { "points: [ [0, 0, 0], [1, 1, 1]...] }

// how the heck to you do this with myDict without doing this hack:
var hack = JSON.stringify(obj);
myDict.parse(hack);

The object->JSON.stringify->dict hack wouldn't be that bad, but I still can't get JSON.stringify to work when I export the patch as an application... ugh. Also, I can't imagine that it is super efficient when the number of points gets large - like say 5000.

Martin.Jirsak's icon

Hi all,

Thank you very much for this.

But I have a small issue with it. If I use a numbers as key in the Dict, then it always crashes.

********************************
This crashes:
{
    “CC names” : {
        ”0” : “Bank Select MSB”,
        ”1” : “Modulation”,
        ”2” : “Breath Controller”
    }
}

********************************
This doesn’t crash:
{
    “CC names” : {
        ”CC0” : “Bank Select MSB”,
        ”CC1” : “Modulation”,
        ”CC2” : “Breath Controller”
    }
}
********************************

Do you have any hint or solution for me, please?

Thanks!

Mattijs Kneppers's icon

For the archives, much simpler ways to convert a js object from and to dict (now?) exist:

function dictionary(dictName)
{
    var inputDict = new Dict(dictName)
    var jsObject = JSON.parse(inputDict.stringify())
}
function bang()
{
        var outputDict = new Dict()
        outputDict.parse(JSON.stringify(jsObject))
}
Hans Leeuw's icon

Hi Mattijs,

I tested this and it does not work or maybe I miss something? (MAX 8.10 on Mojave 10.14.6 (Intell))

Max Patch
Copy patch and select New From Clipboard in Max.

With your code in the java-script object.

Martin.Jirsak's icon

Hi @HANS LEEUW,

If you use the JS exactly, how MATTIJS KNEPPERS wrote it, the "jsObject" variable is not defined in the bang function.

You can define it as a global variable, for example like this:

var jsObject = {}

function dictionary(dictName)
{
    var inputDict = new Dict(dictName)
    jsObject = JSON.parse(inputDict.stringify())
    
}

function bang()
{
        var outputDict = new Dict()
        outputDict.parse(JSON.stringify(jsObject))
}


Btw, myself, I handle the dict from the JS only, once I instantiate it in the JS. It's much easier to handle it in the code for me.

Mattijs Kneppers's icon

Hi Hans,

As Martin says, if you want to literally convert a dict to Javascript and back, you need access to the parsed Javascript object in the bang method. This was not clear in my example, sorry.

Also, if you want to output the resulting dict, you also need to send it to an outlet. For the archives, below I included your patch and the adapted script to make it a complete working example.

Max Patch
Copy patch and select New From Clipboard in Max.

save as testDictionary.js:

var jsObject = {}

function dictionary(dictName)
{
    var inputDict = new Dict(dictName)
    jsObject = JSON.parse(inputDict.stringify())
    post(jsObject.aList) // print a member of the object to the Max window
}


function bang()
{
    var outputDict = new Dict()
    outputDict.parse(JSON.stringify(jsObject))
    outlet(0, "dictionary", outputDict.name)
}
Hans Leeuw's icon

Ah, great. Thanks for the quick reactions. My JS skills are rusty. I am looking for ways to transfer some of my FTM work to other devices and JS seems good for some of it. Knowing it is fairly easy to parse dictionaries back and forth was one of the things to investigate before diving further into it.
Thanks for the good work.