0

I have a complex JSON to load into a data structure in C++11 and I got high recommendations about RapidJSON. I need to iterate over a complex JSON and looked around for answers on how to do it. The best answer I found was in this thread.

However, there's a small glitch in matching this solution to mine, I have members in the JSON that have different names but identical content:

"responsibilities": {
  "starters_reciepe": {
    "name": "bok choi salad",
    "type": "veggie",
    "ingredients": {
      "leafyIng": "bok choi",
      "proteinIng": "tofu",
      "seasoning": [
        {
          "2 tsp": "salt",
          "1 tsp": "turmric"
        }
      ]
    }
  },
  "mainCourse_reciepe": {
    "name": "pad tai",
    "type": "yum yum",
    "ingredients": {
      "leafyIng": "chard",
      "proteinIng": "soylent green"
      "seasoning": [
        {
          "2 tsp": "black pepper",
          "1 tsp": "tears of the angels"
        }
      ]
    }
}

}

Basically, I need to go over the content of the ingredients, but I can't get over the fact that starters_reciepe is not like mainCourse_reciepe.

EDITED: Here's my code:

Document d;

ifstream in("TestingJSON.json", ios::binary);
if (!in)
    throw runtime_error("Failed to open file");

istreambuf_iterator<char> head(in);
istreambuf_iterator<char> tail;
string data(head, tail);

d.Parse(data.c_str());

const Value& prog = d["responsibilities"];
for (Value::ConstValueIterator p = prog.Begin(); p != prog.End(); ++p) {
cout << (*p)["iUniqueID"].GetString()<<endl;
    const Value& inFiles = (*p)["inFiles"];
    for (Value::ConstValueIterator inFile = inFiles.Begin(); inFile != prog.End(); ++inFile) {
        cout << (*inFile)["sFileType"].GetString() << endl;
        cout << (*inFile)["pos"]["x1"].GetInt() << endl;
    }
}

Can I use wildcards and write *_reciepe?

I could find anything on RapidJSON and wildcards. Is this even a possibility?

Lotusmeristem
  • 53
  • 3
  • 13
  • The JSON you posted is not valid. Check with an online linter e.g. https://jsonlint.com/. And, the code you posted is a copy of the [link](https://github.com/Tencent/rapidjson/issues/1343#issuecomment-412591805) you posted above. It has nothing to do with the JSON you posted. You need to update the correct JSON with your correct code. – Azeem Feb 14 '20 at 10:12

2 Answers2

1

Always validate your raw JSON with linters (e.g. https://jsonlint.com/). This JSON in your question is not valid. You need to fix that.

The "responsibilites" object in your JSON contains recipes only. I'm not sure why you need to compare it with *_recipe. But, given the example below, you may easily implement that comparison if needed. This thread might be helpful in this regard.


You can use C++11's range-based for loop for these iterations. Just take care of the correct type you want to use/manipulate according to your use-case. If in doubt, consult rapidjson's tutorial and documentation.

Here's an example with raw literal string as JSON input:

#include <iostream>
#include <rapidjson/document.h>

int main()
{
    constexpr auto data = R"json(
    {
      "responsibilities": {
          "starters_recipe": {
              "name": "bok choi salad",
              "type": "veggie",
              "ingredients": {
                  "leafyIng": "bok choi",
                  "proteinIng": "tofu",
                  "seasoning": [{
                      "2 tsp": "salt",
                      "1 tsp": "turmric"
                  }]
              }
          },
          "mainCourse_recipe": {
              "name": "pad tai",
              "type": "yum yum",
              "ingredients": {
                  "leafyIng": "chard",
                  "proteinIng": "soylent green",
                  "seasoning": [{
                      "2 tsp": "black pepper",
                      "1 tsp": "tears of the angels"
                  }]
              }
          }
      }
    }
    )json";

    rapidjson::Document doc;
    doc.Parse( data );

    const auto& courses = doc["responsibilities"].GetObject();
    for ( const auto& course : courses )
    {
        const auto& course_name = course.name.GetString();
        const auto& recipe      = courses[course_name].GetObject();
        const auto& recipe_name = recipe["name"].GetString();
        const auto& ingredients = recipe["ingredients"].GetObject();
        const auto& leafyIng    = ingredients["leafyIng"].GetString();
        const auto& proteinIng  = ingredients["proteinIng"].GetString();
        const auto& seasoning   = ingredients["seasoning"].GetArray()[0].GetObject();

        std::cout << "Course: " << course_name << '\n'
                  << "Recipe: " << recipe_name << '\n'
                  << "Ingredients:\n"
                  << "- Leaf     : " << leafyIng << '\n'
                  << "- Protein  : " << proteinIng << '\n'
                  << "- Seasoning:\n";

        for ( const auto& s : seasoning )
        {
            const auto& k = s.name.GetString();
            const auto& v = s.value.GetString();
            std::cout << "  - " << k << ", " << v << '\n';
        }

        std::cout << '\n';
    }

    return 0;
}

Output:

Course: starters_recipe
Recipe: bok choi salad
Ingredients:
- Leaf     : bok choi
- Protein  : tofu
- Seasoning:
  - 2 tsp, salt
  - 1 tsp, turmric

Course: mainCourse_recipe
Recipe: pad tai
Ingredients:
- Leaf     : chard
- Protein  : soylent green
- Seasoning:
  - 2 tsp, black pepper
  - 1 tsp, tears of the angels

The "seasoning" array contains only one object that's why this line refers to the 0th index:

const auto& seasoning = ingredients["seasoning"].GetArray()[0].GetObject();
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^

I guess that you intended to have an array of objects, not an array with a single object.

This:

"seasoning": [   
     { "2 tsp": "black pepper" },
     {  "1 tsp": "tears of the angels" }
]

and, not this:

"seasoning": [{    
      "2 tsp": "black pepper",
      "1 tsp": "tears of the angels"
}]

You have to manipulate this accordingly in code also.

Azeem
  • 11,148
  • 4
  • 27
  • 40
0

If you need to check only if a string ends with a known value this is quite simple to do directly comparing it without wildcards libraries:

auto& obj = doc["responsibilities"];
std::string suffix = "_reciepe";
for (auto p = obj.MemberBegin(); p != obj.MemberEnd(); ++p) {
    auto& member_name = p->name;
    if (member_name.GetStringLength() >= suffix.length()) {
        if (memcmp(member_name.GetString() + member_name.GetStringLength() - suffix.length(), suffix.c_str(), suffix.length()) == 0) {
            // Process matching node
            std::cout << p->value["name"].GetString() << std::endl;
        }
    }
}

If you need to match against more complex patterns then you can use std::regex

dewaffled
  • 2,850
  • 2
  • 17
  • 30
  • That looks great, but I tried to run it and got: Assertion failed: IsObject(), file c:\testingcode\rapidjson\document.h, line 1285 TestingCode.exe (process 90364) exited with code 3 I edited the original post and added my code. – Lotusmeristem Feb 14 '20 at 09:11