Accessing Nested Dictionaries in MIN
I'm trying to figure out the best way to access a subdictionary in Min.
For example I have :
{
"dict" : {
"info" : "something",
"moreinfo" : "somethingelse"
}
}
How do I access the value of "dict" in Min?
I tried a bunch of things and learned more about atom_reference. But I wasn't able to make anything good happen. I can see how the fiddliness of dictionaries under the hood is directly related to how easy they are to use in Max,
Feeling oh so very close...
Thanks for any input.
Here is the heinous solution I came up with tonight.
MIN_FUNCTION {
dict d {args[0]};
dict f((atom)d["dict"]);
dict g{ symbol(true) };
g.copyunique(f);
output.send("dictionary", g.name());
return {};
}
File under "things that seem to work but are so very wrong." I think the right answer is something like:
dict d {args[0]};
symbol dictname = //Somehow get the name of the subdictionary
dict f(dictname);
...
But I can't get to the second atom in d["dict"], which as far as i can tell ought to be the name of the subdictionary... a value like "u1234390."
I tried lots of stuff. In the end, I was hoping this might work:
atoms dictName = atoms(d["dict"]);
for (atom i : dictName) {
cout << i << endl;
} //This does not work?
This works as expected if "dict" is { "array" : [1, 2, 3, 4, 5] }. But, alas, when the value is a dictionary it just prints "dictionary" and stops.
Sending "get dict" into the [dict] object gives me "dictionary u981000325." But it appears the value for the "dict" key is not actually a list of 2 atoms, the second one being the name of the dict.
So I'm really unclear on how subdictionaries are handled in a min::dict.
Hi Joe,
Maybe the following will help? This is a modified version of a recent example Rob R posted. (EDIT: It looks like this might be where you ended up in the second post/comment.)
#include "c74_min.h"
using namespace c74::min;
class min_ex_nesteddicts : public object<min_ex_nesteddicts> {
public:
outlet<> output {this, "(anything) dictionary output"};
min_ex_nesteddicts() {
c74::max::common_symbols_init();
}
message<> test {this, "test", "Post the greeting.", MIN_FUNCTION {
using namespace c74::max;
t_atom result[1];
t_object* jsonreader = (t_object*)object_new(_sym_nobox, _sym_jsonreader);
const char* jsontext = "{\"innerdict\":{\"info\":\"something\",\"other\":\"other stuff\"}}";
// Try reading the jsontext into result
t_max_err err = (t_max_err)object_method(jsonreader, _sym_parse, jsontext, result);
if (!err) {
// Return a generic object pointer from the jsonreader's resulting atom
t_object* ro = (t_object*)atom_getobj(result);
if (ro) {
if (object_classname_compare(ro, _sym_dictionary)) {
// Good, the `ro` object is indeed a dictionary,
// so cast it to a t_dictionary ptr.
t_dictionary* maxdict = (t_dictionary*)ro;
t_symbol *dname = NULL;
dictobj_register(maxdict, &dname);
// Create a min dict object from the "raw" t_dictionary*
dict mindict {maxdict};
c74::min::symbol key {"innerdict"};
// Turn the atom_reference from mindict["innerdict"] into an atom
c74::min::atom subdictatom = c74::min::atom(mindict[key].begin());
// Register a new dictionary that will be the subdict
dict subdict {c74::min::symbol(true)};
// Copy into the registered dict
subdict.copyunique(subdictatom);
// Pass it out of the object's outlet
output.send("dictionary", subdict.name());
}
else {
object_free(ro);
}
}
}
object_free(jsonreader);
return {};
}};
};
MIN_EXTERNAL(min_ex_nesteddicts);
But I can't get to the second atom in d["dict"], which as far as i can tell ought to be the name of the subdictionary... a value like "u1234390."
The way to get the subdictionary name is to use the `.name()` method on a c74::min::dict. You have an example of this in your solution already where you call `g.name()`.
Hi Isabel! Thanks for your response! I need to study your example a little bit more, but the max API code does look similar to my solution of creating a new dict and copying the contents of the inner dictionary into it.
To my naive senses, it feels superfluous to create an additional dictionary to get access to the inner dict. I don't know if this is going to create performance issues, or risk leaving something hanging that should be freed. But if that's the best practice, I'm happy to go with it.
I guess your second post gets to the core of the question: it seems like I can't actually get the name of d["dict"]. d["dict"].name() doesn't work because because d["dict"] is an atom reference not a c74::min::dict.
But perhaps the question is, why would I need to? As long as there aren't any performance concerns with creating an additional dict in order to work with an inner dict it will certainly work for my use case... and any other I can conceive of at the moment.
Thinking in that direction brings up a lot more questions to wonder about. But none of them urgent. Thank for your support!
I thought a little bit more about why this matters. Looking at [dict.unpack] object, Max is definitely capable of accessing the name and contents of an internal dictionary, without creating a new dictionary.
I am curious if there is a way to reproduce this behavior with the SDK. If I am passing dictionaries through my objects at very high speeds it seems like creating and freeing a new dict each time I have to access a subdictionary could cause slowdown.
I'm no expert. So if the consensus is there's no concern here, I'm happy to let it go. But for the moment I'm still curious about how to create the behavior demonstrated in the patch below.
Here is the JSON for my dictionary incase "@embed 1" doesn't work:
{
"innerdict" : {
"firstthing" : 1,
"secondthing" : 2
}
}
hi Joe, a solution for accessing unnamed nested dictionaries is presented in the max-sdk example code for the dict.strip object
basically, ask for the dictionary name, if it doesn't exist then register it with a new unique ID:
if (atomisdictionary(a)) {
t_dictionary *d1 = (t_dictionary *)atom_getobj(a);
t_symbol *d1_name;
d1_name = dictobj_namefromptr(d1);
if (!d1_name)
dictobj_register(d1, &d1_name);
atom_setsym(a, ps_dictionary);
atom_setsym(a+1, d1_name);
outlet_anything(x->outlet_keyvals, x->keys[i], 2, a);
}
So great! Thank you.
Hi everyone–thanks for all the great info in this thread. I'm currently dealing with the same problem (trying to access a nested dict in a min external). Is it possible to use the code Rob posted above in a min external? Thanks again
Yes! It takes a tiny bit of doing, but it works.
Note: As far as I can tell, the changes to cmakelists.txt that Rob mentions there need to be made in the file for the particular object, not the file for the package.
As a follow-up, it should now be possible to register existing dictionaries (without cloning them) natively in min as of commit 2aa76d6af. If you have the min-api as a git submodule of your project, you can update your submodules with git submodule foreach --recursive git fetch
In this small addition, there's now a method for a dictionary called `register_as(const symbol name)` which you can use on a min dictionary to register it under a new name.
For example, if you start with a min dictionary called `mindict` that has a subdict called `"innerdict"`:
// Turn the atom_reference from mindict["innerdict"] into an atom
c74::min::symbol key {"innerdict"};
auto subdictatom = c74::min::atom(mindict[key].begin());
// Create an unregistered subdict from the atom
dict subdict {subdictatom};
// Generate a unique name (you could also name it yourself if you want)
auto sym = c74::min::symbol(true);
subdict.register_as(sym);
Wow, this is excellent! I tried Isabel's code before the solution posted earlier, and it seems to be working for my needs. Thank you Isabel, Rob, and Joe.
I've got one more question, and I hope it's not too off-topic: what's the best way to get the keys / number of keys in a min dict?
In Max, you'd use `dictionary_getentrycount(const t_dictionary * d)`.
In Min, you'd want to do something like getting an atom_reference to the dict and then calling `.size()` on it. Right now it looks straightforward to do that for subdicts since doing `mindict[key]` (or equivalently `mindict.at(key)`) returns an atom_reference. There doesn't look like a way to get the atom_reference from a dictionary directly (unless I've missed it in my quick search ;D).
https://github.com/Cycling74/min-api/blob/2aa76d6af555fa85d7c6aa91c48947c11fb61879/include/c74_min_dictionary.h#L91 https://github.com/Cycling74/min-api/blob/043733c81e0ff0fde0e3fecb6c1730b83f0acf58/include/c74_min_atom.h#L292
I've made a GitHub issue that this that would be nice to directly expose through Min. If there are other dictionary methods from the Max SDK you'd like to see exposed as well, please feel free to comment on the ticket: https://github.com/Cycling74/min-api/issues/183
Thanks for this. I got it by adding to the dict class definition:
long get_entry_count() {
return max::dictionary_getentrycount(m_instance);
}
This example of how the Min and Max APIs relate has been instructive. Of course I'd rather not touch the Min headers, but as m_instance is private, I'm not sure it could be done another way.
Here are some other things I've found difficult:
1) Getting the symbolic keys contained in a dictionary as an array
2) Writing an array to a key using operator[]()