0

I am trying to add elements into a .json file between [] as last.

How can I move the cursor to add elements between [...] with efficiently with std::ofstream?

I have tried several open modes but there are strange things. First I created this question about not able to use the file streaming for read and write because of the overwrite issue.

    #include <iostream>
    #include <fstream>

    int main ()
    {
        char errmsg[2048];
        std::ofstream ostream;
    
        ostream.exceptions(std::ios_base::badbit);

        try
        {
            ostream.open("LS22731.json", std::fstream::ate | std::fstream::in);
            strerror_s(errmsg, 2048, errno);
            std::cout << "Error (" << errno << "): " << errmsg << std::endl;
            if (ostream && ostream.is_open())
            {
                auto ppos = ostream.tellp();
                std::streampos sub = 1; // 
                std::cout << "Tellp: " << ppos << std::endl; // Always show zero but file has large data
                if (ppos > 1)
                    ostream.seekp(ppos - sub) << "aa";

                ppos = ostream.teelp();
                std::cout << "New tellp: " << ppos << std::endl;
                ostream.close();
            }
        }
        catch (std::ios_base::failure& fb)
        {
            std::cout << "Failure: " << fb.what() << std::endl;
            char errmsg[2048];
            strerror_s(errmsg, 2048, errno);
            std::cout << "Error (" << errno << "): " << errno << std::endl;
        }
    }

I searched about open modes then I found this but is it good to open file with both mode std::fstream::ate | std::fstream::in together for std::ofstream? And when I open the file with std::fstream::out mode it is rewriting so deleting whole document,

  • std::fstream::out: Delete all contents of the file (overwrite)
  • std::fstream::app: Cannot move the cursor with seekp
  • std::fstream::ate: Delete all contents of the file (overwrite)
  • std::fstream::binary: Delete all contents of the file (overwrite)
  • std::fstream::ate | std::fstream::app: Cannot move the cursor with seekp
  • std::fstream::ate | std::fstream::out: Delete all contents of the file (overwrite)
  • std::fstream::ate | std::fstream::in: Can move the cursor but not insert delete all after.

I don't want to use c FILE.

Nimantha
  • 6,405
  • 6
  • 28
  • 69
