1

I'm currently creating a custom file structure (File extension is .ndev), to increase my skill in working with files in C++. I can save values to a file in a specific way (See below)

{
    "username": "Nikkie",
    "password": "test",
    "role": "Developer",
    "email": "test@gmail.com"
}

This doesn't have anything to do with actual JSON, it's just structured like it.

My question is, how can I read the value of one of those variables with C++, without it coming out like the screenshot below:

enter image description here

My current code to write the file:

void user::RegisterUser(string username, string password, string role, string email)
{

    string filename = "E:\\Coding\\C\\test\\data\\" + username + ".ndev";

    ifstream CheckFile(filename);

    if (CheckFile.good())
    {
        printf("User already exists!");
    }

    else {
        ofstream UserDataFile(filename);

        UserDataFile << "{\n\t\"username\": \"" << username << "\",\n\t\"password\": \"" << password << "\",\n\t\"role\": \"" << role << "\",\n\t\"email\": \"" << email << "\"\n}";
        UserDataFile.close();
    }

    CheckFile.close();
}

Don't bludgeon me about the password encryption, I will add that later. I'm currently trying to actually let it read the values before I do anything else

My current code to read the file:

void user::LoginUser(string username)
{
    string filename = "E:/Coding/C/test/data/" + username + ".ndev";

    ifstream UserFile(filename, ios_base::in);

    if (UserFile.good())
    {
        string name;
        string passw;
        string role;
        string email;

        while (UserFile >> name >> passw >> role >> email)
        {
            cout << name << passw << endl;
            cout << role << email << endl;
        }
    }

    else
    {
        printf("User doesn't exist!");
    }
}

I just can't seem to get it to display the values properly, there are also no errors listed in the console nor in the VS debug build.

NikkieDev
  • 236
  • 3
  • 12
  • It is not clear what you are asking. I assume you do not know how to use the extraction operator `>>`. When you extract a string from a stream only one word is extracted. The leading white-space is ignored. A word means characters between white-spaces. Try to use a simpler format: no special characters, no tags; just one word on each line. – zdf Jan 17 '22 at 19:23
  • I want to read the value that is assigned to the variables `name`, `email`, `password`, and `role`. So it would only display `Nikkie`, `test`, `Developer` and `test@gmail.com` instead of displaying raw text – NikkieDev Jan 17 '22 at 19:24
  • Just save your data like this: `Nikkie test Developer test@gmail.com`. The extra information you added needs a parser. – zdf Jan 17 '22 at 19:26
  • That's not what the question is, I'm trying to make a custom file structure, that implies I don't want to use the normal way but I want to *make* my own way to do it – NikkieDev Jan 17 '22 at 19:27
  • 1
    In this case you have to learn how to write a [parser](https://en.wikipedia.org/wiki/Recursive_descent_parser). – zdf Jan 17 '22 at 19:29
  • Alright, thank you for the info. I will look into that – NikkieDev Jan 17 '22 at 19:29
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/241151/discussion-between-nikkiedev-and-zdf). – NikkieDev Jan 17 '22 at 19:30
  • 1
    Please check out https://github.com/Chlumsky/json-cpp-gen which can automatically generate a parser and serializer for you, based on a C++ structure definition. – Detheroc May 04 '22 at 13:50

1 Answers1

1

From a practical point of view, there is no reason to store your structure in that format. There are simpler ways.

Anyhow, here's a starting point (demo):

#include <iostream>
#include <string>
#include <map>
#include <iomanip>
#include <fstream>

using namespace std;

// your structure
struct person
{
  string name, pass, role, mail;
};

// the tokens your format is using
enum class token : char
{
  lb = '{',
  rb = '}',
  sc = ':',
  comma = ',',
  str,
  end
};

token tk; // current token
string str; // if the current token is token::str, str is its value

// get_token breaks the input stream into tokens - this is the lexer, or tokenizer, or scanner
token get_token(istream& is)
{
  char c;
  if (!(is >> c))
    return tk = token::end;
  switch (c)
  {
  case '{':
  case '}':
  case ':':
  case ',':
    return tk = token(c);
  case '"':
    is.unget();
    is >> quoted(str);
    return tk = token::str;
  default: throw "unexpected char";
  }
}

// throws if the current token is not the expected one
void expect(istream& is, token expected, const char* error)
{
  if (tk != expected)
    throw error;
  get_token(is);
}

// throws if the current token is not a string
string expect_str(istream& is, const char* error)
{
  if (tk != token::str)
    throw error;
  string s = str;
  get_token(is);
  return s;
}

// the actual parser; it extracts the tokens one by oneand compares them with the expected order.
// if the order is not what it expects, it throws an exception.
void read(istream& is, person& p)
{
  get_token(is); // prepare the first token

  expect(is, token::lb, "'{' expected");

  map<string, string> m; // key/values storage
  while (tk == token::str)
  {
    string k = expect_str(is, "key expected");
    expect(is, token::sc, "':' expected");
    string v = expect_str(is, "value expected");

    if (m.find(k) == m.end())
      m[k] = v;
    else
      throw "duplicated key";

    if (tk == token::comma)
      get_token(is);
    else
      break; // end of of key/value pairs
  }

  expect(is, token::rb, "'}' expected");
  expect(is, token::end, "eof expected");

  // check the size of m & the keys & copy from m to p
  // ...
}

int main()
{
  ifstream is{ "c:/temp/test.txt" };
  if (!is)
    return -1;

  try
  {
    person p;
    read(is, p);
  }
  catch (const char* e)
  {
    cout << e;
  }
}
zdf
  • 4,382
  • 3
  • 18
  • 29