Reading and writing to the same file fstream - c++

I would like to update existing json file.
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...
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 :-) ?

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;
}

Related

LLVM YAML API cannot read in yaml file "error: not a sequence"

This question is a continuation of my previous question.
error: implicit instantiation of undefined template 'llvm::yaml::MissingTrait
I am working on a project which uses the LLVM YAML I/O library. This is the documentation/tutorial that I am following:
https://www.llvm.org/docs/YamlIO.html
I have created a small program that would read in a yaml file into objects in memory. Then it would print those objects. Finally it would write the objects out into a different output file.
First it parses the command line arguments. Then it creates the input reader. Then it creates the YAML Input object, which is used to read the *.yaml file in, and parse it. It is in the parsing step that I am having an error. Supposing if parsing the input from the *.yaml file was successful, the data will get stored into the DocType myDoc;. Then it prints all the Person objects stored in that std::vector. An overloaded operator<<() prints each element. Then it creates the output writer, creates the YAML Output, and writes myDoc into the file output_file.yaml.
The goal of this program is to demonstrate reading and writing a *.yaml file with LLVM YAML I/O. It successfully writes the output file, but it cannot read the input file.
Now suppose that instead of filling myDoc with elements from the yin, I would be manually adding elements instead. So I activate the code that push_back each element, and I disable the code that reads the input from the yin into memory.
DocType myDoc;
///*
myDoc.push_back(Person("Tom", 8));
myDoc.push_back(Person("Dan", 7));
myDoc.push_back(Person("Ken"));
//*/
/* Reading input into the memory */
/*
yin >> myDoc;
if (error_code errc = yin.error()) {
errs() << "error parsing YAML input from file " << InputFile << '\n';
errs() << errc.message() << '\n';
return EXIT_FAILURE;
} else {
outs() << "parsing YAML input from file " << InputFile << '\n';
}
*/
In that case, the programs works fine. The myDoc is initialized with those elements, and then it prints each element to the stdout. Then in creates the output writer, creates the YAML Output, and writes the myDoc into the output_file.yaml.
Here is what the output file looks like when it is written:
---
- name: Tom
hat-size: 8
- name: Dan
hat-size: 7
- name: Ken
...
I copy the output file into the input file, for testing the input functionality of the program.
cp output_file.yaml input_file.yaml
Then I deactivate the code which manually fills the myDoc, and I activate the code which fills the myDoc from the yin.
DocType myDoc;
/*
myDoc.push_back(Person("Tom", 8));
myDoc.push_back(Person("Dan", 7));
myDoc.push_back(Person("Ken"));
*/
/* Reading input into the memory */
///*
yin >> myDoc;
if (error_code errc = yin.error()) {
errs() << "error parsing YAML input from file " << InputFile << '\n';
errs() << errc.message() << '\n';
return EXIT_FAILURE;
} else {
outs() << "parsing YAML input from file " << InputFile << '\n';
}
//*/
After that the code no longer works. If I try to provide that same input_file.yaml to the application, I get a bug. LLVM YAML I/O fails to parse the *.yaml file and prints an error! It's weird because this is the exact format that this same LLVM YAML I/O was outputting into the file.
./yaml_project --input-file=input_file2.yaml --output-file=output_file.yaml
opening input file input_file2.yaml
reading input file input_file2.yaml
input_file2.yaml:1:1: error: not a sequence
-
^
error parsing YAML input from file input_file2.yaml
Invalid argument
I cannot find why is it refusing to accept well formatted YAML code from the input file. If anyone knows how to fix this bug, please help me.
Here is the full listing of my code:
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/YAMLTraits.h"
#include "llvm/Support/YAMLParser.h"
#include "llvm/Support/ErrorOr.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/CommandLine.h"
#include <cstdlib> /* for EXIT_FAILURE */
#include <string> /* for std::string */
#include <vector> /* for std::vector */
#include <system_error> /* for std::error_code */
using std::string;
using std::vector;
using std::error_code;
using llvm::outs;
using llvm::errs;
using llvm::raw_ostream;
using llvm::raw_fd_ostream;
using llvm::ErrorOr;
using llvm::MemoryBuffer;
using llvm::yaml::ScalarEnumerationTraits;
using llvm::yaml::MappingTraits;
using llvm::yaml::IO;
using llvm::yaml::Input;
using llvm::yaml::Output;
using llvm::cl::opt;
using llvm::cl::desc;
using llvm::cl::ValueRequired;
using llvm::cl::OptionCategory;
using llvm::cl::ParseCommandLineOptions;
/* Command line options description: */
// Apply a custom category to all command-line options so that they are the
// only ones displayed.
// The category tells the CommonOptionsParser how to parse the argc and argv.
OptionCategory yamlCategory("yaml_project options");
opt<string> InputFile("input-file", desc("The input YAML file"), ValueRequired);
opt<string> OutputFile("output-file", desc("The output YAML file"), ValueRequired);
struct Person {
string name;
int hatSize;
Person(string name = "", int hatSize = 0)
: name(name), hatSize(hatSize) {}
};
raw_ostream& operator<<(raw_ostream& os, const Person& person) {
os << "{ " << person.name;
if (person.hatSize)
os << " , " << person.hatSize;
os << " }";
return os;
}
template <>
struct MappingTraits<Person> {
static void mapping(IO& io, Person& info) {
io.mapRequired("name", info.name);
io.mapOptional("hat-size", info.hatSize, 0);
}
};
typedef vector<Person> DocType;
LLVM_YAML_IS_SEQUENCE_VECTOR(Person)
int main(int argc, const char **argv) {
/* Command line parsing: */
ParseCommandLineOptions(argc, argv);
if (InputFile.empty()) {
errs() << "No input file specified\n";
return EXIT_FAILURE;
}
if (OutputFile.empty()) {
errs() << "No output file specified\n";
return EXIT_FAILURE;
}
/* Create the input reader */
auto reader = MemoryBuffer::getFile(InputFile, true);
if (error_code errc = reader.getError()) {
errs() << "error opening input file " << InputFile << '\n';
errs() << errc.message() << '\n';
// MemoryBuffer does not need to be closed
return EXIT_FAILURE;
} else {
outs() << "opening input file " << InputFile << '\n';
}
/* Create the YAML Input */
// dereference once to strip away the llvm::ErrorOr
// dereference twice to strip away the std::unique_ptr
Input yin(**reader);
if (error_code errc = yin.error()) {
errs() << "error reading input file " << InputFile << '\n';
outs() << errc.message() << '\n';
// MemoryBuffer does not need to be closed
return EXIT_FAILURE;
} else {
outs() << "reading input file " << InputFile << '\n';
}
DocType myDoc;
/*
myDoc.push_back(Person("Tom", 8));
myDoc.push_back(Person("Dan", 7));
myDoc.push_back(Person("Ken"));
*/
/* Reading input into the memory */
///*
yin >> myDoc;
if (error_code errc = yin.error()) {
errs() << "error parsing YAML input from file " << InputFile << '\n';
errs() << errc.message() << '\n';
return EXIT_FAILURE;
} else {
outs() << "parsing YAML input from file " << InputFile << '\n';
}
//*/
for (const Person& element : myDoc)
outs() << element << '\n';
/* Create the output writer */
error_code errc;
raw_fd_ostream writer(OutputFile, errc);
if (errc) {
errs() << "error opening output file " << OutputFile << '\n';
errs() << errc.message() << '\n';
writer.close();
return EXIT_FAILURE;
} else {
outs() << "opening output file " << OutputFile << '\n';
}
/* Create the YAML Output */
Output yout(writer);
/* Writing output into file */
yout << myDoc;
outs() << "writing YAML output into file " << OutputFile << '\n';
writer.close();
return EXIT_SUCCESS;
}
The problem is in the line
auto reader = MemoryBuffer::getFile(InputFile, true);
with llvm-12 change the line to
auto reader = MemoryBuffer::getFile(InputFile);
it should work.

