This is was I was thinking of:

#pragma once
#include <string>

namespace nlohmann {
class json; // forward declaration
};

class SaveState {
	private:
		nlohmann::json* state; // this is a pointer, so it doesn't need the full class declaration
	public:
		std::string getCurrentSong();
		void loadSaveState();
	        SaveState();
};

note that state is now a pointer, so it needs to be assigned a value in the constructor of SaveState:

state = new nlohmann:json;

this way getCurrentSong() returns a std::string and a file including this file doesn't need to include (directly or indirectly) json.hpp.

If you need to return more stuff from getCurrenSong() than a simple string, you could define your custom struct, e.g.:

struct Song {
std::string name;
std::string author;
unsigned int duration;
}

and use this declaration for getCurrentSong():

		Song getCurrentSong();

then, in its implementation you would have to parse the json and fill in the values of the struct object that you'll return.

Every file that includes state.h will also include json.hpp which is a 893KB file (!!).

nickcamillo #include "json.hpp"

Instead include json_fwd.hpp which is a file of only 6KB. This should cut down a lot of compilation time for all files including state.h.

In state.cpp you will need to include the full library using #include "json.hpp".

@giuliomoro when I try your suggestion I get a reference to json is ambiguous error when defining the state pointer:

nlohmann::json* state

@Ward when I try to use the fwd declarations I get this error on the definition of the state variable:

implicit instantiation of undefined template 'nlohmann::json_abi_v3_11_2::basic_json<std::map, std::vector, std::__cxx11::basic_string<char>, bool, long long, unsigned long long, double, std::allocator, adl_serializer, std::vector<unsigned char, std::allocator<unsigned char> >, void>' column: 18, line: 12
#include "json_fwd.hpp"

#ifndef STATE_H_
#define STATE_H_
#endif

