Allegro.cc - Online Community

Allegro.cc Forums » Overly complicated » Reply

 Forums: Overly complicated: Reply
This section is only available to registered Allegro.cc members.
 Thread Review
DanielH:

Just looking for advice on what to do.

Years ago, I wrote a library with an XML parser that would take a file and create the application as it was parsed. It would create the display, event queue, timers, etc. I wanted something I could change the conditions without recompiling. It worked very well.

I got the bug again and wanted to continue where I left off. However, with all the changes to C++ since, it doesn't work anymore. I could revert to an older C++, but instead I started over.

I didn't want to rewrite the xml parser again so I was looking for alternatives. XML, YAML, JSON, ???

I went with JSON and found a light-weight parser. Very light-weight.

I already reported a bug with parsing escape characters and I have no way of checking if an object is an array or a set of objects themselves. Even an extra comma in the wrong place crashes the parser with no indication where the issue lies.

Otherwise, the parser is great. I did modify it some. I put it all in its own namespace and removed the RSJ prefix.

What to do? continue, find a different parser, attempt to modify his parser?

I could try to add some functions like is_array or is_object. But his code is a bit messy. I hate messy code.

Edgar Reynaldo:

You could try tinyXML.

Dizzy Egg:

For Json I usually use quicktype purely because if you can pump the Json into the left hand window and then have all the needed code generated for you (or just the parser); although I only use it with C# which is a lot more straightforward (IMHO) than implementing it in C++.

Mark Oates:

I use (and even donate to) https://github.com/nlohmann/json for JSON. It's header-only, no library, no subproject, no dependencies, no complex build system. The class is written in vanilla C++11. It's insanely well-tested, and is full of features. It's one of my favorite libraries on the internet as an example of what great software design looks like in terms of expected features, error messages, simplicity, test coverage, etc.

To convert or parse your custom object to/from JSON, you would write to_json and from_json functions. Note if the object you are parsing is in a namespace, these functions you create must also be in that namespace.

Here's an example going to and from an ALLEGRO_COLOR:

#SelectExpand
1 2#include <lib/nlohmann/json.hpp> 3#include <allegro5/allegro.h> 4 5 6void to_json(nlohmann::json& j, const ALLEGRO_COLOR& color) 7{ 8 j = nlohmann::json{ 9 {"r", color.r}, 10 {"g", color.g}, 11 {"b", color.b}, 12 {"a", color.a}, 13 }; 14} 15 16 17void from_json(const nlohmann::json& j, ALLEGRO_COLOR& color) 18{ 19 j.at("r").get_to(color.r); 20 j.at("g").get_to(color.g); 21 j.at("b").get_to(color.b); 22 j.at("a").get_to(color.a); 23}

With those functions, you might save/load the data through files like this:

#SelectExpand
1 2#include <fstream> // for std::ofstream, std::ifstream 3#include <iostream> // for std::cout 4 5 6void write_to_file(std::string json_filename, ALLEGRO_COLOR color) 7{ 8 std::ofstream outfile; 9 outfile.open(json_filename, std::ofstream::out); 10 nlohmann::json result; 11 12 result["my_color"] = color; 13 14 outfile << std::setw(3) << result << std::endl; 15 outfile.close(); 16} 17 18 19ALLEGRO_COLOR read_from_file(std::string json_filename) 20{ 21 std::ifstream infile(json_filename); 22 nlohmann::json j; 23 ALLEGRO_COLOR result_color; 24 25 infile >> j; 26 27 if (!j.contains("my_color")) 28 { 29 std::cout << "Expecting \"" << json_filename << "\" to contain an object named \"my_color\" but it does not exist." << std::endl; 30 return ALLEGRO_COLOR{0, 0, 0, 0}; 31 } 32 else 33 { 34 j.at("my_color").get_to(result_color); 35 } 36 37 return result_color; 38} 39 40 41int main(int argc, char **argv) 42{ 43 std::string my_filename = "foobar.json"; 44 ALLEGRO_COLOR my_color = ALLEGRO_COLOR{0.2, 0.6, 0.4, 1.0}; 45 46 write_to_file(my_filename, my_color); 47 ALLEGRO_COLOR color_as_read_from_file = read_from_file(my_filename); 48 49 std::cout << "My color, loaded from a JSON file is:" << std::endl 50 << " r: " << my_color.r << std::endl 51 << " g: " << my_color.g << std::endl 52 << " b: " << my_color.b << std::endl 53 << " a: " << my_color.a << std::endl 54 ; 55 56 return 0; 57}

Running the program, your output might look like this:

My color, loaded from a JSON file is:
  r: 0.2
  g: 0.6
  b: 0.4
  a: 1

and the foobar.json file that was written might look like this:

{
   "my_color": {
      "a": 1.0,
      "b": 0.4000000059604645,
      "g": 0.6000000238418579,
      "r": 0.20000000298023224
   }
}

Peter Hull:

I also have used nlohmann json. It's very good, I recommend it. TinyXML2 also works nicely if you want to stick to XML.

DanielH said:

However, with all the changes to C++ since, it doesn't work anymore.

This surprises me; compatibility is one of the things C++ tries very hard to do

DanielH:

I'll look into nlohmann. the JSON library I'm using doesn't have write capability.
Not fond of the from_json with void return. What if there was an issue grabbing from the file?

This surprises me; compatibility is one of the things C++ tries very hard to do

My library was written ~ 2014 and had a few errors with certain functionality removed. Next chance I get I'll see what they were to give you a better picture of what I mean.

