MIN: Problem converting JSON to DICT (linker issue with object_new??)

Joe Kaplan's icon

I copy-pasted some code from the Max API docs into my MIN project and encountered an unexpected error with "object_new".

I used the code example given in the docs under "Creating a Dictionary from JSON" . I pasted it right into the Hello World external from the default build. The only modifications I made was to add the c74::max namespace and the instantiate and define the "jsontext" variable.

Is there something else I need to do to prepare object_new? Thank you for any assistance. This would be a valuable addition to my project.


The code I used:

t_dictionary *d = NULL;
t_max_err err;
t_atom result[1];
t_object *jsonreader = (t_object*)object_new(_sym_nobox, _sym_jsonreader);
// assume we have an argument called 'jsontext' which is a const char* with the JSON
// from which we wish to create a t_dictionary instance
err = (t_max_err)object_method(jsonreader, _sym_parse, jsontext, result);
if (!err) {
t_object *ro = (t_object*)atom_getobj(result);
if (ro) {
if (object_classname_compare(ro, _sym_dictionary))
d = (t_dictionary*)ro;
else
object_free(ro);
}
}
object_free(jsonreader);
// we now have a t_dictionary in d that can be used as we see fit

Joe Kaplan's icon

Can anybody assist with this? My project is at a standstill until I get this sorted. Any input would be much appreciated.

Isabel Kaspriskie's icon

The first issue is that the object_new method is actually a macro from `ext_obex.h` in the max-sdk-base. This will result in invalid code when you try to prepend it with `c74::max::` when the preprocessor expands the macro. You might need to `#undef object_new`?