class SaveState {
	private:
		nlohmann::json state;
	public:
		nlohmann::json getCurrentSong();
		void loadSaveState();
	        SaveState();
};};

    nickcamillo when I try your suggestion I get a reference to json is ambiguous error when defining the state pointer:

    nlohmann::json* state

    Uff ... turns out nlohmann::json is not actually a json class in the nlohmann namespace, but json is an alias for basic_json<>, courtesy of:

     using json = basic_json<>;

    pfffffff.... so you'd need an even more hack-ish thing:

    SaveState.h:

    #include <string>
    
    class myjson;
    class SaveState {
    	private:
    		myjson* state; // this is a pointer, so it doesn't need the full class declaration
    	public:
    		std::string getCurrentSong();
    		void loadSaveState();
    	        SaveState();
    };

    Then in SaveState.cpp:

    #include "json.hpp"
    #include "SaveState.h"
    class myjson: public nlohmann::json {}; // definition for the myjson class

    Anyhow, given how your library provides a json_fwd.hpp, things can be simplified without using the myjson class:
    SaveState.h:

    #include <string>
    #include "json_fwd.hpp"
    
    class SaveState {
    	private:
    		nlohmann::json* state; // this is a pointer, so it doesn't need the full class declaration
    	public:
    		std::string getCurrentSong();
    		void loadSaveState();
    	        SaveState();
    };

    Then in SaveState.cpp:

    #include "json.hpp"
    ....

    Note how this solution still uses a json* pointer instead of a json object as a member to SaveState. This will avoid the issue you reported:

    nickcamillo when I try to use the fwd declarations I get this error on the definition of the state variable:

    implicit instantiation of undefined template 'nlohmann::json_abi_v3_11_2::basic_json<std::map, std::vector, std::__cxx11::basic_string<char>, bool, long long, unsigned long long, double, std::allocator, adl_serializer, std::vector<unsigned char, std::allocator<unsigned char> >, void>' column: 18, line: 12

    In general, when using forward declarations you cannot use objects in files that only see the fwd declaration. You can only use them (and/or call any method(including contstructor/destructor) on them) on files that see the full declaration.

    5 days later

    @giuliomoro Alright, I think I'm on the verge of getting this working. I did what you outline above, the only difference being I kept the return value of getCurrentSong as nlohmann::json* because it's an object. Also, currentSongId is another private int added to the header along with a simple setter.

    Everything compiles but then I get a runtime error the first time I try call getCurrentSong. When I parse the JSON on startup I'm setting the private state variable like so:

    void SaveState::loadSaveState() {
    	
    	std::ifstream f(kStateJsonFilepath);
    	nlohmann::json tempState = json::parse(f);
    	
    	state = &tempState;
    	
    	int currentSongId = state->value(kCurrentSongId, kIdNotFound);
    	
    	rt_printf("CURRENT SONG ID: %d\n", currentSongId);
    	
    	if (currentSongId != kIdNotFound) {
    		setCurrentSongId(currentSongId);
    	}
    	rt_printf("state: %s\n", state->dump(-1).c_str());
    }

    At the end of that same function, I've verified that the json has been properly parsed via:

    rt_printf("state: %s\n", state->dump(-1).c_str());

    which looks exactly as I'd expect.

    However, when I call getCurrentSong and try to run the same print statement from there, I'm getting mixed bad results:

    nlohmann::json* SaveState::getCurrentSong() {	
    	rt_printf("state in getCurrentSong: %s\n", state->dump(-1).c_str());
    	
    	for(auto &song : (*state)[kSongs]) {
    	    int songId = song[kSongId];
    	    
    	    if (songId == currentSongId) {
    	    	return &song;
    	    }
    	}
    	
    	return &kIdNotFound;
    } 

    The first time it's called I get

    rt_printf("state in getCurrentSong: %s\n", state->dump(-1).c_str());
    //  null

    The second time I get

    rt_printf("state in getCurrentSong: %s\n", state->dump(-1).c_str());
    // {"songs": null} 

    There are no places in which that private state variable are being set or modified except for in that initial load function where I am successfully setting/reading from/printing out the dereferenced variable. The getCurrentSong function works perfectly when the state variable is not a pointer. The only change I made to the loadSaveState function was to first load the parsed JSON data into a tempState variable before assigning the class instance's state variable to the pointer. I only did this because trying to do it directly like so

    state = &(json::parse(f));

    produced an error: `Taking the address of a temporary object of type 'nlohmann::json...etc'

    Am I doing anything obviously wrong here? It seems to me like maybe that tempState variable is getting discarded at the end of the function call and so when I try to read the state variable later it is null? Although I don't see why I'd get two different results when trying to parse the state. I'm not sure I can see the way around this without declaring another private variable in the header that is not a pointer. Thoughts?

      nickcamillo nlohmann::json tempState = json::parse(f);

      state = &tempState;

      When tempState goes out of scope- at the end of the function- the pointer stored in state becomes invalid and should no longer be used.

      nickcamillo There are no places in which that private state variable are being set or modified except for in that initial load function where I am successfully setting/reading from/printing out the dereferenced variable.

      The pointer doesn't change, but the content of the memory it points to may, once the object that was originally stored there gets destroyed.

        giuliomoro so does that mean I woud need to do i/o and re-parse the file every time I need to access the current state? or does this mean that instead of leaving it as the nlohmann::json type I should parse, then assign to a private struct that I define myself that will remain in memory?

        you need to make a copy of the memory instead of the pointer to the memory, which means you need to allocate memory for it. I think something like this may work

        delete tempState; // delete old object
        state = new json(json::parse(f));

        you'll also need to set tempState = nullptr in the constructor or in the class declaration and to call delete state in the destructor. The former is important so the first call to delete is successful (it's valid to call delete on a nullptr); the latter is important so that the json object is properly destroyed and its memory released when the SaveState object is destroyed.

          Not sure I understand why it would... Does the change I suggested work?

            giuliomoro I don't understand how the constructor would know about the tempState variable without declaring it in the header. Using state = new json(json::parse(f)) seems to work without the tempState variable.

            oh I see, my bad. It should be:

            	delete state; // delete old object
            	state = new json(json::parse(f));

            you'll also need to set state = nullptr in the constructor or in the class declaration.