Having trouble opening a json file in C++

I am trying to open a json file that I will be working with in C++. Code that I have used successfully before fails to open the file. I am using Visual Studio 2017 on Windows 10 Pro with JSON for Modern C++ version 3.5.0.
I have a very simple function, which is supposed to open a file as input to a json object. It appears to open the file, but aborts when writing it to the json object. Originally the file to be opened was in another directory, but I moved it into the same directory as the executable while testing...but it didn't help.
Here is the very short function that fails:
json baselineOpenAndRead(string fileName) //passed string used for filename
{
json baseJObject;
cout << "we have a baseJObject" << endl;
//ifstream inFileJSON("test_file.json"); // Making this explicit made no difference
ifstream inFileJSON;
inFileJSON.open("test_file.json", ifstream::in);
cout << "we have opened json inFileJSON" << endl; // get here
inFileJSON >> baseJObject;
cout << " Can direct inFileJSON into baseJObject" << endl; //never get here; the app aborts.
inFileJSON.close();
return baseJObject;
}
This seems basically identical to the example on the nlohmann site:
// read a JSON file
std::ifstream i("file.json");
json j;
i >> j;
I just expected this to open the json file, load it into the object, and return the object. Instead, it just quits.
Thanks for any thoughts...i.e., what am I doing wrong? (I'm going to ignore that it worked before...maybe I missed something).
--Al
As requested, here is a minimal reproducible example, but it will require nlohmann's json.hpp in order to compile:
#include <iostream>
#include <fstream>
#include "json.hpp"
using json = nlohmann::json;
using namespace std;
string fileName;
json baselineOpenAndRead(string);
int main(int argC, char *argV[])
{
json baseJObject;
if (argC != 2) // check to make sure proper number of arguments are given.
{
cout << "\n\nFilename needed...";
exit(1); // number of arguments is wrong - exit program
}
else
{
fileName = argV[1];
baseJObject = baselineOpenAndRead(fileName); // opens and reads the Base Line JSON file
cout << "baseJObject returned" << endl;
}
return 0;
}
json baselineOpenAndRead(string fileName) //
{
cout << "File name: " << fileName << endl;
json baseJObject;
cout << "we have a baseJObject" << endl;
ifstream inFileJSON(fileName);
if (inFileJSON.is_open())
{
cout << "file open..." << endl;
if (nlohmann::json::accept(inFileJSON))
{
cout << "valid json" << endl;
try { inFileJSON >> baseJObject; }
catch (const std::exception &e) { std::cout << e.what() << '\n'; throw; }
}
else
{
cout << "not valid json" << endl;
}
}
else
{
cout << "file not really open" << endl;
}
inFileJSON >> baseJObject;
cout << " We can echo inFileJSON into baseJObject" << endl;
inFileJSON.close();
return baseJObject;
}
I tested it with this json file:
{
"people": [{
"name": "Scott",
"website": "stackabuse.com",
"from": "Nebraska"
},
{
"name": "Larry",
"website": "google.com",
"from": "Michigan"
},
{
"name": "Tim",
"website": "apple.com",
"from": "Alabama"
}
]
}
When I run this passing it the json above as data.json, I get the following output and then it quits:
./Test_json data.json
File name: data.json
we have a baseJObject
file open...
valid json
[json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal
Without the try, it just quits. It never gets past inFileJSON >> baseJObject;
Another try that seems to work, but why?
OK. I tried this with the same main (the only changes are in the function):
json baselineOpenAndRead(string fileName) //
{
json baseJObject;
string filePath = "../baselines/" + fileName;
cout << "filePath: " << filePath << endl;
ifstream inFileJSON(fileName);
//baseJObject = json::parse(inFileJSON);
inFileJSON >> baseJObject;
cout << baseJObject << std::endl;
return baseJObject;
}
This looks basically the same to me. I tried making it ifstream inFileJSON(fileName.c_str()) on both the original and in this one. The original continued to fail, this one continued to work. Sorry this is getting so long, but I can't get decent formatting out of comments... Should I just try answering my own question instead?
I think I've got this. I believe my initial problem was caused by an errant ',' in one of my json test files. Subsequently, the if (inFileJSON.is_open) worked, but the if (nlohmann::json::accept(inFileJSON) was failing and causing the same (or perhaps a similar) error. I thought that I needed the c_str() for file paths outside of the executable's directory, but it doesn't seem to make a difference one way or the other. I took out the accept(), and this code seems to work consistently:
json baselineOpenAndRead(string fileName) //
{
json baseJObject;
cout << "we have a baseJObject" << endl;
string filePath = "../baselines/" + fileName;
cout << "filePath: " << filePath << endl;
//ifstream inFileJSON(filePath.c_str());
ifstream inFileJSON(filePath);
if (inFileJSON.is_open())
{
cout << "File is open." << endl;
inFileJSON >> baseJObject;
cout << baseJObject << std::endl;
inFileJSON.close();
return baseJObject;
}
else
{
cout << "File not open." << endl;
exit(1);
}
}
Thanks to everyone for your help. I appreciate it.
--Al

std::ofstream : Writing to a file using append and out flags

Below is a simple class which attempts to write an integer to a file. The mode of writing the file is to append characters at the end of the file (In this mode, file should be created if it doesn't exist)
#include <iostream>
#include <fstream>
class TestFileStream
{
private:
std::ofstream* _myFileStream;
bool isFileOpen;
public:
TestFileStream():isFileOpen(false)
{
_myFileStream = new std::ofstream("TestFile.txt", std::ios_base::out | std::ios_base::app );
isFileOpen = _myFileStream->is_open();
if( !isFileOpen )
{
std::cout << "Unable to open log file" << std::endl;
std::cout << "Good State: " << _myFileStream->good() <<std::endl;
std::cout << "Eof State: " << _myFileStream->eof() <<std::endl;
std::cout << "Fail State: " << _myFileStream->fail() <<std::endl;
std::cout << "Bad State: " << _myFileStream->bad() <<std::endl;
}
else
{
std::cout << "Opened log file" << std::endl;
}
}
~TestFileStream()
{
_myFileStream->close();
delete _myFileStream;
_myFileStream = nullptr;
}
void WriteFile( unsigned number )
{
if ( isFileOpen )
{
(*_myFileStream) << "Number: " << number << std::endl;
}
}
};
int main()
{
// Number of iterations can be multiple.
// For testing purpose, only 1 loop iteration executes
for( unsigned iter = 1; iter != 2; ++iter )
{
TestFileStream fileWriteObj;
fileWriteObj.WriteFile( 100+iter );
}
return 0;
}
When I execute the above code, I get following log output:
Unable to open log file
Good State: 0
Eof State: 0
Fail State: 1
Bad State: 0
This seems like trivial task, but I am not able to find out whats causing the failure. Note that this question is most likely related to the following question
Just to summarize the comments, there is nothing wrong about the code you posted (apart from the rather unconventional new ostream ;) )
Note however that opening files may fail for a number of reasons (Permissions, file in use, disk unavailable, the file does not exist, the file exists...). That is why you must always test it.
If you tried to run the above code in an online emulator, then chances are file IO is disabled. Which would explain why you get that the streams fail-bit is set.

File not opening with C++ fstream even with full path

string mapFile;
cout << "Enter the file name : ";
cin >> mapFile;
ifstream mapfh;
mapfh.open(mapFile.c_str());
if(mapfh.is_open()) { ... }
else //if board file did not open properly
{
throw;
}
mapfh.close();
I am compiling with g++ in the command line. Whenever I put a file input (even with a full path i.e. /User/...etc./file.txt) it throws an error. I know the input is good, but for whatever reason the open always fails.
This isn't fully portable, but you'll get a more informed output if you interpret the errno,
#include <cerrno>
#include <cstring>
...
if(mapfh.is_open()) { ... }
else //if board file did not open properly
{
std::cout << "error: " << strerror(errno) << std::endl;
throw;
}
And if your policy is to communicate the errors as exceptions then use iostreams native support for the exceptions:
ifstream mapfh;
mapfh.exceptions(std::ios::failbit);
try {
mapfh.open(mapFile.c_str());
...
mapfh.close();
} catch (const std::exception& e) {
std::cout << e.what() << " : " << std::strerror(errno) << std::endl;
}

std::ifstream setting fail() even when no error

Using GCC 4.7.3 on Cygwin 1.7.24. Compiler options include: -std=gnu++11 -Wall -Wextra
I am working on a command line application and I needed to be able to load and save a set of strings so I wrote a quick wrapper class around std::set to add load and save methods.
// KeySet.h
#ifndef KEYSET_H
#define KEYSET_H
#include <cstdlib>
#include <sys/stat.h>
#include <cerrno>
#include <cstring>
#include <string>
#include <set>
#include <iostream>
#include <fstream>
inline bool file_exists (const std::string& filename)
{
/*
Utility routine to check existance of a file. Returns true or false,
prints an error and exits with status 2 on an error.
*/
struct stat buffer;
int error = stat(filename.c_str(), &buffer);
if (error == 0) return true;
if (errno == ENOENT) return false;
std::cerr << "Error while checking for '" << filename << "': " << strerror(errno) << std::endl;
exit (2);
}
class KeySet
{
private:
std::string filename;
std::set<std::string> keys;
public:
KeySet() {}
KeySet(const std::string Pfilename) : filename(Pfilename) {}
void set_filename (const std::string Pfilename) {filename = Pfilename;}
std::string get_filename () {return filename;}
auto size () -> decltype(keys.size()) {return keys.size();}
auto cbegin() -> decltype(keys.cbegin()) {return keys.cbegin();}
auto cend() -> decltype(keys.cend()) {return keys.cend();}
auto insert(const std::string key) -> decltype(keys.insert(key)) {return keys.insert(key);}
void load ();
void save ();
};
void KeySet::load ()
{
if (file_exists(filename)) {
errno = 0;
std::ifstream in (filename, std::ios_base::in);
if (in.fail()) {
std::cerr << "Error opening '" << filename << "' for reading: " << strerror(errno) << std::endl;
exit (2);
}
std::string token;
if (token.capacity() < 32) token.reserve(32);
while (in >> token) keys.insert(token);
if (!in.eof()) {
std::cerr << "Error reading '" << filename << "': " << strerror(errno) << std::endl;
exit (2);
}
in.clear(); // need to clear flags before calling close
in.close();
if (in.fail()) {
std::cerr << "Error closing '" << filename << "': " << strerror(errno) << std::endl;
exit (2);
}
}
}
void KeySet::save ()
{
errno = 0;
std::ofstream out (filename, std::ios_base::out);
if (out.fail()) {
std::cerr << "Error opening '" << filename << "' for writing: " << strerror(errno) << std::endl;
exit (2);
}
for (auto key = keys.cbegin(), end = keys.cend(); key != end; ++key) {
out << *key << std::endl;
}
out.close();
if (out.fail()) {
std::cerr << "Error writing '" << filename << "': " << strerror(errno) << std::endl;
exit (2);
}
}
#endif
//
Here's a quick program to test the load method.
// ks_test.cpp
#include "KeySet.h"
int main()
{
KeySet test;
std::string filename = "foo.keys.txt";
test.set_filename(filename);
test.load();
for (auto key = test.cbegin(), end = test.cend(); key != end; ++key) {
std::cout << *key << std::endl;
}
}
The data file just has "one two three" in it.
When I go to run the test program, I get the following error from my test program:
$ ./ks_test
Error closing 'foo.keys.txt': No error
Both cppreference.com and cplusplus.com say that the close method should set the fail bit on error. The save method works fine, and the load method works correctly if I comment out the error check after the close. Should this really work or have I misunderstood how close is supposed to work? Thanks in advance.
Edited to clarify, fix typo's and adjust code per Joachim Pileborg's and Konrad Rudolph's comments.
Edited to add solution to the code.
You have two errors here: The first is about how you do your reading, more specifically the loop for reading. The eof flag will not be set until after you tried to read and the read failed. Instead you should do like this:
while (in >> token) { ... }
Otherwise you will loop one time to many and try to read beyond the end of the file.
The second problem is the one you notice, and it depends on the the first problem. Since you try to read beyond the end of the file, the stream will set failbit causing in.fail() to return true even though there is no real error.
As it turns out, the close method for ifstream (and I assume all other IO objects) DOES NOT clear the error flags before closing the file. This means you need to add an explicit clear() call before you close the stream after end of file if you are checking for errors during the close. In my case, I added in.clear(); just before the in.close(); call and it is working as I expect.