Orkun Kasapoglu
  • 133
  • 2
  • 13
  • 1
    I must say that you go about solving this in a pretty roundabout kind of way. Use a `JSON` library (like [JSON for Modern C++](https://github.com/nlohmann/json) or [RapidJSON](https://github.com/Tencent/rapidjson/) to name a few) to read the file into memory, manipulate the content and then dump the result to file. – Ted Lyngmo Mar 25 '21 at 12:29
  • 1
    What about reading from one file and outputting to another. Or depending on filesize, read the file to a string stream, and input your things where you want them and then overwrite the original file. But as @TedLyngmo, writes. If the question is about how to do it _properly_ you should probably use a lib :P – Lasersköld Mar 25 '21 at 12:35
  • 1
    Thank you for comment @TedLyngmo I'll try it. – Orkun Kasapoglu Mar 25 '21 at 12:50
  • 1
    Thank you for comment @Lasersköld it is a big file and getting bigger in the time. It is a machine's IoT data. – Orkun Kasapoglu Mar 25 '21 at 12:51
  • 1
    @OrkunKasapoglu You're welcome! If your file is big and the memory in the IoT machine is limited you may want to _not_ read the whole document into memory and instead use an event driven approach, like using the JSON library's SAX-like interface. – Ted Lyngmo Mar 25 '21 at 13:12
  • 1
    @TedLyngmo thanks again I'll look at it. – Orkun Kasapoglu Mar 25 '21 at 13:53
  • Files are not organized in a way that makes it easy to insert new data in the middle; there's no efficient way to move the existing contents down. You might have better luck with a database. – Mark Ransom Mar 25 '21 at 14:36

2 Answers2

1

Well JSON files are err... sequential text files. That means that the file contains a stream of bytes representing the JSON content. And AFAIK, no filesystem has provision for inserting data in the middle of a sequential file. The foolproof way is:

  • copy up to the insertion point to a temp file
  • write the new data
  • add the remaining data from the original file
  • rename the old file to a backup name
  • rename the temp file with the original name
  • (optionaly) remove the backup file

The brave way is to move the second part up by chunks starting from the end to create an emply place to put the data write the new data in that place, and pray all along the operation for no problem in the middle because the file would be irremediably corrupted.

Those 2 ways can process files of arbitrary sizes. For small files, you could load everything in memory, write the new data at the insertion point and rewrite the remaining data after the new data. You just need to use a default fstream and use neither ate nor trunc. out does not mean deleting all the file content. You simply replace the original bytes at the place where you write.

So you should use:

ostream.open("LS22731.json", std::fstream::out | std::fstream::in);

Then you:

  • read up to your insertion point and discard the data
  • note the position with tellp
  • read the end of file and save it
  • go to the insertion point
  • write the new data
  • write the saved data
  • close the stream

Here is an adaptation of the previous algorithm. The cautious points as:

  • you must use a fstream with std::fstream::out | std::fstream::in mode to be able to read and write a file. The file must exist and you will be initially positioned at the beginning of the file
  • to reliably be able to compute positions, you must open the file in binary mode (std::fstream::binary)(should be possible in text mode but I could not find my way...)

Here is a close adaptation of your code: it opens the file, search for the first closing bracket (]), and inserts ,"h" before to simulate adding a value into a list.

...
std::fstream ostream;

ostream.exceptions(std::ios_base::badbit);

try
{
    // use binary mode to ba able to relyably seek the file.
    ostream.open("LS22731.json",
                 std::fstream::out | std::fstream::in | std::fstream::binary);
    strerror_s(errmsg, 2048, errno);
    std::cout << "Error (" << errno << "): " << errmsg << std::endl;
    if (ostream && ostream.is_open())
    {
        std::streampos ppos;
        // search the first ]
        ostream.ignore(std::numeric_limits<std::streamsize>::max(), ']');
        // we want to insert just before it
        ppos = ostream.tellg() - std::streampos(1);
        ostream.seekg(ppos);    // prepare to read from the ]
        std::string old = "", tmp;
        // save end of file, starting at the ]
        while (std::getline(ostream, tmp)) {
            old += tmp + "\n";
        }
        ostream.clear();                    // clear eof indicator
        ostream.seekp(ppos, std::ios::beg); // go back to the insertion point
        ostream << ",\"h\"";                // add some data
        ostream << old;                     // add the remaining of the original data
        ostream.close();
    }
...

Disclaimers:

  • DO NOT PRETEND I ADSISED YOU THIS WAY. If there is a problem in the middle of processing, the file will be irremediately corrupted.
  • it will fail miserabily if a text field contains a closing bracket, because it is not a JSON parser
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • Thanks for answer. When I use `std::fstream::out | std::fstream::in` together, won't it overwrite my file? Because when I use `std::fstream::out` the `tellp()` returns 0 for the first open and file size goes directly 0KB in the file explorer. – Orkun Kasapoglu Mar 25 '21 at 14:18
  • I have just tested it, and could rewrite only 2 byte in a 16 bytes file. – Serge Ballesta Mar 25 '21 at 14:32
  • Hey Serge, thanks for test but I'm not able to seek to last position when I try with `std::fstream::in |std::fstream::out` together via `std::fstream`. I am able to insert data without overwriting but not to correct place in the file. Did you test with seeking the cursor? – Orkun Kasapoglu Mar 26 '21 at 09:14
  • 1
    @OrkunKasapoglu: Seeking and rewriting a text file is indeed possible (see code in my edit). But again I strongly advise you against that: it is not easy in C++ for a reason... – Serge Ballesta Mar 26 '21 at 12:44
  • Thanks for detailed edit and answer @serge-ballesta. I tried your advice but it almost costs 2secs when there are almost 33000000 characters in a file and it is growing up. When I use `std::fstream::ate | std::fstream::in | std::fstream::out` with a `std::fstream` it costs only 7msecs. I have measured times for `open/add/close` processes with 1000 times and took mean of total times. – Orkun Kasapoglu Mar 29 '21 at 11:34
  • @OrkunKasapoglu: if you want to add something in a middle of a file of 33 Mo, you will have to read 33 Mo and write an average of 16 Mo. For me, 2 secs is a reasonable time. Adding *at the end* if of course much simpler and faster, but it is not what you asked for... – Serge Ballesta Mar 29 '21 at 11:39
  • Thanks @serge-ballesta. I'm not adding to the end with `std::fstream::ate | std::fstream::in | std::fstream::out` mode. I'm getting the last position with opining `::ate`. All other modes to open needs to cursor moved to the last position. Because of the `]` of `json`. – Orkun Kasapoglu Mar 29 '21 at 12:16
  • @OrkunKasapoglu: `std::fstream:ate` means *At The End*. Any write to a file opened in that mode will be added at the end of the file, independently on the previous position in the file. If the file is opened in that mode you will never be able to write before the closing `]`. – Serge Ballesta Mar 29 '21 at 12:19
  • I thought it was but I found the link that I gave in question. It can seek the write pointer of a file. And it is seeking. – Orkun Kasapoglu Mar 29 '21 at 12:23
  • @OrkunKasapoglu: Then what is your question? – Serge Ballesta Mar 29 '21 at 12:24
  • Efficient way to handle it. Probably there is no efficient way directly in files but I will go with `ate` mode. There are ways to do that. If there are constant and limited number of character after the add point, can be used with `ate | in | out` faster but if there are a lot of characters and variable values; there is no way other than yours. Sorry to bothering you. – Orkun Kasapoglu Mar 29 '21 at 12:38
0

If you open a file for reading, you cant set the write head of it.

You are using std::ofstream with ios::in mode which I'm not sure is effective. but std::ofstream must be opened with ios::out or ios::app. When you override the default you should give also the default.

If you need to open a file for both read and write, you should use std::fstream.

Another issue is that you trying to add some string in the middle of a text file, and it is not so good idea, it is not similar to paste some string in a text file when opened in Notepad. you must replace a section with another section with the same length, pushing some string won't move the rest of the data forward.

I think the easy way is to read the whole JSON to memory, process it by add or remove some data, and finally rewrite the whole JSON to the file.

SHR
  • 7,940
  • 9
  • 38
  • 57
  • Thanks @SHR. Yes I know but it was the only way to move file cursor/pointer with `std::ofstream`. I can handle the deleting issue because it is just a `]`. But it is a json for big data, when growed it is going to bad to read-change-write for whole document. – Orkun Kasapoglu Mar 25 '21 at 13:59