Having converted recipes to JSON, I have to say that I’m not impressed with the API that picojson gives us. I find it awkward to use. So, I’d like to propose a wrapper around picojson, intended to be much easier to use (and provide much more condensed code).
In terms of reading via picojson, my two main gripes are that it’s awkward to iterate over lists (I think this is partly a gripe about STL iterators), and it’s really awkward to safely read a simple value (7+ non-bracket lines of not-exactly-short code for something that could be handled in 1 line).
First, an example of how I’m currently reading simple values, like ints:
if (iter->contains("difficulty"))
{
if (iter->get("difficulty").is<double>())
{
difficulty = static_cast<int>(iter->get("difficulty").get<double>());
}
else
{
debugmsg("Invalid recipe: non-numeric difficulty");
continue;
}
}
else
{
debugmsg("Invalid recipe: no difficulty");
continue;
}
Next, an example of how I’m iterating over lists:
if (comp_iter->is<picojson::array>())
{
++cl;
for (picojson::array::const_iterator inner_iter = comp_iter->get<picojson::array>().begin();
inner_iter != comp_iter->get<picojson::array>().end();
++inner_iter)
{
// more stuff was in here
}
}
else
{
debugmsg("Invalid component for recipe: not an array");
continue;
}
I do not like having to write this boilerplate code all the time.
So, I propose we introduce a wrapper class, which I’ll call “catajson” for the time being. The goal of catajson is to 1-line as many operations as possible, and make statements not excessively long. The programmer can instantiate a root-level catajson instance by calling catajson::open(game *g, std::string filename). The game pointer is there so that we have access to debugmsg.
I’m also going to assume that data file integrity is the responsibility of the modder. That simplifies error handling tremendously, and actually makes it possible to cut down to 1 line in some cases (whereas otherwise, we’d need 4+ to handle the error).
Here’s how I would like to write code which fetches a single value, assuming that “json_recipe” is of type catajson:
int difficulty = json_recipe.get("difficulty").int();
And then, behind the scenes, catajson would do all the sanity checks, the conversions, etc., and if any errors are encountered, it would spit out a message such as ‘JSON error at data/raw/recipes.json[“recipes”][42][“difficulty”]: value is not an integer’.
Existence checking would still be possible
And here’s how I would like to write code which loops over a JSON list:
catajson comp_list = json_recipe.get("components");
for (comp_list.set_begin(); comp_list.has_more(); comp_list.next())
{
// do stuff
}
Personally, I would find this API for JSON reading to be much more tolerable. I’d like to make sure that the API to write JSON is about as easy to use, which would probably be possible if we made sure that there are OO ways to turn a given object into a catajson representation.
Having that in-between layer might also make it easier to swap to a different serialization method, as long as there are ways to do lists, string-value pairs, and basic values.
If there are no strong objections, I’ll probably implement this next time I write code that interfaces with data files. It would be nice if TDW, Kevin and/or GlyphGryph approve of the proposal, sure, but mostly I just want to not be annoyed at the data interface :P. And maybe cut down game::init_recipes() to 100 lines or fewer, if I can.