user3490777 user3490777 - 2 months ago 47
C++ Question

Reading and writing to the same file fstream

I would like to update existing json file.

I prepared this code:

#include "json.hpp"
#include <iostream>
#include <fstream>
#include <string>

using json = nlohmann::json;


void readAndWriteDataToFile(std::string fileName) {
std::fstream fs;
fs.open(fileName.c_str(), std::ios::in|std::ios::out);

if (fs.is_open())
{
json json_data(fs);
std::cout << "Operation successfully performed\n";
std::cout << json_data.at("Number") << std::endl;
std::cout << json_data.at("Test") << std::endl;
std::cout << json_data.at("Foo") << std::endl;

json_data.at("Foo") = 46.32;

std::cout << json_data.at("Foo") << std::endl;

std::string json_content = json_data.dump(3);
fs.flush();
fs.write(json_content.data(), json_content.size());
std::cout << "Done" << std::endl;

fs.close();
}
else
{
std::cout << "Error opening file";
}
}

int main()
{
std::string fileName = "C:/new/json1.json";
readAndWriteDataToFile(fileName);
}


And file is not updated..

This is example json file:

{
"Foo": 51.32,
"Number": 100,
"Test": "Test1"
}


Logs from program:

Operation successfully performed
100
"Test1"
51.32
46.32
Done


Looks like everythink works as expected...

If I change fstream to ifstream to read and later ofstream to write it's working...

#include "json.hpp"
#include <iostream>
#include <fstream>
#include <string>

using json = nlohmann::json;


std::string getDataFromFile(std::string fileName) {
std::ifstream ifs(fileName);
json json_data(ifs);

if (ifs.is_open())
{
std::cout << "Operation successfully performed\n";
std::cout << json_data.at("Number") << std::endl;
std::cout << json_data.at("Test") << std::endl;
std::cout << json_data.at("Foo") << std::endl;

json_data.at("Foo") = 51.32;

std::cout << json_data.at("Foo") << std::endl;

std::string json_content = json_data.dump(3);
ifs.close();
return json_content;
}
else
{
std::cout << "Error opening file";
return "";
}
}

bool writeDataToFile(std::string fileName, std::string dataToWrite) {
std::ofstream ofs;
ofs.open(fileName);
if (ofs.is_open())
{
ofs.write(dataToWrite.data(), dataToWrite.size());
ofs.close();
std::cout << "Done" << std::endl;
return true;
}
else
{
std::cout << "Error opening file";
return false;
}
}

int main()
{
std::string fileName = "C:/new/json1.json";
std::string dataFromFile = getDataFromFile(fileName);
std::cout << dataFromFile << std::endl;

if ("" != dataFromFile) {
writeDataToFile(fileName, dataFromFile);
}
}


I tried use debugger and as I see I have wrong data in basic_ostream object... but I dont know why, I use data from string with corrected (updated data).
Any idea what is wrong :-) ?

Answer

You have a few problems here.

First the command json json_data(fs); reads to the end of the file setting the EOF flag. The stream will stop working until that flag is cleared.

Second the file pointer is at the end of the file. If you want to overwrite the file you need to move back to the beginning again:

if (fs.is_open())
{
    json json_data(fs); // reads to end of file
    fs.clear(); // clear flag
    fs.seekg(0); // move to beginning

Unfortunately that still doesn't fix everything because if the file you write back is smaller than the one you read in there will be some of the old data tagged to the end of the new data:

    std::cout << "Operation successfully performed\n";
    std::cout << json_data.at("Number") << std::endl;
    std::cout << json_data.at("Test") << std::endl;
    std::cout << json_data.at("Foo") << std::endl;

    json_data.at("Foo") = 4.32; // what if new data is smaller?

Json file:

{
   "Foo": 4.32, // this number is smaller than before
   "Number": 100,
   "Test": "Test1"
}} // whoops trailing character from previous data!!

In this situation I would simply open one file for reading then another for writing, its much less error prone and expresses the intention to overwrite everything.

Something like:

#include "json.hpp"
#include <iostream>
#include <fstream>
#include <string>

using json = nlohmann::json;

void readAndWriteDataToFile(std::string fileName) {

    json json_data;

    // restrict scope of file object (auto-closing raii)
    if(auto fs = std::ifstream(fileName))
    {
        json_data = json::parse(fs);

        std::cout << "Operation successfully performed\n";
        std::cout << json_data.at("Number") << std::endl;
        std::cout << json_data.at("Test") << std::endl;
        std::cout << json_data.at("Foo") << std::endl;
    }
    else
    {
        throw std::runtime_error(std::strerror(errno));
    }

    json_data.at("Foo") = 4.32;
    std::cout << json_data.at("Foo") << std::endl;
    std::string json_content = json_data.dump(3);

    if(auto fs = std::ofstream(fileName))
    {
        fs.write(json_content.data(), json_content.size());
        std::cout << "Done" << std::endl;
    }
    else
    {
        throw std::runtime_error(std::strerror(errno));
    }

}

int main()
{
    try
    {
        std::string fileName = "C:/new/json1.json";
        readAndWriteDataToFile(fileName);
    }
    catch(std::exception const& e)
    {
        std::cerr << e.what() << '\n';
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}