Unable to write to JSON file from Qt - c++

I have a program where I store some attributes in a class and then I create objects for that. Then I store those objects in a vector. I can sort and manipulate it. Now when I try to write out that data to a .JSON file, it does not do that.
My Code -
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonValue>
vector<FlightData>FlightList;
vector<QJsonObject> JsonArray;
void MainWindow::WriteToJson()
{
sort(FlightList.begin(), FlightList.end());
vector<FlightData>::iterator itr;
for (itr = FlightList.begin(); itr != FlightList.end(); itr++)
{
static int i = 0;
QString Time = QString::fromStdString(FlightList[i].getTime());
QString Latitude = QString::number(FlightList[i].getLatitude());
QString Longitude = QString::number(FlightList[i].getLongitude());
QString Course = QString::number(FlightList[i].getCourse());
QString KTS = QString::number(FlightList[i].getKTS());
QString MPH = QString::number(FlightList[i].getMPH());
QString AltitudeFeet = QString::number(FlightList[i].getAltitudeFeet());
QString ReportingFacility = QString::fromStdString(FlightList[i].getReportingFacility());
i++;
QJsonObject obj;
obj.insert("Time", Time+",");
obj.insert("Latitude", Latitude+",");
obj.insert("Longitude", Longitude+",");
obj.insert("Course", Course+",");
obj.insert("KTS", KTS+",");
obj.insert("MPH", MPH+",");
obj.insert("AltitudeFeet", AltitudeFeet+",");
obj.insert("ReportingFacility", ReportingFacility+",");
JsonArray.push_back(obj);
}
}
void MainWindow::on_Write_JSON_clicked()
{
WriteToJson();
QFile JsonFile("FlightDatar.json");
if(JsonArray.empty())
{
qDebug() << "Unable to write to JSON as JsonArray is empty";
}
if(!JsonArray.empty())
{
if(JsonFile.open(QIODevice::WriteOnly | QIODevice::Text))
{
QTextStream out(&JsonFile);
for(auto itr = JsonArray.begin(); itr != JsonArray.end(); itr++)
{
out << QJsonDocument(*itr).toJson() << Qt::endl;
qDebug() << *itr;
}
ui->statusbar->showMessage("Writing to JSON successful", 4000);
JsonArray.clear();
JsonFile.flush();
JsonFile.close();
}
}
}
The interesting part is- the values get printed on the console with the qDebug() but they are not getting written into Flight_Datar.json for some reason.
Kindly help me if you can.

Related

How to extract value from json array with QJsonDocument format

