Parse JSON File with Array of Objects without converting to USTRUCT

Hello,

I have multiple JSON files as follows. I am trying to create an all-purpose plugin to handle some JSON object-array files given to me.

// Multiple files like this:
[
    {
        // JSON Object
    },
    {
        // JSON Object
    }
]

I have seen discussion threads such as Parsing json array of objects suggesting using FJsonObjectConverter::JsonArrayStringToUStruct after reading the file as a FString to convert the JSON Array to a TArray<USTURCT of JSON Object A>. I tried defining USTRUCTs for the JSON objects to perform the conversion using this function. However, I have hit some hurdles:

  1. The JSON Objects per-file are somewhat different, requiring custom structs per-file. However, the JSON Objects have a lot of attributes, and therefore the USTRUCTs are getting massive (500+ lines defining structs per file).
  2. I only need certain attributes from the JSON Objects that match a certain pattern (like having a “b” at the start). Unfortunately, there quite a few of these and I cannot get a definite list of attributes needed, since I am trying to create an all-purpose plugin.

Given these issues, I believe that it would be easier just to directly handle the JSON files as JSON Objects rather than converting to USTRUCT and just parse all keys for what I want to match to. However, it seems that FJsonSerializer::Deserialize() cannot handle JSON object-array files.

TSharedPtr<FJsonObject> JsonParsed = MakeShareable(new FJsonObject());
const TSharedRef<TJsonReader<>> JsonReader = TJsonReaderFactory<>::Create(json);
if (FJsonSerializer::Deserialize(JsonReader, JsonParsed) && JsonParsed.IsValid())
{
	// Doesn't run
	UE_LOG(LogScript, Warning, TEXT("PRINTING TYPE: %s"), *JsonParsed->GetStringField("Type"));
}
else
{
	// This prints
	UE_LOG(LogScript, Warning, TEXT("CANNOT CONVERT!"));
}

Is there any UE function that handles JSON object arrays while preserving the JSON object for easy field matching?

Ummm, I’m not 100% sure what you’re asking, but there is a DOM-style JSON interface here:

Is that what you want?

The DOM objects/functions you linked are what the FJsonSerializer and FJsonConvertor my code above use as input/output. The issue is that although I can convert a JSON object string to one of the DOM objects (like FJsonObject) I cannot convert a JSON object array to something like a TArray<FJsonObject>.

It seems that FJsonSerializer cannot handle JSON array strings ([{}, {}, …]) and only handles single JSON object (just {}). It is possible to convert a JSON array string to a TArray of USTRUCTs instead of FJsonObjects, but that requires me to create gigantic structs and cannot handle getting all fields that match a pattern which I require.

1 Like

Normally I would expect a JSON parser to parse [{},{},{}] as an FJsonValueArray, the elements of which are FJsonObjects. Is that not what the unreal parser does?

Sorry for the late response. I tested using FJsonValueArray (well, TArray<TSharedPtr<FJsonValue>> which I believe is the same thing) and it worked! Thank you for your help!

I guess I never tried using the parser to parse into an array explicitly - dumb mistake. Thank you again!

For anyone stumbling on this thread, here is the code I used which worked:

FString json = "[{\"Type\": \"TestType\",\"Name\": \"TestName\",\"Drop\": \"TestDrop\",\"OptionalStr\": \"TestOptional\"}]";
TArray<TSharedPtr<FJsonValue>> JsonParsed;
const TSharedRef<TJsonReader<>> JsonReader = TJsonReaderFactory<>::Create(json);
if (FJsonSerializer::Deserialize(JsonReader, JsonParsed) && JsonParsed[0].IsValid())
{
	// Prints TestType
	UE_LOG(LogScript, Warning, TEXT("PRINTING TYPE: %s"), *JsonParsed[0].Get()->AsObject()->GetStringField("Type"));
}
else
{
	// Doesn't print, it works!
	UE_LOG(LogScript, Warning, TEXT("CANNOT CONVERT!"));
}
2 Likes

One would think that parsing JSON is such a trivial topic, but here I am, necroing an old thread because absolutely nothing would work. I am also seeking for the holy grail of parsing any amount of universal JSON data, nested, arrayed, what have you.

Tried this last code like so, but the Deserialize would always fail.

FString jsonString = wholeText;
UE_LOG(LogHttpServices, Display, TEXT("Content: %s "), *jsonString);
TArray<TSharedPtr<FJsonValue>> jsonParsed;// = ResponseObj->GetArrayField("docs");
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(jsonString);

if (FJsonSerializer::Deserialize(Reader, jsonParsed))
{
	UE_LOG(LogHttpServices, Display, TEXT("Deserialized: %i"), jsonParsed.Num())
}
else
    fail;

← this always happens and JsonParsed is always empty and by this point I don’t even know if it should be.

Another option was to fill the JsonParsed with my json parsed as array, because that is how I’ve seen it done elsewhere, but nothing changed in the result.

In case it helps to anyone this is my code to read an array of Json objects:

First parse Json sting into Json object:

FString JsonString = "a json from somewhere"; //Of course this should be a Json not a string like mine

TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
TSharedRef<TJsonReader<TCHAR>> JsonReader = TJsonReaderFactory<TCHAR>::Create(JsonString);
bool Result =  FJsonSerializer::Deserialize(JsonReader, JsonObject);

Now assuming that Result is true and we have a valid JsonObject that has an array of objects in a field called “pets”:

if(JsonObject->HasField(ANSI_TO_TCHAR("pets")))
{
	TArray<TSharedPtr<FJsonValue>> JsonValues = JsonObject->GetArrayField(ANSI_TO_TCHAR("pets"));
	//Iterate through the array of objects and do something with them
	for(auto PetJsonValue : JsonValues )
	{
		TSharedPtr<FJsonObject> JsonObject = ChannelJsonValue->AsObject();
		JsonObject->GetStringField(ANSI_TO_TCHAR("name")) //or do whatever is needed with the object
	}
}