Need help with Json objects

I’m trying to recreate the development kit gem on JSon save games, but I’m stuck on creating an FJsonObject. I’m not figuring out how to initialize it.

Consider the following code:

	static bool SaveGame(const FString& FileName)
	{
		if (!GFileManager || !GWorld)
		{
			return false;
		}

		FString FullPath = FPaths::GameSavedDir();
		FullPath += FileName;
		FArchive* SaveFile = GFileManager->CreateFileWriter(*FullPath);
		if (!SaveFile)
		{
			return false;
		}
		TSharedRef > JsonWriter = TJsonWriterFactory<>::Create(SaveFile);
		TSharedPtr JsonObject;

		int32 i=0;
		for (FConstLevelIterator It = GWorld->GetLevelIterator(); It; ++It)
		{
			FString LevelId = TEXT("Level_");
			LevelId += FString::FromInt(i);
			i++;
			ULevel* TheLevel = Cast(*It);
			JsonObject->SetStringField(*LevelId, *TheLevel->GetFullName());
		}
		JsonObject->SetBoolField("SomeBool", true);
		JsonObject->SetStringField("SomeString", *FullPath);
		JsonObject->SetNumberField("SomNumber", 32.0);
		if (FJsonSerializer::Serialize(JsonObject.ToSharedRef(), JsonWriter))
		{
			return true;
		}
		return false;
	}

It fails on the first SetStringField() because JsonObject is NULL. How do I initialize it? Or should the entire thing be done some other way?

A better way to do this would probably not involve a FJsonObject at all. At the very least it’s more direct.

// Create a writer and hold it in this FString
FString JsonStr;
TSharedRef > JsonWriter = TJsonWriterFactory<>::Create(&JsonStr);

// Write data
// Start of an object
JsonWriter->WriteObjectStart();

// Start of an array
JsonWriter->WriteArrayStart("SomeArrayKey");
for (int32 StuffIdx =0; StuffIdx < ArrayOfStuff.Num(); StuffIdx ++)
{
	const FString& SomeStrValue = ArrayOfStuff[StuffIdx];
	JsonWriter->WriteValue(SomeStrValue );
}
JsonWriter->WriteArrayEnd();

// End of object
JsonWriter->WriteObjectEnd();

// Close the writer and finalize the output such that JsonStr has what we want
JsonWriter->Close();

So now you have an FString with all the Json formatted goodness, you can just

*SaveFile << JsonStr;
// you forgot this, this will commit the save to disk and free up your memory :)
delete SaveFile;

Thanks for the advice Josh! That does seems like a more compact way of doing it. I’m just having trouble reading back the data now (also having the same issues when using the other method)

So, this is what the SaveGame function looks like right now.

	static bool SaveGame3(const FString& FileName)
	{
		// Create a writer and hold it in this FString
		FString JsonStr;
		TSharedRef > JsonWriter = TJsonWriterFactory<>::Create(&JsonStr);
 
		JsonWriter->WriteObjectStart();
 
		// ObjectInstance1 (will be MyObject->GetFullPath() or something)
		JsonWriter->WriteObjectStart("ObjectInstance1");
		JsonWriter->WriteValue("SomeBool", true);
		JsonWriter->WriteValue("SomeString", *FileName);
		JsonWriter->WriteValue("SomeNumber", 32.0);
		JsonWriter->WriteObjectEnd();
 
		// ObjectInstance2
		JsonWriter->WriteObjectStart("ObjectInstance2");
		JsonWriter->WriteValue("SomeBool", false);
		JsonWriter->WriteValue("SomeString", TEXT("Bleh"));
		JsonWriter->WriteValue("SomeNumber", 64.0);
		JsonWriter->WriteObjectEnd();
 
		JsonWriter->WriteObjectEnd();
 
		// Close the writer and finalize the output such that JsonStr has what we want
		JsonWriter->Close();
		FString FullPath = FPaths::GameSavedDir();
		FullPath += FileName;
		FArchive* SaveFile = GFileManager->CreateFileWriter(*FullPath);
		if (!SaveFile)
		{
			return false;
		}
		*SaveFile << JsonStr;
		// this will commit the save to disk and free up your memory :)
		delete SaveFile;
		return true;
	}

The idea is to save two (or more) instances of a same class and read the values back.

