How do I convert a rapidjson array iterator to a rapidjson::value?
I do not want answers that focus on how to get the contents of a rapid json array, or how to iterate through it.
I am also very aware that I can access members through the iterator using the example in the rapidjson documentation itr->name, but that is not what I want either. That form of working with rapidjson arrays already appears on many stack overflow questions and the rapidjson docs, and has been covered.
I need to end up with a rapidjson::value when starting with a rapidjson array iterator.
If we have an std::vector<int> than we can assign
std::vector<int>::iterator itMyInt = myvector.begin();
const int & myInt = *itMyInt;
I would expect to be able to do the same thing with a rapidjson array iterator, but my compiler disagrees.
The reason I need a rapidjson::value is that I'd like to reuse the same parsing method to parse the json object when is an element of an array, as I would when parsing that object on its own and not in an array.
Let me demonstrate with my minimal example:
// Rapid JSON Includes
#include <rapidjson/Document.h>
#include <rapidjson/StringBuffer.h>
#include <rapidjson/writer.h>
// Standard Includes
#include <exception>
#include <string>
#include <sstream>
//--------------------------------------------------------------------------------------------------
// NOTE -This stub cannot change
void ParseCar(const rapidjson::Value & carJson)
{
}
//--------------------------------------------------------------------------------------------------
int main()
{
std::string json =
"{"
" \"cars\" : ["
" {"
" \"name\" : \"Fiat\","
" \"price\" : 19.95"
" },"
" {"
" \"name\" : \"FRS\","
" \"price\" : 19995.00"
" }]"
"}";
// Parse the JSON
rapidjson::Document document;
document.Parse(json.c_str());
if (document.HasParseError())
{
// Error - Failed to parse JSON
std::ostringstream msg;
msg << "There was an error parsing the JSON"
<< " Error Code: " << document.GetParseError()
<< " Error Offset: " << document.GetErrorOffset();
throw std::exception(msg.str().c_str());
}
// Cars array
if (!document.HasMember("cars") ||
!document["cars"].IsArray())
{
std::string msg("Expected \"cars\" JSON array");
throw std::exception(msg.c_str());
}
const rapidjson::Value & carsArrayJSON = document["cars"];
/* Doesn't compile - No GetArray method exists
for (auto & carJSON : carsArrayJSON.GetArray())
{
}
*/
for (rapidjson::Value::ConstMemberIterator itCarJSON = carsArrayJSON.MemberBegin(); itCarJSON != carsArrayJSON.MemberEnd(); ++itCarJSON)
{
// Error - const rapidjson::GenericMember<Encoding,Allocator>' to 'const rapidjson::Value
const rapidjson::Value & carJSON = *itCarJSON;
ParseCar(carJSON);
}
return 0;
}
MemberIterator iterates Members of an Object. ValueIterator iterates Values in an Array.
Also, I'm not sure why that loop had issues for you, this compiles for me:
std::string json =
"{"
" \"cars\" : ["
" {"
" \"name\" : \"Fiat\","
" \"price\" : 19.95"
" },"
" {"
" \"name\" : \"FRS\","
" \"price\" : 19995.00"
" }]"
"}";
Document doc;
doc.Parse(json.data());
Value const& cars = doc["cars"];
for (auto& car : cars.GetArray()) {
cout << "name: " << car["name"].GetString() << " "
<< "price: " << car["price"].GetFloat() << endl;
}
And returns:
name: Fiat price: 19.95
name: FRS price: 19995
As for applying your function in a loop:
for (Value& car : cars.GetArray()) {
ParseCar(car);
}
If you must use an iterator:
for (Value::ValueIterator car = cars.Begin(); car != cars.End(); ++car) {
ParseCar(*car);
}
Or with auto:
for (auto car = cars.Begin(); car != cars.End(); ++car) {
ParseCar(*car);
}
Note: this will only work in an array.
Recently, I faced a similar problem and I came out with a different approach.
First, I set up a pointer to the desired path like so:
int myIterator = 0;
string myString = "/Layer1/Layer2/" + to_string(myIterator);
Pointer p(myString.c_str());
I now have a pointer to a variable path in my JSON object that varies with myIterator.
Then, I just used a for loop in which I use the SetValueByPointer function to append the pointer's value to a JSON object.
Related
I would like to iterate over each of entries in a json object, but I am getting one incomprehensible error after the other. How to correct the following example?
#include <iostream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
void bla(std::string a) {
std::cout << a << '\n';
}
int main() {
json RecentFiles;
RecentFiles["1"]["Name"] = "test1.txt";
RecentFiles["1"]["Last modified"] = "monday";
RecentFiles["1"]["Score"] = 5.0f;
RecentFiles["2"]["Name"] = "test2.txt";
RecentFiles["2"]["Last modified"] = "tuesday";
RecentFiles["2"]["Score"] = 5.0f;
for (auto it = RecentFiles.begin(); it != RecentFiles.end(); ++it) {
bla("JSON: Recent file = " + it.value()["Name"]);
}
std::cout << RecentFiles; }
Error:
prog.cc: In function 'int main()':
prog.cc:18:31: error: invalid conversion from 'const char*' to 'nlohmann::detail::iter_impl<nlohmann::basic_json<> >::difference_type {aka long int}' [-fpermissive]
std::cout << it["Name"];
^
In file included from prog.cc:2:0:
./nlohmann/json.hpp:4418:15: note: initializing argument 1 of 'nlohmann::detail::iter_impl<BasicJsonType>::reference nlohmann::detail::iter_impl<BasicJsonType>::operator[](nlohmann::detail::iter_impl<BasicJsonType>::difference_type) const [with BasicJsonType = nlohmann::basic_json<>; nlohmann::detail::iter_impl<BasicJsonType>::reference = nlohmann::basic_json<>&; nlohmann::detail::iter_impl<BasicJsonType>::difference_type = long int]'
reference operator[](difference_type n) const
^
The above is done in the sandbox
https://wandbox.org/permlink/LNck7Gktm14bmPy0
This is not actual code that I am using, I just want to see if I can understand how to do the various basic things that I need to do with JSON.
Currently I understand so little, that I do not know if what I am doing is essentially correct but just breaks due to something stupid, or if I am doing something fundamentally wrong.
The nlohmann json library promotes itself as "JSON for modern C++" and aspires to behave "just like an STL container". There is, however, no container in the C++ standard library that is both "vector-like" and "map-like", and that supports both begin/end iterators over values, and begin/end iterators over key/value pairs. So something new is needed.
nlohmann's original solution was to copy jsoncpp's approach, which supports begin/end iterators for json arrays, and adds a distinctly unstandard key() function to the iterator to also support json objects. So you could write
for (auto it = RecentFiles.begin(); it != RecentFiles.end(); ++it)
{
std::cout << it.key() << "\n";
std::cout << (*it)["Name"].get<std::string>() << "\n";
std::cout << (*it)["Last modified"].get<std::string>() << "\n";
}
But being an unstandard way of iterating over key/values, this doesn't have standard library support for range based for loops over key/values.
nlohmann later added the json::items() function that does support iteration over json objects with standard iterators, and which does have standard library support for range based for loops, viz.
int main()
{
json RecentFiles;
RecentFiles["1"]["Name"] = "test1.txt";
RecentFiles["1"]["Last modified"] = "monday";
RecentFiles["1"]["Score"] = 5.0f;
RecentFiles["2"]["Name"] = "test2.txt";
RecentFiles["2"]["Last modified"] = "tuesday";
RecentFiles["2"]["Score"] = 5.0f;
for (const auto& item : RecentFiles.items())
{
std::cout << item.key() << "\n";
for (const auto& val : item.value().items())
{
std::cout << " " << val.key() << ": " << val.value() << "\n";
}
}
std::cout << "\nor\n\n";
for (const auto& item : RecentFiles.items())
{
std::cout << item.key() << "\n";
std::cout << " " << item.value()["Name"].get<std::string>() << "\n";
std::cout << " " << item.value()["Last modified"].get<std::string>() << "\n";
std::cout << " " << item.value()["Score"].get<double>() << "\n";
}
}
Output:
1
Last modified: "monday"
Name: "test1.txt"
Score: 5.0
2
Last modified: "tuesday"
Name: "test2.txt"
Score: 5.0
or
1
test1.txt
monday
5
2
test2.txt
tuesday
5
I'm new to C++ and I have a vector of doctors.
I add a new doctor with the following code:
void DoctorAdmin::setDoctor(std::string lastname, std::string forename,
Person::Sex sex){
//Create new doctor
Doctor* doc = new Doctor(lastname, forename, sex);
//insert at the end of the vector
doctors.push_back(doc);
}
Then I want to show their information on the console:
void DoctorAdmin::showDoctors(){
cout << "Doctors:" << endl;
cout << "Name" << "\t\t\t" << "Forename" << "\t\t\t" << "Sex" << endl;
for (vector<Doctor*>::iterator i = doctors.begin(); i != doctors.end(); i++){
Doctors* doc = doctors.at(i);
cout << doc->getName() << "\t\t\t" << doc->getForename() << "\t\t\t"
<< doc->getSex() << endl;
}
After doing it like this I get two Errors:
E0304 No instance of overloaded function "std::vector<_Ty, _Alloc>::at [mit _Ty=Doctors *, _Alloc=std::allocator<Doctors *>]" matches the argument list.
// and
C2664 "Doctors *const &std::vector<Doctors *,std::allocator<_Ty>>::at(const unsigned int) const" : cannot convert from Argument "std::_Vector_iterator<std::_Vector_val<std::_Simple_types<_Ty>>>" in "const unsigned int"
How do I use the vector iterator correctly to avoid this?
An iterator is not index-like, it is pointer-like.
for (vector<Arzt*>::iterator doc = aerzte.begin(); doc != aerzte.end(); doc++)
{
cout << (*doc)->getName() << "\t\t\t" << (*doc)->getVorname() << "\t\t\t"
<< (*doc)->getGeschlecht() << endl;
}
It seems like you are confused as to when you need to new things too. Most of the time you don't need new
vector<Arzt> aerzte;
void ArztAdmin::anlegenArzt(std::string name, std::string vorname, Person::Geschlecht geschlecht){
// Create new doctor at the end of the vector
aerzte.emplace_back(name, vorname, geschlecht);
}
You can also directly bind references as loop variables
for (Arzt & doc : aerzte)
{
cout << doc.getName() << "\t\t\t" << doc.getVorname() << "\t\t\t"
<< doc.getGeschlecht() << endl;
}
The at function requires an index, but a vector<Arzt*>::iterator is not an index, neither semantically nor technically. An iterator points directly to an element, whereas an index represents the distance between a container's start and the element in a container that allows random element access.
Because an iterator points directly to an element, the at function isn't even necessary in your loop. *i yields the element:
Arzt* doc = *i;
Beginning with C++11, the code for such simple loops can be written in a shorter way using auto:
for (auto i = aerzte.begin(); i != aerzte.end(); i++){
The compiler knows what type i really is because it knows what begin() returns.
Even better, use a range-based loop:
for (auto doc : aerzte){
cout << doc->getName() << "\t\t\t" << doc->getVorname() << "\t\t\t"
<< doc->getGeschlecht() << endl;
}
And while we're at it, don't use dynamic memory allocation when you don't have to. This isn't Java or C#; new is dangerous territory in C++ and should be avoided:
#include <vector>
#include <string>
#include <iostream>
struct Arzt
{
Arzt(std::string const& name, std::string const& vorname) :
name(name),
vorname(vorname)
{
}
std::string name;
std::string vorname;
// Geschlecht omitted for brevity's sake
};
int main()
{
std::vector<Arzt> aerzte;
Arzt doc1("foo", "bar");
Arzt doc2("foo", "bar");
Arzt doc3("foo", "bar");
aerzte.push_back(doc1);
aerzte.push_back(doc2);
aerzte.push_back(doc3);
for (auto const& arzt : aerzte)
{
std::cout << arzt.name << ' ' << arzt.vorname << '\n';
}
}
As you are no longer iterating over pointers but over larger objects, const& should be used in the for loop.
I am trying to print a map in an organized way. My map is defined like this:
map<std::string,std::vector<message *> > data;
where message is a struct like this:
struct message{
static unsigned int last_id;
unsigned int id;
std::string msg;
std::string timestamp;
message(const std::string& recvbuf_msg,const std::string& a_timestamp) :
msg(recvbuf_msg), timestamp(a_timestamp), id(++last_id)
{
}
};
I tried this way of printing it:
std::cout << (data[username]).at(0)->msg << std::endl;
But it gives a debug error when reaching that function, how can i solve it?
Error R6010 - abort() has been called suggests that either there is no entry for key username in the map, or the vector of messages for that user is empty. You need to make sure the containers are nonempty before accessing elements. It is a good idea to use iterators, here is an example of how to print the messages for all usernames:
for(auto mapIt = data.cbegin(); mapIt != data.cend(); ++mapIt)
{
std::cout << "printing data for " << mapIt->first << ":" << std::endl;
for(auto vectIter = mapIt->second.cbegin(); vectIter != mapIt->second.cend(); ++vectIter)
{
std::cout << (*vectIter)->msg << ", " << (*vectIter)->timestamp << ", "
<< (*vectIter)->id << std::endl;
}
}
The code uses auto, so if you are not using a C++11 compliant compiler, you will have to write the iterator types yourself.
I'm trying to use boost json with property trees to decode a json message. I'm only interested about checking whether "mykey" is in the message and, if that is the case, get the corresponding values.
I'm a little lost in boost documentation, and I was trying to see what the actual code would be to parse a message such as the one below.
{
// some values
"mykey": [
{
"var1": "value1_str",
"var2" : "value2"
}
]
// some other values
}
I don't know about Boost ptree for JSON. I've tried it but it seemed... very clunky.
Here's a simple JSON parser based on the RFC, made in Spirit: https://github.com/sehe/spirit-v2-json/tree/q21356666
You could use it for your use case like test.cpp
#include <vector>
#include "json.hpp"
struct X {
static X from_json(JSON::Value const& v);
std::string var1;
double var2;
};
int main()
{
auto doc = as_object(JSON::parse(
"{\n"
" // some values\n"
" \"mykey\": [\n"
" {\n"
" \"var1\": \"value1_str\",\n"
" \"var2\" : 3.14\n"
" }\n"
" ]\n"
" // some other values\n"
"}\n"
));
if (doc.has_key("mykey"))
{
X data = X::from_json(doc["mykey"]);
std::cout << "X.var1: " << data.var1 << "\n";
std::cout << "X.var2: " << data.var2 << "\n";
}
std::cout << "doc: " << doc << "\n";
std::cout << "doc[\"mykey\"]: " << doc["mykey"] << "\n";
}
X X::from_json(JSON::Value const& v)
{
X result;
auto& o = as_object(as_array(v)[0]);
result.var1 = as_string(o["var1"]);
result.var2 = as_double(o["var2"]);
return result;
}
Output:
X.var1: value1_str
X.var2: 3.14
doc: {"mykey":[{"var1":"value1_str","var2":3.14}]}
doc["mykey"]: [{"var1":"value1_str","var2":3.14}]
There are other json libraries around. I suggest you select one to suit your needs.
I have a problem in using the std::map correctly. The class Example is a class with an ID, a label, a vector of keypoints and a descriptor matrix. The class Examples is a map for retrieving an example given its ID. The examples are read from files on disk, stored in the map, then used later.
Even if it is conceptually very simple, I am not able to fill the map properly.
I have the following class:
class Example
{
public:
std::string id;
std::string label;
std::vector<cv::KeyPoint> keypoints;
cv::Mat descriptors;
Example(std::string id_, std::string label_)
: id(id_), label(label_)
{
// ... nothing ...
}
string to_string() const
{
stringstream ss;
ss << "#" << id
<< " (" << label << ")"
<< " - #keypoints " << keypoints.size()
<< ", descr " << descriptors.rows << " x " << descriptors.cols;
return ss.str();
} // to_string
}; // class Example
ostream& operator <<(ostream & out, const Example &ex)
{
out << ex.to_string();
return out;
} // operator <<
And this one:
// OLD: class Examples : public std::map<std::string, Example*> {
class Examples {
// New line after Martini's comment
std::map<std::string, Example*> _map;
[...]
void fill() {
// create an example
Example *example = new Example(id, label);
// inputstream in
// Read all the keypoints
cv::KeyPoint p;
for(int i=0; ... ) {
in.read(reinterpret_cast<char *>(&p), sizeof(cv::KeyPoint));
example->keypoints.push_back(p); // push_back copies p
} // for
// ... misc code
cv::Mat descr(n_keypoints, d_size, cv_type, cv::Scalar(1));
// ... read Mat from inputstream in, then assign it to the example
example->descriptors = descr;
// SEE THE OUTPUT BELOW
std::cout << "INSERT THIS: " << (*example) << std::endl;
_map.insert(std::pair<string,Example*>(id, example));
std::cout << "READ THIS: " << *(get_example(id)) << std::endl;
// ... other code
} // fill
// Code modified after Martini's comment.
Example* get_example(const std::string &id) const {
std::map<std::string, Example*>::const_iterator it = _map.find(id);
if( it == _map.end()) {
// ... manage error
// ... then exit
} // if
return it->second;
} // get_example
} // class Examples
The output from the insert/get lines is:
INSERT THIS: #122225084 (label) - #keypoints 711, descr 711 x 128
READ THIS: #122225084 (label) - #keypoints 0, descr 0 x 0
In the insert I had a pointer to an example with 711 keypoints and a 711x128 descriptor matrix. If I read the example using its ID right after the insert, I get a pointer to an example with 0 keypoints and an empty matrix.
What am I doing wrong?
Looking into your code one possible explanation is that you already have element in the map with the same key. To diagnose that first of all print value of pointer before you add object and after that (something like this):
std::cout << "INSERT THIS: " << (void *)example << " " << (*example) << std::endl;
_map.insert(std::pair<string,Example*>(id, example));
std::cout << "READ THIS: " << (void *)get_example(id) << " " << *(get_example(id)) << std::endl;
Next or another way is to check result of insert:
if( !_map.insert(std::pair<string,Example*>(id, example)).second )
std::cout << "ERROR: example:" << id << " is already there";
If you want to override element unconditionally you can use oprator[]:
_map[ id ] = example;
If there are really duplicates you will get memory leak (you are getting it anyway) so I would strongly recommend to use smart pointer to store data in your map.