I'm getting a json format like this and I want to get the value of "Duration", "Id", "LoadCumulLimit" and "Notes".
QJsonDocument({"d":{"results":[{"Duration":"420.000","Id":"123456789XYZ","LoadCumulLimit":"15.000","NavWpNioshToOpNoish":{"__deferred":{"uri":"http://xxx/WorkplaceNOISHDataSet('123456789XYZ')/NavWpNioshToOpNoish"}},"Notes":"123456789XYZ","__metadata":{"id":"xxx/WorkplaceNOISHDataSet('123456789XYZ')","type":"xxx.WorkplaceNOISHData","uri":"xxx/WorkplaceNOISHDataSet('123456789XYZ')"}}]}})
I tried to do this but it doesn't work and it return empty with array
`
QJsonDocument document = QJsonDocument::fromJson(content.toUtf8());
QJsonArray documentArray = document.array();
QStringList wordList;
for (const QJsonValue &i : documentArray)
{
//qInfo() << i.toString() << endl;
wordList << i.toString();
}
Could you guys give me a help or any suggest?
You could convert the QJsonDocument to a QVariant. Then you can use QVariantMap or QVariantList to walk the document and use the appropriate toString() or toDouble() to retrieve the values.
The following is hard-coded to your JSON there are only minimal validation checks included. (i.e. it is a disclaimer that the code is presented for educational purposes only and made not be production ready).
bool parse()
{
QString json = "{\"d\":{\"results\":[{\"Duration\":\"420.000\",\"Id\":\"123456789XYZ\",\"LoadCumulLimit\":\"15.000\",\"NavWpNioshToOpNoish\":{\"__deferred\":{\"uri\":\"http://xxx/WorkplaceNOISHDataSet('123456789XYZ')/NavWpNioshToOpNoish\"}},\"Notes\":\"123456789XYZ\",\"__metadata\":{\"id\":\"xxx/WorkplaceNOISHDataSet('123456789XYZ')\",\"type\":\"xxx.WorkplaceNOISHData\",\"uri\":\"xxx/WorkplaceNOISHDataSet('123456789XYZ')\"}}]}}";
QJsonDocument document = QJsonDocument::fromJson(json.toUtf8());
if (document.isEmpty() || document.isNull()) return false;
QVariantMap root = document.toVariant().toMap();
if (root.isEmpty()) return false;
QVariantMap d = root["d"].toMap();
if (d.isEmpty()) return false;
QVariantList results = d["results"].toList();
if (results.isEmpty()) return false;
foreach (QVariant varResult, results)
{
QVariantMap result = varResult.toMap();
if (result.isEmpty()) return false;
bool ok = true;
double duration = result["Duration"].toDouble(&ok);
if (!ok) return false;
QString id = result["Id"].toString();
if (id.isEmpty() || id.isNull()) return false;
double loadCumulLimit = result["LoadCumulLimit"].toDouble(&ok);
if (!ok) return false;
QString notes = result["Notes"].toString();
if (!notes.isEmpty() || notes.isNull()) return false;
qDebug() << id << duration << loadCumulLimit << notes; // "123456789XYZ" 420 15 "123456789XYZ"
}
return true;
}
Alternatively, you can just use QJsonDocument, QJsonValue and QJsonArray to walk the document and use the corresponding toString() and toDouble() to retrieve the values. Again, there are minimal validation checks included:
bool parse2()
{
QString json = "{\"d\":{\"results\":[{\"Duration\":\"420.000\",\"Id\":\"123456789XYZ\",\"LoadCumulLimit\":\"15.000\",\"NavWpNioshToOpNoish\":{\"__deferred\":{\"uri\":\"http://xxx/WorkplaceNOISHDataSet('123456789XYZ')/NavWpNioshToOpNoish\"}},\"Notes\":\"123456789XYZ\",\"__metadata\":{\"id\":\"xxx/WorkplaceNOISHDataSet('123456789XYZ')\",\"type\":\"xxx.WorkplaceNOISHData\",\"uri\":\"xxx/WorkplaceNOISHDataSet('123456789XYZ')\"}}]}}";
QJsonDocument document = QJsonDocument::fromJson(json.toUtf8());
if (document.isEmpty() || document.isNull()) return false;
QJsonValue d = document["d"];
if (d.isNull() || d.isUndefined()) return false;
QJsonArray results = d["results"].toArray();
if (results.isEmpty()) return false;
foreach (QJsonValue result, results)
{
double duration = result["Duration"].toDouble();
QString id = result["Id"].toString();
if (id.isEmpty() || id.isNull()) return false;
double loadCumulLimit = result["LoadCumulLimit"].toDouble();
QString notes = result["Notes"].toString();
if (!notes.isEmpty() || notes.isNull()) return false;
qDebug() << id << duration << loadCumulLimit << notes; // "123456789XYZ" 420 15 "123456789XYZ"
}
return true;
}
You have:
object d {
object results {
[ { several objects to be extracted} ]
}
}
To extract a value of an object by given a key, call operator[](key) on QJsonValue.
When you have an array, to extract its first item call operator[](0) on this array. When you have found an object at desired key, you can convert its value to the value of specified type by toString/toInt/toDouble etc. methods of QJsonValue.
Short version:
QJsonValue item0 = document["d"]["results"].toArray()[0];
QStringList wordList;
wordList << item0["Duration"].toString() << item0["Id"].toString() << item0["LoadCumulLimit"].toString() << item0["Notes"].toString();
the longer version:
QJsonValue dObj = document["d"];
QJsonValue resultsObj = dObj["results"];
QJsonArray resultsArray = resultsObj.toArray();
QJsonValue item0 = resultsArray[0];
QStringList wordList;
wordList << item0["Duration"].toString() << item0["Id"].toString() << item0["LoadCumulLimit"].toString() << item0["Notes"].toString();

looking for specific Design Pattern in C++ that solve this problem

I am looking for specific Design Pattern in C++ that solve this problem.
I want to design a Storyboard. Our version of the Storyboard
contains arbitrary many notes (imagine it like putting sticky notes on a board).
Every note has a title, a text and a set of tags. E.g.
- title: "Test Traceplayer"
- text: "Implement a unit test for the class Traceplayer of the spark core framework."
- tags: {"unit test", "traceplayer", "testing", "spark core"}
Our Storyboard should enable us to search for notes by title, text and tags.
E.g.:
searchByTitle( "Test Traceplayer" )
searchByTag({"testing", "unit test"})
searchByText("Implement a unit test for the class Traceplayer of the spark core framework.")
For the sake of simplicity we don't want to do any similiarity or prefix matching when
searching for a title, tag or text. Only an exact match should give results.
I have number of solution that solve this problem O(1) search complexity But can any one suggest any "Design Pattern" that solve this problem.
Solve that issue with three STL map and get constant time search complexity
Looking for a specific Design Pattern that solves this problem.
I have solved this problem using 3 STL Map and solution get O(1) search complexity
#include <iostream>
#include <vector>
#include <map>
#define INPUT 8
class Note {
public:
string Tital;
string Text;
vector<string> vec;
Note(){
Tital = "\0";
Text = "\0";
}
};
class storyBoard{
public:
void AddNote(string Tital,string Text,vector<string> vec );
void RemoveByTital(string &tital);
void PrintStoredData();
Note* searchByTitle(string titleSearch);
Note* searchByText(string text_);
vector<Note*> searchByTag(string titleSearch);
void printSlip(Note *tm);
storyBoard(){}
private:
std::map<string,Note *> TitalMap;
std::map<string,Note *> TextMap;
std::map<string,std::vector<Note *> > TagsMap;
};
Note* storyBoard::searchByTitle(string titleSearch){
auto it_v = TitalMap.find(titleSearch);
if (it_v != TitalMap.end()){
cout<< "Tital search result is below:-"<<endl;
return it_v->second;
} else {
cout <<"data "<<titleSearch << " Not found"<<endl;
return NULL;
}
}
Note* storyBoard::searchByText(string titleSearch){
auto it_v = TextMap.find(titleSearch);
if (it_v != TextMap.end()){
cout<< "Text search result is below:-"<<endl;
return it_v->second;
} else {
cout <<"data "<<titleSearch << " Not found"<<endl;
return NULL;
}
}
vector<Note*> storyBoard::searchByTag(string tagSearch){
auto it_v = TagsMap.find(tagSearch);
if (it_v != TagsMap.end()){
cout<< "Tag search result is below:-"<<endl;
return it_v->second;
} else {
cout <<"data "<<tagSearch << " Not found"<<endl;
vector<Note*> del;
return del;
}
}
void storyBoard::AddNote(string Tital, string Text, vector<string> v){
Note *note = new Note;
note->Tital = Tital;
note->Text = Text;
note->vec = v;
TitalMap[note->Tital] = note;
TextMap[note->Text] = note;
for (auto it = note->vec.begin(); it != note->vec.end(); ++it){
//check that is tags already
auto it_v = TagsMap.find(*it);
if (it_v != TagsMap.end()){
it_v->second. push_back(note);
} else {
vector<Note *> &v = TagsMap[*it];
v.push_back(note);
}
}
}
void storyBoard::printSlip(Note *tm){
cout << "Tital=" << tm->Tital <<endl
<< "Text=" << tm->Text <<endl
<< "Tags = ";
for (auto it = tm->vec.begin(); it != tm->vec.end(); ++it){
cout<< *it<<"\t";
}
cout<<endl<<endl;
}
void storyBoard::PrintStoredData(){
for(auto tm : TitalMap){
printSlip(tm.second);
}
cout<<endl;
}
void feed_data_for_testing(storyBoard &Sb);
void TestCase(storyBoard &Sb);
int main() {
storyBoard Sb;
feed_data_for_testing(Sb);
Sb.PrintStoredData(); /*Print all contain data */
cout<<"************* From Here start searching ************"<<endl;
TestCase(Sb);
return 0;
}
void TestCase(storyBoard &Sb){
Note* obj = Sb.searchByTitle("Tital-3");
if(obj != NULL){
Sb.printSlip(obj);
}
obj = Sb.searchByText("Text-4");
if(obj != NULL){
Sb.printSlip(obj);
}
vector<Note *> vec = Sb.searchByTag("tag-3");
if(vec.size() !=0){
for (auto it = vec.begin(); it != vec.end(); ++it){
//cout<<(*it)->Tital << "\t";
Sb.printSlip(*it);
}
}
}
void feed_data_for_testing(storyBoard &Sb){
vector<string> tags ;
int count =INPUT;
for(int i =1;i<=count;i++){
string tital = "Tital-" + std::to_string(i);
string text = "Text-" + std::to_string(i);
tags.clear();
for(int j =1;j<=i;j++){
string tag_ = "tag-" + std::to_string(j);
tags.push_back(tag_);
}
Sb.AddNote(tital,text,tags);
}
}
I am looking for a design pattern that solves this issue.
I update your code at the following point:-
convert the class into singleton so that data maintain a single map for each type
change map to unorder_map
#define INPUT 8
using namespace std;
/*use class to store single slip data*/
class Note {
public:
string Tital;
string Text;
vector<string> vec;
Note(){ //constructor to initialize data zero
Tital = "\0";
Text = "\0";
}
};
/*create a singalton pattern class so that create only one data storage*/
class storyBoard{
public:
static storyBoard* getInstance(){
storyBoard* Instance= instance.load();
if ( !Instance ){
std::lock_guard<std::mutex> myLock(lock);
Instance= instance.load();
if( !Instance ){
Instance= new storyBoard();
instance.store(Instance);
}
}
return Instance;
}
void AddNote(string Tital,string Text,vector<string> vec );
void RemoveByTital(string &tital);
void PrintStoredData();
Note* searchByTitle(string titleSearch);
Note* searchByText(string text_);
vector<Note*> searchByTag(string titleSearch);
void printSlip(Note *tm);
private:
storyBoard()= default;
~storyBoard()= default;
storyBoard(const storyBoard&)= delete;
storyBoard& operator=(const storyBoard&)= delete;
static std::atomic<storyBoard*> instance;
static std::mutex lock;
std::unordered_map<string,Note *> TitalMap;
std::unordered_map<string,Note *> TextMap;
std::unordered_map<string,std::vector<Note *> > TagsMap;
};
std::atomic<storyBoard*> storyBoard::instance;
std::mutex storyBoard::lock;
Note* storyBoard::searchByTitle(string titleSearch){
auto it_v = TitalMap.find(titleSearch);
if (it_v != TitalMap.end()){
cout<< "Tital search result is below:-"<<endl;
return it_v->second;
} else {
cout <<"data "<<titleSearch << " Not found"<<endl;
return NULL;
}
}
Note* storyBoard::searchByText(string titleSearch){
auto it_v = TextMap.find(titleSearch);
if (it_v != TextMap.end()){
cout<< "Text search result is below:-"<<endl;
return it_v->second;
} else {
cout <<"data "<<titleSearch << " Not found"<<endl;
return NULL;
}
}
vector<Note*> storyBoard::searchByTag(string tagSearch){
auto it_v = TagsMap.find(tagSearch);
if (it_v != TagsMap.end()){
cout<< "Tag search result is below:-"<<endl;
return it_v->second;
} else {
cout <<"data "<<tagSearch << " Not found"<<endl;
vector<Note*> del;
return del;
}
}
void storyBoard::AddNote(string Tital, string Text, vector<string> v){
Note *note = new Note;
note->Tital = Tital;
note->Text = Text;
note->vec = v;
TitalMap[note->Tital] = note;
TextMap[note->Text] = note;
for (auto it = note->vec.begin(); it != note->vec.end(); ++it){
//check that is tags already
auto it_v = TagsMap.find(*it);
if (it_v != TagsMap.end()){
it_v->second. push_back(note);
} else {
vector<Note *> &v = TagsMap[*it];
v.push_back(note);
}
}
}
void storyBoard::printSlip(Note *tm){
cout << "Tital=" << tm->Tital <<endl
<< "Text=" << tm->Text <<endl
<< "Tags = ";
for (auto it = tm->vec.begin(); it != tm->vec.end(); ++it){
cout<< *it<<"\t";
}
cout<<endl<<endl;
}
void storyBoard::PrintStoredData(){
for(auto tm : TitalMap){
printSlip(tm.second);
}
cout<<endl;
}
/**temporary function only use for testing*/
void TestCase(){
storyBoard *Sb = storyBoard::getInstance();
Note* obj = Sb->searchByTitle("Tital-3");
if(obj != NULL){
Sb->printSlip(obj);
}
obj = Sb->searchByText("Text-4");
if(obj != NULL){
Sb->printSlip(obj);
}
vector<Note *> vec = Sb->searchByTag("tag-3");
if(vec.size() !=0){
for (auto it = vec.begin(); it != vec.end(); ++it){
//cout<<(*it)->Tital << "\t";
Sb->printSlip(*it);
}
}
}
/**temporary function only use for testing*/
void feed_data_for_testing(){
storyBoard *Sb = storyBoard::getInstance();
vector<string> tags ;
int count =INPUT;
for(int i =1;i<=count;i++){
string tital = "Tital-" + std::to_string(i);
string text = "Text-" + std::to_string(i);
tags.clear();
for(int j =1;j<=i;j++){
string tag_ = "tag-" + std::to_string(j);
tags.push_back(tag_);
}
Sb->AddNote(tital,text,tags);
}
}
int main() {
storyBoard *Sb = storyBoard::getInstance();
feed_data_for_testing();
Sb->PrintStoredData(); /*Print all contain data */
cout<<"************* From Here start searching ************"<<endl;
TestCase();
return 0;
}
I think the term design pattern was mistakenly used by your interviewer in place of the term idiom.
A major issue with your code (and could be the cause of rejection) is memory handling using classical c++ idioms:
use of smart pointers to manage the lifetime of your notes
rule of 3 (or 5) to handle copy/assignment(/move) of your StoryBoard object
Note that using smart pointers is only one way of managing the memory. You could as well use other idioms:
arena of notes and referencing notes from the arena inside your hashmaps
...
Once this issue is solved you have minor issues:
returning pointers instead of references, the StoryBoard is the owner of the memory, you should not return a pointer that a caller could inadvertently free.
no const accessors (returning const references)
repetitive code that could be factored
If I am not mistaken in interpreting what the interviewer said, this question should be moved to codereview.stackexhange.com

How to read in a .cfg.txt file

I've been trying to read this file for some time now and tried about everything I could think of. I placed the file in my Products folder and In my resource folder and included (ResourcePath + "File.cfg.txt") and neither worked. I'd appreciate it if someone could tell me what I'm missing and where to put this file to read it in. Again I'm using Xcode and the SFML library with it.
Keys.cfg.txt
Window_close 0:0
Fullscreen_toggle 5:89
Move 9:0 24:38
/////////////////////////////////////////
.CPP
#include "EventManager.h"
using namespace std;
EventManager::EventManager(): m_hasFocus(true){ LoadBindings(); }
EventManager::~EventManager(){
for (auto &itr : m_bindings){
delete itr.second;
itr.second = nullptr;
}
}
bool EventManager::AddBinding(Binding *l_binding){
if (m_bindings.find(l_binding->m_name) != m_bindings.end())
return false;
return m_bindings.emplace(l_binding->m_name, l_binding).second;
}
bool EventManager::RemoveBinding(std::string l_name){
auto itr = m_bindings.find(l_name);
if (itr == m_bindings.end()){ return false; }
delete itr->second;
m_bindings.erase(itr);
return true;
}
void EventManager::SetFocus(const bool& l_focus){ m_hasFocus = l_focus; }
void EventManager::HandleEvent(sf::Event& l_event){
// Handling SFML events.
for (auto &b_itr : m_bindings){
Binding* bind = b_itr.second;
for (auto &e_itr : bind->m_events){
EventType sfmlEvent = (EventType)l_event.type;
if (e_itr.first != sfmlEvent){ continue; }
if (sfmlEvent == EventType::KeyDown || sfmlEvent == EventType::KeyUp){
if (e_itr.second.m_code == l_event.key.code){
// Matching event/keystroke.
// Increase count.
if (bind->m_details.m_keyCode != -1){
bind->m_details.m_keyCode = e_itr.second.m_code;
}
++(bind->c);
break;
}
} else if (sfmlEvent == EventType::MButtonDown || sfmlEvent == EventType::MButtonUp){
if (e_itr.second.m_code == l_event.mouseButton.button){
// Matching event/keystroke.
// Increase count.
bind->m_details.m_mouse.x = l_event.mouseButton.x;
bind->m_details.m_mouse.y = l_event.mouseButton.y;
if (bind->m_details.m_keyCode != -1){
bind->m_details.m_keyCode = e_itr.second.m_code;
}
++(bind->c);
break;
}
} else {
// No need for additional checking.
if (sfmlEvent == EventType::MouseWheel){
bind->m_details.m_mouseWheelDelta = l_event.mouseWheel.delta;
} else if (sfmlEvent == EventType::WindowResized){
bind->m_details.m_size.x = l_event.size.width;
bind->m_details.m_size.y = l_event.size.height;
} else if (sfmlEvent == EventType::TextEntered){
bind->m_details.m_textEntered = l_event.text.unicode;
}
++(bind->c);
}
}
}
}
void EventManager::Update(){
if (!m_hasFocus){ return; }
for (auto &b_itr : m_bindings){
Binding* bind = b_itr.second;
for (auto &e_itr : bind->m_events){
switch (e_itr.first){
case(EventType::Keyboard) :
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key(e_itr.second.m_code))){
if (bind->m_details.m_keyCode != -1){
bind->m_details.m_keyCode = e_itr.second.m_code;
}
++(bind->c);
}
break;
case(EventType::Mouse) :
if (sf::Mouse::isButtonPressed(sf::Mouse::Button(e_itr.second.m_code))){
if (bind->m_details.m_keyCode != -1){
bind->m_details.m_keyCode = e_itr.second.m_code;
}
++(bind->c);
}
break;
case(EventType::Joystick) :
// Up for expansion.
break;
default:
break;
}
}
if (bind->m_events.size() == bind->c){
auto callItr = m_callbacks.find(bind->m_name);
if(callItr != m_callbacks.end()){
callItr->second(&bind->m_details);
}
}
bind->c = 0;
bind->m_details.Clear();
}
}
void EventManager::LoadBindings(){
std::string delimiter = ":";
std::ifstream bindings;
bindings.open("keys.cfg");
if (!bindings.is_open()){ std::cout << "! Failed loading keys.cfg." << std::endl; return; }
std::string line;
while (std::getline(bindings, line)){
std::stringstream keystream(line);
std::string callbackName;
keystream >> callbackName;
Binding* bind = new Binding(callbackName);
while (!keystream.eof()){
std::string keyval;
keystream >> keyval;
int start = 0;
int end = keyval.find(delimiter);
if (end == std::string::npos){ delete bind; bind = nullptr; break; }
EventType type = EventType(stoi(keyval.substr(start, end - start)));
int code = stoi(keyval.substr(end + delimiter.length(),
keyval.find(delimiter, end + delimiter.length())));
EventInfo eventInfo;
eventInfo.m_code = code;
bind->BindEvent(type, eventInfo);
}
if (!AddBinding(bind)){ delete bind; }
bind = nullptr;
}
bindings.close();
}
The problem is that you have to copy the respective files in the bundle, and that only objective-c can provide you the full name of the file on the destination device.
To overcome this, make a .mm-file and place a c++ trampoline function in there, which gives you the full path (see code below).
One pitfall can be that you have to make sure that the config- and text files like "keys.cfg" are actually copied into the bundle. Select the respective file in the project and open the property inspector; make sure that - the respective target in "Target Membership" is checked.
// File: myFileNameProvider.mm
#import <Foundation/Foundation.h>
#include <iostream>
std::string GetTextureFilename(const char *name)
{
NSString *nameAsNSString = [NSString stringWithUTF8String:name];
NSString *fullName = [[NSBundle mainBundle]
pathForResource:nameAsNSString ofType: nil];
if (fullName)
return std::string([fullName UTF8String]);
else
return "";
}
Then, in your CPP-code, declare the signature of std::string GetTextureFilename(const char *name), and before opening the file get the full path by calling it:
// MyCPPFile.cpp
#include <iostream>
#include <fstream>
// declaration:
std::string GetTextureFilename(const char *name);
void myC_Func {
std::string fullPath = GetTextureFilename("keys.cfg");
std::ifstream bindings;
bindings.open(fullPath.c_str());
if (!bindings.is_open()) {
std::cout << "! Failed loading keys.cfg." << std::endl;
}
...
}

Strange behaviour in console program

I have to write a small console program for a developer internship interview and something big and very hard to find is going wrong. I'm supposed to write a program that checks a directory full of binary .dat files for duplicate files.
What I did:
I input a file using stdin from main.cpp and if the directory exists I pass the path on to my fileChecker function which then generates MD5 hashes for all the files in the given directory and then creates a QHash with the file names as key and the hashes as values. I then try to iterate over the QHash using a java-style iterator. When I run the program it crashes completely and I have to choose debug or end program which makes it impossible for me to figure out what's going wrong as QT's debugger doesn't output anything.
My guess is that something is going wrong with my getDuplicates function in fileChecker.cpp as i've never used java-style itterators before to itterate over a QHash. i'm trying to take the first key-value pair and store it in two variables. Then I remove those values from the QHash and try to itterate over the remainder of the QHash using an itterator inside the previous itterator. If anyone has any idea what i'm doing wrong please let me know asap as I need to have this done before monday to get an interview... the code for fileChecker.h and fileChecker.cpp are below please let me know if there's anything more I can add.
Thanks
my code:
main.cpp:
#include "filechecker.h"
#include <QDir>
#include <QTextStream>
#include <QString>
#include <QStringList>
QTextStream in(stdin);
QTextStream out(stdout);
int main() {
QDir* dir;
FileChecker checker;
QString dirPath;
QStringList duplicateList;
out << "Please enter directory path NOTE: use / as directory separator regardless of operating system" << endl;
dirPath = in.readLine();
dir->setPath(dirPath);
if(dir->exists()) {
checker.processDirectory(dir);
duplicateList = checker.getDuplicateList();
}
else if(!(dir->exists()))
out << "Directory does not exist" << endl;
foreach(QString str, duplicateList){
out << str << endl;
}
return 0;
}
fileChecker.h:
#ifndef FILECHECKER_H
#define FILECHECKER_H
#include <QString>
#include <QByteArray>
#include <QHash>
#include <QCryptographicHash>
#include <QStringList>
#include <QDir>
class FileChecker
{
public:
FileChecker();
void processDirectory(QDir* dir);
QByteArray generateChecksum(QFile* file);
QStringList getDuplicateList();
private:
QByteArray generateChecksum(QString fileName);
QHash<QString, QByteArray> m_hash;
};
#endif // FILECHECKER_H
fileChecker.cpp:
#include "filechecker.h"
FileChecker::FileChecker() {
}
void FileChecker::processDirectory(QDir* dir) {
dir->setFilter(QDir::Files);
QStringList fileList = dir->entryList();
for (int i = 0; i < fileList.size(); i++) {
bool possibleDuplicatesFound = false;
QString testName = fileList.at((i));
QFile* testFile;
testFile->setFileName(testName);
foreach(QString s, fileList) {
QFile* possibleDuplicate;
possibleDuplicate->setFileName(s);
if(testFile->size() == possibleDuplicate->size() && testFile->fileName() != possibleDuplicate->fileName()) {
QByteArray md5HashPd = generateChecksum(possibleDuplicate);
m_hash.insert(possibleDuplicate->fileName(), md5HashPd);
possibleDuplicatesFound = true;
fileList.replaceInStrings(possibleDuplicate->fileName(), "");
}
QByteArray md5Hasht = generateChecksum(testFile);
fileList.replaceInStrings(testFile->fileName(), "");
possibleDuplicatesFound = false;
}
}
}
QByteArray FileChecker::generateChecksum(QFile* file) {
if(file->open(QIODevice::ReadOnly)) {
QCryptographicHash cHash(QCryptographicHash::Md5);
cHash.addData(file->readAll());
QByteArray checksum = cHash.result();
return checksum;
}
}
QStringList FileChecker::getDuplicateList() {
QStringList tempList;
QString tempStr;
QString currentKey;
QByteArray currentValue;
QMutableHashIterator<QString, QByteArray> i(m_hash);
do {
while (i.hasNext()){
i.next();
currentKey = i.key();
currentValue = i.value();
tempStr.append("%1 ").arg(currentKey);
if (i.value() == currentValue) {
tempStr.append("and %1").arg(i.key());
i.remove();
}
tempList.append(tempStr);
tempStr.clear();
}
} while (m_hash.size() > 0);
return tempList;
}
Aside from your sad Qt memory management problem, you really don't have to calculate md5 sums of all files.
Just for groups of files of equal size :)
Files with a unique size can be left out. I wouldn't even call this an optimization but simply not doing a potentially absurd amount of unnecessary extra work :)
All Qt Java-style iterators come in "regular" (const) and mutable versions (where it is safe to modify the object you are iterating). See QMutableHashIterator. You're modifying a const iterator; thus, it crashes.
While you're at it, look at the findNext function the iterator provides. Using this function eliminates the need for your second iterator.
Just add i.next() as following.
do {
while (i.hasNext()) {
i.next();
currentKey = i.key();
currentValue = i.value();
tempStr.append(currentKey);
m_hash.remove(currentKey);
QHashIterator<QString, QByteArray> j(m_hash);
while (j.hasNext()) {
if (j.value() == currentValue) {
tempStr.append(" and %1").arg(j.key());
m_hash.remove(j.key());
}
}
tempList.append(tempStr);
tempStr.clear();
}
} while (m_hash.size() > 1);
Some things that stand out:
It's a bad idea to readAll the file: it will allocate a file-sized block on the heap, only to calculate its hash and discard it. That's very wasteful. Instead, leverage the QCryptographicHash::addData(QIODevice*): it will stream the data from the file, only keeping a small chunk in memory at any given time.
You're explicitly keeping an extra copy of the entry list of a folder. This is likely unnecessary. Internally, the QDirIterator will use platform-specific ways of iterating a directory, without obtaining a copy of the entry list. Only the OS has the full list, the iterator only iterates it. You still need to hold the size,path->hash map of course.
You're using Java iterators. These are quite verbose. The C++ standard-style iterators are supported by many containers, so you could easily substitute other containers from e.g. C++ standard library or boost to tweak performance/memory use.
You're not doing enough error checking.
The code seems overly verbose for the little it's actually doing. The encapsulation of everything into a class is probably also a Java habit, and rather unnecessary here.
Let's see what might be the most to-the-point, reasonably performant way of doing it. I'm skipping the UI niceties: you can either call it with no arguments to check in the current directory, or with arguments, the first of which will be used as the path to check in.
The auto & dupe = entries[size][hash.result()]; is a powerful expression. It will construct the potentially missing entries in the external and internal map.
// https://github.com/KubaO/stackoverflown/tree/master/questions/dupechecker-37557870
#include <QtCore>
#include <cstdio>
QTextStream out(stdout);
QTextStream err(stderr);
int check(const QString & path) {
int unique = 0;
// size hash path
QMap<qint64, QMap<QByteArray, QString>> entries;
QDirIterator it(path, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
QCryptographicHash hash{QCryptographicHash::Sha256};
while (it.hasNext()) {
it.next();
auto const info = it.fileInfo();
if (info.isDir()) continue;
auto const path = info.absoluteFilePath();
auto const size = info.size();
if (size == 0) continue; // all zero-sized files are "duplicates" but let's ignore them
QFile file(path); // RAII class, no need to explicitly close
if (!file.open(QIODevice::ReadOnly)) {
err << "Can't open " << path << endl;
continue;
}
hash.reset();
hash.addData(&file);
if (file.error() != QFile::NoError) {
err << "Error reading " << path << endl;
continue;
}
auto & dupe = entries[size][hash.result()];
if (! dupe.isNull()) {
// duplicate
out << path << " is a duplicate of " << dupe << endl;
} else {
dupe = path;
++ unique;
}
}
return unique;
}
int main(int argc, char ** argv) {
QCoreApplication app{argc, argv};
QDir dir;
if (argc == 2)
dir = app.arguments().at(1);
auto unique = check(dir.absolutePath());
out << "Finished. There were " << unique << " unique files." << endl;
}

How to convert XML to JSON in C++?

I came across How to convert XML to JSON in ASP.NET C# (link) and one in javascript (at goessner.net/download/prj/jsonxml/). But have yet to find one in c++ that takes just a string as input (Or a tinyxml node as I'm using that library).
By far, the only specifically designed C++ library that directly converts XML to JSON I found on the Internet is xml2json: https://github.com/Cheedoong/xml2json
Please head to http://json.org. You may find something of interest there. It lists many libraries available for JSON processing in C++ and many other languages.
But for the thing you linked to [pdhcc] you may have to do some work yourself to port it to C++.
Ideally this would be done using XSLT instead writing a translator in C++/C#. You can find some solutions online (for example this one).
However, if you would prefer to do it in C++ then I recommend the jsoncpp library.
Have you looked at http://www.json.org/?
I implemented this function to convert xml to json using jsoncpp & rapidxml libraries, maybe will help someone.
To compile : g++ -o xmltojson main.cpp -ljsoncpp.
Tested on debian 9 only.
#include <set>
#include <string>
#include <string.h>
#include <iostream>
#include <jsoncpp/json/json.h>
#include <rapidxml/rapidxml.hpp>
void parse_node_tree(const rapidxml::xml_node<>* node, Json::Value& parent)
{
Json::Value obj(Json::objectValue);
std::multiset<std::string> array;
for(rapidxml::xml_node<> *child = node->first_node(); child != nullptr; child = child->next_sibling())
{
if(child->type() != rapidxml::node_element)
continue;
array.insert(child->name());
}
if(node->value_size() > 0)
{
obj["#text"] = node->value();
}
bool hasChilds = false;
for(rapidxml::xml_attribute<> *attr = node->first_attribute(); attr != nullptr; attr = attr->next_attribute())
{
hasChilds = true;
obj[attr->name()] = attr->value();
}
for(rapidxml::xml_node<> *child = node->first_node(); child != nullptr; child = child->next_sibling())
{
if(child->type() != rapidxml::node_element)
continue;
hasChilds = true;
Json::Value& next = obj[child->name()];
if(array.count(child->name()) > 1 && !next.isArray())
{
next = Json::arrayValue;
}
parse_node_tree(child, next);
}
// set result.
if(parent.isArray())
{
parent.append(obj);
}
else
{
if(obj.isArray() || hasChilds)
parent = obj;
else
parent = node->value();
}
}
// convert xml string to json
std::string xmltojson(const std::string& xml)
{
char xml_text[xml.size()+1];
memset(xml_text, 0, xml.size());
strncpy(xml_text, xml.c_str(), xml.size());
xml_text[xml.size()] = '\0';
rapidxml::xml_document<> doc;
try
{
doc.parse<0>(xml_text);
}
catch(rapidxml::parse_error& exp)
{
std::cout << exp.what() << std::endl;
return std::string();
}
rapidxml::xml_node<> *node = doc.first_node();
if(node == nullptr)
return std::string();
Json::Value jdoc;
Json::Value& jroot = jdoc[node->name()];
parse_node_tree(node, jroot);
Json::FastWriter fast_writer;
return fast_writer.write(jdoc);
}
int main(int argc, char** argv)
{
// xml
std::string test_xml = "<anagrafica><testata><nomemercato id='007'>Mercato di test</nomemercato><data>Giovedi 18 dicembre 2003 16.05.29</data></testata><record><codice_cliente>5</codice_cliente><rag_soc>Miami American Cafe</rag_soc><codice_fiscale>IT07654930130</codice_fiscale><indirizzo tipo='casa'>Viale Carlo Espinasse 5, Como</indirizzo><num_prodotti>13</num_prodotti></record><record><codice_cliente>302</codice_cliente><rag_soc>Filiberto Gilardi</rag_soc><codice_fiscale>IT87654770157</codice_fiscale> <indirizzo tipo='ufficio'>Via Biancospini 20, Messina</indirizzo><num_prodotti>8</num_prodotti> </record><record><codice_cliente>1302</codice_cliente><rag_soc>Eidon</rag_soc><codice_fiscale>IT887511231</codice_fiscale><indirizzo tipo='ufficio'>Via Bassini 17/2, Milano</indirizzo><num_prodotti>18</num_prodotti> </record><record><codice_cliente>202</codice_cliente><rag_soc>SkillNet</rag_soc><codice_fiscale>IT887642131</codice_fiscale><indirizzo tipo='ufficio'>Via Chiasserini 11A, Milano</indirizzo><num_prodotti>24</num_prodotti></record><record><codice_cliente>12</codice_cliente><rag_soc>Eidon</rag_soc><codice_fiscale>IT04835710965</codice_fiscale><indirizzo tipo='casa'>Via Cignoli 17/2, Roma</indirizzo><num_prodotti>1112</num_prodotti></record><record><codice_cliente>5</codice_cliente><rag_soc>Miami American Cafe</rag_soc><codice_fiscale>IT07654930130</codice_fiscale><indirizzo tipo='casa'>Viale Carlo Espinasse 5, Como</indirizzo><num_prodotti>13</num_prodotti></record><record><codice_cliente>302</codice_cliente><rag_soc>Filiberto Gilardi</rag_soc><codice_fiscale>IT87654770157</codice_fiscale><indirizzo tipo='ufficio'>Via Biancospini 20, Messina</indirizzo><num_prodotti>8</num_prodotti></record><record><codice_cliente>1302</codice_cliente><rag_soc>Eidon</rag_soc><codice_fiscale>IT887511231</codice_fiscale><indirizzo tipo='ufficio'>Via Bassini 17/2, Milano</indirizzo><num_prodotti>18</num_prodotti></record><record><codice_cliente>202</codice_cliente><rag_soc>SkillNet</rag_soc><codice_fiscale>IT887642131</codice_fiscale><indirizzo tipo='ufficio'>Via Chiasserini 11A, Milano</indirizzo><num_prodotti>24</num_prodotti></record><record><codice_cliente>202</codice_cliente><rag_soc>SkillNet</rag_soc><codice_fiscale>IT887642131</codice_fiscale><indirizzo tipo='ufficio'>Via Chiasserini 11A, Milano</indirizzo><num_prodotti>24</num_prodotti></record><record><codice_cliente>12</codice_cliente><rag_soc>Eidon</rag_soc><codice_fiscale>IT04835710965</codice_fiscale><indirizzo tipo='casa'>Via Cignoli 17/2, Roma</indirizzo><num_prodotti>1112</num_prodotti></record></anagrafica>";
// convert
std::string json = xmltojson(test_xml);
// log
std::cout << test_xml << std::endl;
std::cout << std::endl;
std::cout << json << std::endl;
return 0;
}