Now, to read the data back, I tried two methods:

	static void LoadGame(const FString& FileName)
	{
		FString FullPath = FPaths::GameSavedDir();
		FullPath += FileName;

		FArchive* SaveFile = GFileManager->CreateFileReader(*FullPath);
		if (!SaveFile)
		{
			return;
		}
		TSharedRef > JsonReader = TJsonReaderFactory<>::Create(SaveFile);
		
		EJsonNotation::Type ValueType;
		while (JsonReader->ReadNext(ValueType))
		{
			if (ValueType == EJsonNotation::Boolean)
			{
				bool b = JsonReader->GetValueAsBoolean();
			}
			else if (ValueType == EJsonNotation::String)
			{
				FString b = JsonReader->GetValueAsString();
			}
		}
		delete SaveFile;
	}

and

	static void LoadGame2(const FString& FileName)
	{
		FString FullPath = FPaths::GameSavedDir();
		FullPath += FileName;

		FArchive* SaveFile = GFileManager->CreateFileReader(*FullPath);
		if (!SaveFile)
		{
			return;
		}
		TSharedPtr JsonObject;
		TSharedRef > JsonReader = TJsonReaderFactory<>::Create(SaveFile);

		if (FJsonSerializer::Deserialize(JsonReader,JsonObject) &&
			JsonObject.IsValid())
		{
			TSharedPtr Object1 = JsonObject->GetObjectField(TEXT("ObjectInstance1"));
			TSharedPtr Object2 = JsonObject->GetObjectField(TEXT("ObjectInstance2"));
			FString a = JsonObject->GetStringField(TEXT("SomeString"));
			bool b = JsonObject->GetBoolField(TEXT("SomeBool2"));
		}
	}

But both of them fail. The JSonReader has the following in its ErrorMessage property:

ErrorMessage = L"Invalid Json Token. Line: 1 Ch: 1"

What am I doing wrong?

By the way, this is what the saved file looks like:

Ï   {
	"ObjectInstance1":
	{
		"SomeBool": true,
		"SomeString": "Quicksave.save",
		"SomeNumber": 32
	},
	"ObjectInstance2":
	{
		"SomeBool": false,
		"SomeString": "Bleh",
		"SomeNumber": 64
	}
} 

I tried manually deleting that Ï from the beginning, but didn’t help too.

Nevermind, I think I got it…

Just one question. What is the difference between an Object (WriteObjectStart()) and an Array (WriteArrayStart())?

If I want to save several instances of a same class, how would I do it? (one Object per instance, or one Array per instance… how?)

That’s the power of Json, it can be whatever you want, so long as you understand it and like it. Json Spec here

I would suggest an array of objects. You can even have an object that has arrays of objects (with or without names). Notice that both of those functions can be called with or without an identifier.

Point is, its up to you :slight_smile: Pardon the formatting below, but this is the idea.

[ // optional
{ 
 array1 : [
 { key : val }
 { key : val }
]
 anotherkey : anotherval
}
] // optional

Ohhh ok. All clear now. Thanks!

I’ll post a tutorial on this when I’m done.

#MakeShareable

This should do the trick!

instead of

TSharedPtr JsonObject;

use this

TSharedPtr TheJsonObject = MakeShareable( new FJsonObject() );

I’ve had issues sometimes with a naming scheme that only removes the prefix like F or U, so I changed it to TheJsonObject


Summary

You were creating a pointer but not allocating any memory for that pointer to point to

you did the equivalent of

AActor* AnActor;

AnActor->GetActorLocation();

When using SharedRefs and SharedPtrs, the syntax is the above use of MakeShareable

Enjoy!

Rama

PS: im not sure any other JSON-specific initialization is required but this should get you started

#JsonWriter.h Example

You can see this syntax of make shareable in JsonWriter.h

template < class PrintPolicy = TPrettyJsonPrintPolicy >
class TJsonStringWriter : public TJsonWriter
{
public:

	static TSharedRef< TJsonStringWriter > Create( FString* const InStream )
	{
		return MakeShareable( new TJsonStringWriter( InStream ) );
	}

That worked. Thanks!

Can you tell me what you did about that “Ï” in the begining?

Hello,

This is a question from the beta version of the engine. We are marking this answered for tracking purposes. If you are experiencing an issue similar to this please post a new question.

Thank you.