Search code examples
c++jsonfilestructure

Reading custom (.ndev) Json-like file structure


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": "[email protected]"
}

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.


Solution

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