There might also be namespace clashes with the common symbols you're passing into object_new. They're common symbols (which is basically an optimization so that you don't have to call `gensym()` every time you want to use some common symbol).

At the moment I don't have an immediate answer on how to properly call object_new for what you're aiming for, but this should be possible with some tinkering. I'd have to take a closer look to provide any code snippets.

Joshua Kit Clayton's icon

Hi Joe,

I just wanted to chime in here.

Mixing and matching Max API code and Min code requires a bit of careful understanding of how each of them work. It's typically preferable to rewrite the code using Min based idioms or being careful with direct use of the Max API inside Min code. Now that we've made this easier to do with the Max 8.2 SDK, we realize that it would probably be good for us to establish some tips on common issues that might occur when mixing and matching the Min and Max APIs (a few of which you've discovered here).

We'll try to do this in the near future, however please note that as a small company, our support for third party development is limited. We don't have a dedicated staff to help with these classes of problems, and assume that people entering into the world of coding take on a lot of responsibility to solve their own problems. We do try to help when we can, however, and in the meantime, thank you for your patience.

Kind regards,
Joshua

Joe Kaplan's icon

Thanks to you both! I really appreciate it. This has been a learning project for me, and it has taken me much deeper than I expected. But I've definitely learned a lot and am very excited about the potential results. I'm getting very close to having all my bases covered as far as SDK is concerned!

I was able to get the Max API code to compile as soon as I quarantined it in it's own block to keep it separate from the Min namespace.

So I think I'm now on the path to a viable solution. The last wrinkle here seems to converting a Min dict to a Max t_dictionary* and back.

Writing a JSON parser for Min, and reverse engineering the dictionary API both sound like daunting tasks for a novice programmer. Any tips on how to make this conversion would be a big boost in getting this work across the finish line.

Thanks again for your assistance and kind consideration.
-Joe

Joe Kaplan's icon

Well, I discovered that I can cast a min::dict to a max::t_object* and then cast the max::t_object* to a max::t_dictionary*. A max::t_dictionary* casts back to a min::dict no problem.

sadly, I was mistaken when I said the code had compiled when I quarantined the namespace. (newb error here, but I see what I did wrong). Although the IDE doesn't report problems with that approach, I still get linker errors. Not as close to the finish line as I thought.

I'll look forward to additional docs and caveats when working with Max API code in Min. Meanwhile, I'll study up on the fundamentals of both APIs, and see if I can figure it out. Or get started on a JSON <-> min::dict converter, which might be easier.

Meanwhile, any additional insight into how to execute the example code in MIN would be a big help.

Thanks a ton for everything already! The Max team has been integral in helping me get started here.

-Joe

Rob Ramirez's icon

there seem to be a few issues wrapped up in this post, and as Joshua mentioned mixing min-api with max-sdk code is tricky business.

max-sdk uses common-symbols (e.g. _sym_dictionary, etc) all over the place, but this is not something min exposes or wraps AFAICT. the easiest way to proceed may be to replace any uses of these symbols with c74::max::gensym(). However if you'd like to use these, you must do two things:

  • first include common_syms.c into your project this can be achieved by modifying your projects cmakelists to include the file like so:

set( SOURCE_FILES
${PROJECT_NAME}.cpp
${MAX_SDK_INCLUDES}/common/commonsyms.c
)

  • second you must call c74::max::common_symbols_init(); in your object constructor (or at some other initialization point, prior to when the symbols are referenced).

converting a c74::max::t_dictionary to a min dict is as simple as dict mindict {maxdict};
But you must be very careful about dict ownership when passing around dictionaries, as mentioned in the min dict header documentation.

using object_new also takes some care, and will give different outcomes depending on what namespaces are currently active.

here's code that I was able to compile and run in a min hello-world project (that includes commonsyms as described above) that creates a max dictionary, converts it to a min-dict and prints the contents:

message<> test { this, "test", "Post the greeting.",
 MIN_FUNCTION {
  using namespace c74::max;
  t_dictionary* d = NULL;
  t_max_err       err;
  t_atom          result[1];
  t_object* jsonreader = (t_object*)object_new(_sym_nobox, _sym_jsonreader);
  const char* jsontext = "{\"name\":\"John\", \"age\":30, \"car\":null}";
  err = (t_max_err)object_method(jsonreader, _sym_parse, jsontext, result);
  if (!err) {
   t_object* ro = (t_object*)atom_getobj(result);
   if (ro) {
    if (object_classname_compare(ro, _sym_dictionary)) {
     d = (t_dictionary*)ro;
     dict mindict {d};
     c74::min::symbol key {"name"};
     c74::min::atom value = mindict[key].begin();
     cout << "key: " << key << ". Value: "  << value << endl;
    }
    else
     object_free(ro);
   }
  }
  object_free(jsonreader);
  return {};
 }
};
Joe Kaplan's icon

Thank you for all the input here! I wish I could say this last post got me running.

I'll do more research into common symbols in Max API to understand how to replace them with gensym().   For now, I thought I'd try bringing common symbols into Min for this one task.

Rob, the instructions above worked! I was able to build with commonsyms.c and print the contents of mindict.

I encountered trouble when I tried to send the dictionary out the outlet though. Its giving me an access violation deep in c74_min_outlet.h.

I simply added the line: output.send("dictionary", mindict.name());
immediately after you print to the console

My .cpp file is attached. I created a new message test2, just to confirm I am able to output dictionaries correctly. I am able to use that same syntax to output a different dictionary. I'm not sure what the matter is here.


/// @file
///    @ingroup     minexamples
///    @copyright    Copyright 2018 The Min-DevKit Authors. All rights reserved.
///    @license    Use of this source code is governed by the MIT License found in the License.md file.

#include "c74_min.h"

using namespace c74::min;


class hello_world : public object<hello_world> {
public:
MIN_DESCRIPTION    {"Post to the Max Console."};
MIN_TAGS        {"utilities"};
MIN_AUTHOR        {"Cycling '74"};
MIN_RELATED        {"print, jit.print, dict.print"};

inlet<> input    { this, "(bang) post greeting to the max console" };
    outlet<> output {this, "(anything) output the message which is posted to the max console"};

hello_world() {
        c74::max::common_symbols_init();
}


message<> test {this, "test", "Post the greeting.", MIN_FUNCTION {using namespace c74::max;
t_dictionary* d = NULL;
    t_max_err err;
    t_atom result[1];
    t_object* jsonreader = (t_object*)object_new(_sym_nobox, _sym_jsonreader);
    const char* jsontext = "{\"name\":\"John\", \"age\":30, \"car\":null}";
    err = (t_max_err)object_method(jsonreader, _sym_parse, jsontext, result);
    if (!err) {
        t_object* ro = (t_object*)atom_getobj(result);
        if (ro) {
            if (object_classname_compare(ro, _sym_dictionary)) {
                d = (t_dictionary*)ro;
                dict mindict {d};
                c74::min::symbol key {"name"};
                c74::min::atom value = mindict[key].begin();
                cout << "key: " << key << ". Value: " << value << endl;
output.send("dictionary", mindict.name()); //Problem occurs here.
            }
            else
                object_free(ro);
        }
    }
    object_free(jsonreader);
    return {};
}
};

message<> test2 {this, "test2", "Post the greeting.", MIN_FUNCTION {using namespace c74::max;
dict d {c74::min::symbol(true)};
     d["name"] = "jimbob";
     output.send("dictionary", d.name());

return {};
        }
        };



};


MIN_EXTERNAL(hello_world);

min.hello-world.cpp
text/plain 1.95 KB

Joe Kaplan's icon

It looks like the problem here is that mindict.name() is returning a null value.

I tried adding a function to c74_min_dictionary to rename the dict with a value I specify using:
m_instance = max::dictobj_register(d, &s);

But I can't find a way to do that that doesn't clear the contents of mindict in the process.

Rob Ramirez's icon

the following works for me and is able to send the dictionary out for viewing in dict.view. This is just for demonstration and a real solution would need to manage the objects, or free them after output:
https://gist.github.com/robtherich/19b12d27f5a31cd5e1e6af6de34fa65e

Joe Kaplan's icon

Thanks Rob!

I was just about to post my own solution, which was to initialize another min dictionary with {c74::min::symbol(true)}, and then use copy unique() to move the contents of mindict into the new dictionary.

Registering the t_dictionary properly in the first place is even better.

Deep appreciation to C74 for this. Thank you.

Venetian's icon

When receiving a dictionary via the

message<> dictionary{this, "dictionary", "Dictionary is received",
MIN_FUNCTION{try {dict noteDictionary = {args[0]}; ... etc

How do you read `noteDictionary` into the json reader? In the examples above, the dictionary is constructed as const char* jsontext = "{\"name\":\"John\", \"age\":30, \"car\":null}";

If using the `message<> dictionary{this, "dictionary",` function I wanted to try and read the object/atoms?

Joe Kaplan's icon

Hi Venetian,

I'm not certain I'm following your question, but this is how I was able to take a dictionary into an inlet and convert it to JSON.

Toward the end of the Min Documentation , there is a section that covers the fundamentals of dealing with dictionaries.

Inside your MIN FUNCTION, you can use dict d(args[0]);
to take the incoming dictionary and write it to a variable named "d."

Then feed "d" into the proper JSON reading function per the Max API docs. Below is how I did it.

const char* convertDICTtoJSON(dict d){
using namespace c74::max;
t_object* object = d;
t_dictionary* maxdict = (t_dictionary*)object;
t_object* jsonwriter = (t_object*)object_new(_sym_nobox, _sym_jsonwriter);
t_handle json;
const char* str;
object_method(jsonwriter, _sym_writedictionary, maxdict);
object_method(jsonwriter, _sym_getoutput, &json);
str = *json;
// now str contains our JSON serialization of the t_dictionary d
object_free(jsonwriter);
return str;
}


In order to use these functions you have to make the additions to cmakelists that Rob recommends above. You have to do this for cmakelists for the individual object, not for the package. Don't forgot to rebuild your package with the appropriate cmake commands afterward.

Hope that helps.