Plus, while I was looking at the code to see if I could fix it, I decided I wasn't happy with it. The library was bulky. I am on the fence on what is deemed "better" practice. I know it's subjective. Do I add bulky object functionality or keep them barebone? I'm leaning towards barebone at the moment.

Plus with all the new functionality, I thought I could make it better/cleaner.

Mark Oates:
DanielH said:

Not fond of the from_json with void return. What if there was an issue grabbing from the file?

to_json and from_json don't grab from a file. The file interfacing is entirely up to you to implement if you wish.

If you want to be certain that the fields you expect exist in the JSON object, you can check with .contains("my_color"). You can then handle it however you want.

If there's an error parsing JSON from an invalid JSON string, then nolhmann will raise a parse error, which you can catch. It has detailed information. For example, if I were to parse a string containing this invalid JSON:

   my_color": {
      "a": 1.0,
      "b": 0.4000000059604645,
      "g": 0.6000000238418579,
      "r": 0.20000000298023224
   }
}

It would throw this error:

terminating with uncaught exception of type nlohmann::detail::parse_error: [json.exception.parse_error.101] parse error at line 2, column 4: syntax error while parsing value - invalid literal; last read: '<U+000A>   m'

DanielH:

Made the plunge. Took a few hours, but have everything converted over.

There was even a NuGet package for it.

A bit of a learning curve, and I documentation is overwhelming.

Thanks,

Edgar Reynaldo:

Also, if you're interested, Python has good JSON parsing. Though a scripting language may not be right for your project.

DanielH:

No, not quite useful.

Years ago I was messing with android programming. They use a manifest.xml that specifies any addons, app type, etc.

I thought it was a good way to initialize an Allegro program. Easier for the end-user. It parses a file and does all the background work (initializes addons, creates a display, event queue, and any timers). Also load any initial objects (bitmaps, fonts, etc).

I have a series of functions that parses json objects.

ALLEGRO_BITMAP* parse(json data);
// etc

I also have a resource handler that manages those objects. If I need an object, I just grab it. If it doesn't exists then it loads it for me.

//  set 'keep' to true if the handler manages deletion
ALLEGRO_BITMAP *grab(handler *h, const std::string& id, bool keep);

Edgar Reynaldo:

I generally use a config file for setup. Easy map like language.

if (config["Graphics"]["Fullscreen"] == std::string("1")) {
   fullscreen = true;
}

DanielH:

I thought about it, but wasn't sure if nesting is possible. That's why I originally went with xml.

json is simple. just a load of pairs with a key and an object, which could be a set of objects {}, an array [] or a value "".

Here is my manifest.json file. I like the simplicity and cleaner look over xml. The only thing is missing is ability to add comments. I could add a comment object and make sure my program ignores it.

#SelectExpand
1{ 2 "addon": 3 { 4 "audio": false, 5 "audio_codecs": false, 6 "color": true, 7 "font": true, 8 "font_ttf": true, 9 "image": true, 10 "memfile": false, 11 "native": true, 12 "physics_fs": false, 13 "primitives": true, 14 "video_streaming": false 15 }, 16 "application": 17 { 18 "display": 19 { 20 "id": "main", 21 "title": "@text/title", 22 "icon": "@image/icon", 23 "width": "@config/display_width", 24 "height": "@config/display_height", 25 "background": "@color/red", 26 "flags": "windowed|resizable", 27 "options": 28 [ 29 { 30 "option": "single_buffer", 31 "value": 1, 32 "importance": "suggest" 33 } 34 ] 35 }, 36 "timer": 37 [ 38 { 39 "id": "logic", 40 "type": "timer", 41 "speed": 60.0 42 }, 43 { 44 "id": "tick", 45 "type": "timer", 46 "speed": 100.0 47 } 48 ], 49 "eventqueue": 50 { 51 "id": "main", 52 "source": 53 [ 54 { 55 "display": "@display/main" 56 }, 57 { 58 "timer": "@timer/logic" 59 }, 60 { 61 "timer": "@timer/tick" 62 }, 63 { 64 "input": 65 { 66 "keyboard": true, 67 "mouse": true 68 } 69 } 70 ] 71 }, 72 "image": 73 [ 74 "@image/buffer" 75 ], 76 "font": 77 [ 78 "@font/main" 79 ] 80 }, 81 "path": 82 { 83 "color": "data/colors/", 84 "config": "data/config/", 85 "font": "data/fonts/", 86 "image": "data/images/", 87 "map": "data/maps/", 88 "text": "data/text/" 89 }, 90 "filename": 91 { 92 "color": "@path/color/color.json", 93 "config" : "@path/config/config.json", 94 "font": "@path/font/font.json", 95 "map": "@path/map/map.json", 96 "image": "@path/image/image.json", 97 "text": "@path/text/text.json" 98 } 99}

amarillion:

I'll check out nlohmann's library. I like that it's header only, that's a good call.

So far I've used my own JSON parser, mainly because I didn't want to bother with compiling yet another dependency. Dependencies are such a pain in C++, to the point where you prefer to write your own code rather than deal with that.

My own implementation is tested well enough. So far it works with all the JSON I've thrown at it :)

GullRaDriel:

For JSON in C, I use cJSON, and it's pretty good IMHO. (you can find it here: https://github.com/DaveGamble/cJSON )

I recently discovered that KAFKA's librdkafka is also using it (while building on RedHat 7, because the provided librdkafka is way too old, like from 2018).

That aside, it fits my needs, which are maybe simpler than others. cJSON is just a header and a c file.