A Demo Project for the UnrealEngineSDK
Loading...
Searching...
No Matches
DlgJsonParser.cpp
Go to the documentation of this file.
1// Copyright Csaba Molnar, Daniel Butum. All Rights Reserved.
2#include "IO/DlgJsonParser.h"
3
4
5#include "Logging/LogMacros.h"
6#include "UObject/Object.h"
7#include "Misc/FileHelper.h"
8#include "Misc/Paths.h"
9#include "UObject/UnrealType.h"
10#include "UObject/EnumProperty.h"
11#include "UObject/UObjectIterator.h"
12#include "UObject/TextProperty.h"
13#include "UObject/PropertyPortFlags.h"
14#include "JsonObjectConverter.h"
15#include "JsonObjectWrapper.h"
16#include "Internationalization/CulturePointer.h"
17#include "Internationalization/Culture.h"
18#include "Misc/OutputDevice.h"
19#include "Misc/FeedbackContext.h"
20
21#include "NYReflectionHelper.h"
22
23
24DEFINE_LOG_CATEGORY(LogDlgJsonParser);
25
26bool GetTextFromObject(const TSharedRef<FJsonObject>& Obj, FText& TextOut)
27{
28 // get the prioritized culture name list
29 const FCultureRef CurrentCulture = FInternationalization::Get().GetCurrentCulture();
30 const TArray<FString> CultureList = CurrentCulture->GetPrioritizedParentCultureNames();
31
32 // try to follow the fall back chain that the engine uses
33 FString TextString;
34 for (const FString& CultureCode : CultureList)
35 {
36 if (Obj->TryGetStringField(CultureCode, TextString))
37 {
38 TextOut = FText::FromString(TextString);
39 return true;
40 }
41 }
42
43 // no luck, is this possibly an unrelated json object?
44 return false;
45}
46
47FString GetStringForJsonType(const EJson Type)
48{
49 switch (Type)
50 {
51 case EJson::None:
52 return TEXT("EJson::None");
53 case EJson::Null:
54 return TEXT("EJson::Null");
55 case EJson::String:
56 return TEXT("EJson::String");
57 case EJson::Number:
58 return TEXT("EJson::Number");
59 case EJson::Boolean:
60 return TEXT("EJson::Boolean");
61 case EJson::Array:
62 return TEXT("EJson::Array");
63 case EJson::Object:
64 return TEXT("EJson::Object");
65 default:
66 return TEXT("UNKNOWN TYPE, should never happen");
67 }
68}
69
71void FDlgJsonParser::InitializeParser(const FString& FilePath)
72{
73 if (FFileHelper::LoadFileToString(JsonString, *FilePath))
74 {
75 FileName = FPaths::GetBaseFilename(FilePath, true);
76 bIsValidFile = true;
77 }
78 else
79 {
80 UE_LOG(LogDlgJsonParser, Error, TEXT("Failed to load config file %s"), *FilePath);
81 bIsValidFile = false;
82 }
83
84 // TODO check here if the JSON file is valid.
85}
86
89{
90 JsonString = Text;
91 bIsValidFile = true;
92 FileName = "";
93}
94
96void FDlgJsonParser::ReadAllProperty( const UStruct* ReferenceClass, void* TargetObject, UObject* InDefaultObjectOuter)
97{
98 if (!IsValidFile())
99 {
100 return;
101 }
102
103 // TODO use DefaultObjectOuter;
104 DefaultObjectOuter = InDefaultObjectOuter;
105 bIsValidFile = JsonObjectStringToUStruct(ReferenceClass, TargetObject);
106}
107
109bool FDlgJsonParser::ConvertScalarJsonValueToProperty(const TSharedPtr<FJsonValue>& JsonValue, FNYProperty* Property, void* ContainerPtr, void* ValuePtr)
110{
111 check(Property);
112 if (bLogVerbose)
113 {
114 UE_LOG(LogDlgJsonParser, Verbose, TEXT("ConvertScalarJsonValueToProperty, Property = `%s`"), *Property->GetPathName());
115 }
116 if (ValuePtr == nullptr)
117 {
118 // Nothing else to do
119 return true;
120 }
121
122 // Enum
123 if (auto* EnumProperty = FNYReflectionHelper::CastProperty<FNYEnumProperty>(Property))
124 {
125 if (JsonValue->Type == EJson::String)
126 {
127 // see if we were passed a string for the enum
128 const UEnum* Enum = EnumProperty->GetEnum();
129 check(Enum);
130 const FString StrValue = JsonValue->AsString();
131 const int64 IntValue = Enum->GetValueByName(FName(*StrValue));
132 if (IntValue == INDEX_NONE)
133 {
134 UE_LOG(LogDlgJsonParser,
135 Error,
136 TEXT("ConvertScalarJsonValueToProperty - Unable import enum `%s` from string value `%s` for property `%s`"),
137 *Enum->CppType, *StrValue, *Property->GetNameCPP());
138 return false;
139 }
140 EnumProperty->GetUnderlyingProperty()->SetIntPropertyValue(ValuePtr, IntValue);
141 }
142 else
143 {
144 // Numeric enum
145 // AsNumber will log an error for completely inappropriate types (then give us a default)
146 EnumProperty->GetUnderlyingProperty()->SetIntPropertyValue(ValuePtr, static_cast<int64>(JsonValue->AsNumber()));
147 }
148
149 return true;
150 }
151
152 // Numeric, int, float, possible enum
153 if (auto* NumericProperty = FNYReflectionHelper::CastProperty<FNYNumericProperty>(Property))
154 {
155 if (NumericProperty->IsEnum() && JsonValue->Type == EJson::String)
156 {
157 // see if we were passed a string for the enum
158 const UEnum* Enum = NumericProperty->GetIntPropertyEnum();
159 check(Enum); // should be assured by IsEnum()
160 const FString StrValue = JsonValue->AsString();
161 const int64 IntValue = Enum->GetValueByName(FName(*StrValue));
162 if (IntValue == INDEX_NONE)
163 {
164 UE_LOG(
165 LogDlgJsonParser,
166 Error,
167 TEXT("ConvertScalarJsonValueToProperty - Unable import enum %s from string value %s for property %s"),
168 *Enum->CppType, *StrValue, *Property->GetNameCPP()
169 );
170 return false;
171 }
172 NumericProperty->SetIntPropertyValue(ValuePtr, IntValue);
173 }
174 else if (NumericProperty->IsInteger())
175 {
176 if (JsonValue->Type == EJson::String)
177 {
178 // parse string -> int64 ourselves so we don't lose any precision going through AsNumber (aka double)
179 NumericProperty->SetIntPropertyValue(ValuePtr, FCString::Atoi64(*JsonValue->AsString()));
180 }
181 else
182 {
183 // AsNumber will log an error for completely inappropriate types (then give us a default)
184 NumericProperty->SetIntPropertyValue(ValuePtr, static_cast<int64>(JsonValue->AsNumber()));
185 }
186 }
187 else if (NumericProperty->IsFloatingPoint())
188 {
189 // AsNumber will log an error for completely inappropriate types (then give us a default)
190 NumericProperty->SetFloatingPointPropertyValue(ValuePtr, JsonValue->AsNumber());
191 }
192 else
193 {
194 UE_LOG(
195 LogDlgJsonParser,
196 Error,
197 TEXT("ConvertScalarJsonValueToProperty - Unable to set numeric property type %s for property %s"),
198 *Property->GetClass()->GetName(), *Property->GetNameCPP()
199 );
200 return false;
201 }
202
203 return true;
204 }
205
206 // Bool
207 if (auto* BoolProperty = FNYReflectionHelper::CastProperty<FNYBoolProperty>(Property))
208 {
209 // AsBool will log an error for completely inappropriate types (then give us a default)
210 BoolProperty->SetPropertyValue(ValuePtr, JsonValue->AsBool());
211 return true;
212 }
213
214 // FString
215 if (auto* StringProperty = FNYReflectionHelper::CastProperty<FNYStrProperty>(Property))
216 {
217 // Seems unsafe: AsString will log an error for completely inappropriate types (then give us a default)
218 FString String = JsonValue->AsString();
219 StringProperty->SetPropertyValue(ValuePtr, String);
220 return true;
221 }
222
223 // FName
224 if (auto* NameProperty = FNYReflectionHelper::CastProperty<FNYNameProperty>(Property))
225 {
226 FString String;
227 const FName StringFName = FName(*JsonValue->AsString());
228 NameProperty->SetPropertyValue(ValuePtr, StringFName);
229 return true;
230 }
231
232 // FText
233 if (auto* TextProperty = FNYReflectionHelper::CastProperty<FNYTextProperty>(Property))
234 {
235 if (JsonValue->Type == EJson::String)
236 {
237 // assume this string is already localized, so import as invariant
238 const FString String = JsonValue->AsString();
239 TextProperty->SetPropertyValue(ValuePtr, FText::FromString(String));
240 }
241 else if (JsonValue->Type == EJson::Object)
242 {
243 const TSharedPtr<FJsonObject> Obj = JsonValue->AsObject();
244 check(Obj.IsValid()); // should not fail if Type == EJson::Object
245
246 // import the subvalue as a culture invariant string
247 FText Text;
248 if (!GetTextFromObject(Obj.ToSharedRef(), Text))
249 {
250 UE_LOG(
251 LogDlgJsonParser,
252 Error,
253 TEXT("ConvertScalarJsonValueToProperty - Attempted to import FText from JSON object with invalid keys for property %s"),
254 *Property->GetNameCPP()
255 );
256 return false;
257 }
258 TextProperty->SetPropertyValue(ValuePtr, Text);
259 }
260 else
261 {
262 UE_LOG(
263 LogDlgJsonParser,
264 Error,
265 TEXT("ConvertScalarJsonValueToProperty - Attempted to import FText from JSON that was neither string nor object for property %s"),
266 *Property->GetNameCPP()
267 );
268 return false;
269 }
270
271 return true;
272 }
273
274 // TArray
275 if (auto* ArrayProperty = FNYReflectionHelper::CastProperty<FNYArrayProperty>(Property))
276 {
277 if (JsonValue->Type == EJson::Array)
278 {
279 const TArray<TSharedPtr<FJsonValue>> ArrayValue = JsonValue->AsArray();
280 const int32 ArrayNum = ArrayValue.Num();
281
282 // make the output array size match
283 FScriptArrayHelper Helper(ArrayProperty, ValuePtr);
284 Helper.EmptyValues();
285 Helper.Resize(ArrayNum);
286
287 // set the property values
288 bool bReturnStatus = true;
289 for (int32 Index = 0; Index < ArrayNum; Index++)
290 {
291 const TSharedPtr<FJsonValue>& ArrayValueItem = ArrayValue[Index];
292 if (ArrayValueItem.IsValid())
293 {
294 if (!JsonValueToProperty(ArrayValueItem, ArrayProperty->Inner, ContainerPtr, Helper.GetRawPtr(Index)))
295 {
296 bReturnStatus = false;
297 UE_LOG(
298 LogDlgJsonParser,
299 Error,
300 TEXT("ConvertScalarJsonValueToProperty - Unable to deserialize array element [%d] for property %s"),
301 Index, *Property->GetNameCPP()
302 );
303 }
304 }
305 }
306
307 return bReturnStatus;
308 }
309
310 UE_LOG(LogDlgJsonParser,
311 Error,
312 TEXT("ConvertScalarJsonValueToProperty - Attempted to import TArray from non-array JSON key for property %s"),
313 *Property->GetNameCPP());
314 return false;
315 }
316
317 // Set
318 if (auto* SetProperty = FNYReflectionHelper::CastProperty<FNYSetProperty>(Property))
319 {
320 if (JsonValue->Type == EJson::Array)
321 {
322 const TArray<TSharedPtr<FJsonValue>> ArrayValue = JsonValue->AsArray();
323 const int32 ArrayNum = ArrayValue.Num();
324
325 FScriptSetHelper Helper(SetProperty, ValuePtr);
326 Helper.EmptyElements();
327
328 // set the property values
329 bool bReturnStatus = true;
330 for (int32 Index = 0; Index < ArrayNum; ++Index)
331 {
332 const TSharedPtr<FJsonValue>& ArrayValueItem = ArrayValue[Index];
333 if (ArrayValueItem.IsValid())
334 {
335 const int32 NewIndex = Helper.AddDefaultValue_Invalid_NeedsRehash();
336 if (!JsonValueToProperty(ArrayValueItem, SetProperty->ElementProp, ContainerPtr, Helper.GetElementPtr(NewIndex)))
337 {
338 bReturnStatus = false;
339 UE_LOG(
340 LogDlgJsonParser,
341 Error,
342 TEXT("ConvertScalarJsonValueToProperty - Unable to deserialize set element [%d] for property %s"),
343 Index,
344 *Property->GetNameCPP()
345 );
346 }
347 }
348 }
349
350 Helper.Rehash();
351 return bReturnStatus;
352 }
353
354 UE_LOG(
355 LogDlgJsonParser,
356 Error,
357 TEXT("ConvertScalarJsonValueToProperty - Attempted to import TSet from non-array (JsonValue->Type = `%s`) JSON key for property %s"),
358 *GetStringForJsonType(JsonValue->Type), *Property->GetNameCPP()
359 );
360 return false;
361 }
362
363 // TMap
364 if (auto* MapProperty = FNYReflectionHelper::CastProperty<FNYMapProperty>(Property))
365 {
366 if (JsonValue->Type == EJson::Object)
367 {
368 const TSharedPtr<FJsonObject> ObjectValue = JsonValue->AsObject();
369 FScriptMapHelper Helper(MapProperty, ValuePtr);
370 Helper.EmptyValues();
371
372 // set the property values
373 bool bReturnStatus = true;
374 for (const auto& Entry : ObjectValue->Values)
375 {
376 if (Entry.Value.IsValid())
377 {
378 const int32 NewIndex = Helper.AddDefaultValue_Invalid_NeedsRehash();
379
380 // NOTE if key is a FNYStructProperty no need to Import the text item here as it will do that below in UStruct
381 // Add key
382 const TSharedPtr<FJsonValueString> KeyAsString = MakeShared<FJsonValueString>(Entry.Key);
383 const bool bKeySuccess = JsonValueToProperty(KeyAsString, Helper.GetKeyProperty(), ContainerPtr, Helper.GetKeyPtr(NewIndex));
384
385 // Add value
386 const bool bValueSuccess = JsonValueToProperty(Entry.Value, Helper.GetValueProperty(), ContainerPtr, Helper.GetValuePtr(NewIndex));
387
388 if (!bKeySuccess || !bValueSuccess)
389 {
390 Helper.RemoveAt(NewIndex);
391 bReturnStatus = false;
392 UE_LOG(
393 LogDlgJsonParser,
394 Error,
395 TEXT("ConvertScalarJsonValueToProperty - Unable to deserialize map element [key: %s] for property %s"),
396 *Entry.Key, *Property->GetNameCPP()
397 );
398 }
399 }
400 }
401
402 Helper.Rehash();
403 return bReturnStatus;
404 }
405
406 UE_LOG(LogDlgJsonParser,
407 Error,
408 TEXT("ConvertScalarJsonValueToProperty - Attempted to import TMap from non-object JSON key for property %s"),
409 *Property->GetNameCPP());
410 return false;
411 }
412
413 // UStruct
414 if (auto* StructProperty = FNYReflectionHelper::CastProperty<FNYStructProperty>(Property))
415 {
416 static const FName NAME_DateTime(TEXT("DateTime"));
417 static const FName NAME_Color(TEXT("Color"));
418 static const FName NAME_LinearColor(TEXT("LinearColor"));
419
420 // Default struct export
421 if (JsonValue->Type == EJson::Object)
422 {
423 const TSharedPtr<FJsonObject> Obj = JsonValue->AsObject();
424 check(Obj.IsValid()); // should not fail if Type == EJson::Object
425 if (!JsonObjectToUStruct(Obj.ToSharedRef(), StructProperty->Struct, ValuePtr))
426 {
427 UE_LOG(
428 LogDlgJsonParser,
429 Error,
430 TEXT("ConvertScalarJsonValueToProperty - JsonObjectToUStruct failed for property %s"),
431 *Property->GetNameCPP()
432 );
433 return false;
434 }
435 }
436
437 // Handle some structs that are exported to string in a special way
438 else if (JsonValue->Type == EJson::String && StructProperty->Struct->GetFName() == NAME_LinearColor)
439 {
440 const FString ColorString = JsonValue->AsString();
441 const FColor IntermediateColor = FColor::FromHex(ColorString);
442 FLinearColor& ColorOut = *static_cast<FLinearColor*>(ValuePtr);
443 ColorOut = IntermediateColor;
444 }
445 else if (JsonValue->Type == EJson::String && StructProperty->Struct->GetFName() == NAME_Color)
446 {
447 const FString ColorString = JsonValue->AsString();
448 FColor& ColorOut = *static_cast<FColor*>(ValuePtr);
449 ColorOut = FColor::FromHex(ColorString);
450 }
451 else if (JsonValue->Type == EJson::String && StructProperty->Struct->GetFName() == NAME_DateTime)
452 {
453 const FString DateString = JsonValue->AsString();
454 FDateTime& DateTimeOut = *static_cast<FDateTime*>(ValuePtr);
455 if (DateString == TEXT("min"))
456 {
457 // min representable value for our date struct. Actual date may vary by platform (this is used for sorting)
458 DateTimeOut = FDateTime::MinValue();
459 }
460 else if (DateString == TEXT("max"))
461 {
462 // max representable value for our date struct. Actual date may vary by platform (this is used for sorting)
463 DateTimeOut = FDateTime::MaxValue();
464 }
465 else if (DateString == TEXT("now"))
466 {
467 // this value's not really meaningful from json serialization (since we don't know timezone) but handle it anyway since we're handling the other keywords
468 DateTimeOut = FDateTime::UtcNow();
469 }
470 else if (FDateTime::ParseIso8601(*DateString, DateTimeOut))
471 {
472 // ok
473 }
474 else if (FDateTime::Parse(DateString, DateTimeOut))
475 {
476 // ok
477 }
478 else
479 {
480 UE_LOG(
481 LogDlgJsonParser,
482 Warning,
483 TEXT("ConvertScalarJsonValueToProperty - Unable to import FDateTime for property %s"),
484 *Property->GetNameCPP()
485 );
486 return false;
487 }
488 }
489 else if (JsonValue->Type == EJson::String &&
490 StructProperty->Struct->GetCppStructOps() &&
491 StructProperty->Struct->GetCppStructOps()->HasImportTextItem())
492 {
493 // Import as simple native string
494 UScriptStruct::ICppStructOps* TheCppStructOps = StructProperty->Struct->GetCppStructOps();
495
496 const FString ImportTextString = JsonValue->AsString();
497 const TCHAR* ImportTextPtr = *ImportTextString;
498 if (!TheCppStructOps->ImportTextItem(ImportTextPtr, ValuePtr, PPF_None, nullptr, static_cast<FOutputDevice*>(GWarn)))
499 {
500 // Fall back to trying the tagged property approach if custom ImportTextItem couldn't get it done
501 Property->ImportText(ImportTextPtr, ValuePtr, PPF_None, nullptr);
502 }
503 }
504 else if (JsonValue->Type == EJson::String)
505 {
506 // Import as simple string
507 // UTextBuffer* ImportErrors = NewObject<UTextBuffer>();
508 const FString ImportTextString = JsonValue->AsString();
509 const TCHAR* ImportTextPtr = *ImportTextString;
510 Property->ImportText(ImportTextPtr, ValuePtr, PPF_None, nullptr);
511 }
512 else
513 {
514 UE_LOG(
515 LogDlgJsonParser,
516 Error,
517 TEXT("ConvertScalarJsonValueToProperty - Attempted to import UStruct from non-object JSON key for property %s"),
518 *Property->GetNameCPP()
519 );
520 return false;
521 }
522
523 return true;
524 }
525
526 // UObject
527 if (auto* ObjectProperty = FNYReflectionHelper::CastProperty<FNYObjectProperty>(Property))
528 {
529 // NOTE: The Value here should be a pointer to a pointer
530 // Because the UObjects are pointers, we must deference it. So instead of it being a void** we want it to be a void*
531 auto* ObjectPtrPtr = static_cast<UObject**>(ObjectProperty->ContainerPtrToValuePtr<void>(ValuePtr, 0));
532 if (ObjectPtrPtr == nullptr)
533 {
534 UE_LOG(
535 LogDlgJsonParser,
536 Error,
537 TEXT("PropertyName = `%s` Is a FNYObjectProperty but can't get non null ContainerPtrToValuePtr from it's StructObject"),
538 *Property->GetNameCPP()
539 );
540 return false;
541 }
542
543 // NOTE: We must check one level up to check if it is a nullptr or not
544 // Reset first, if non nullptr
545 const UObject* ContainerObjectPtr = ObjectProperty->GetObjectPropertyValue_InContainer(ContainerPtr);
546 if (ContainerObjectPtr != nullptr)
547 {
548 *ObjectPtrPtr = nullptr;
549 }
550
551 // Nothing else to do
552 if (JsonValue->IsNull())
553 {
554 return true;
555 }
556 const UClass* ObjectClass = ObjectProperty->PropertyClass;
557
558 // Special case, load by reference, See CanSaveAsReference
559 // Handle some objects that are exported to string in a special way. Similar to the UStruct above.
560 if (JsonValue->Type == EJson::String)
561 {
562 const FString Path = JsonValue->AsString();
563 if (!Path.TrimStartAndEnd().IsEmpty()) // null reference?
564 {
565 *ObjectPtrPtr = StaticLoadObject(UObject::StaticClass(), DefaultObjectOuter, *Path);
566 }
567 return true;
568 }
569
570 // Load the Normal JSON object
571 // Must have the type inside the Json Object
572 check(JsonValue->Type == EJson::Object);
573 const TSharedPtr<FJsonObject> JsonObject = JsonValue->AsObject();
574 check(JsonObject.IsValid()); // should not fail if Type == EJson::Object
575
576 const FString SpecialKeyType = TEXT("__type__");
577 if (!JsonObject->HasField(SpecialKeyType))
578 {
579 UE_LOG(
580 LogDlgJsonParser,
581 Error,
582 TEXT("ConvertScalarJsonValueToProperty - PropertyName = `%s` JSON does not have the __type__ special property."),
583 *Property->GetNameCPP()
584 );
585 return false;
586 }
587
588 // Create the new Object
589 FString JsonObjectType;
590 check(JsonObject->TryGetStringField(SpecialKeyType, JsonObjectType));
591
592 const UClass* ChildClass = GetChildClassFromName(ObjectClass, JsonObjectType);
593 if (ChildClass == nullptr)
594 {
595 UE_LOG(
596 LogDlgJsonParser,
597 Error,
598 TEXT("ConvertScalarJsonValueToProperty - Trying to load by string reference. Could not find class `%s` for FNYObjectProperty = `%s`. Ignored."),
599 *JsonObjectType, *Property->GetNameCPP()
600 );
601 return false;
602 }
603 *ObjectPtrPtr = CreateNewUObject(ChildClass, DefaultObjectOuter);
604
605 // Something is wrong
606 if (*ObjectPtrPtr == nullptr || !(*ObjectPtrPtr)->IsValidLowLevelFast())
607 {
608 UE_LOG(
609 LogDlgJsonParser,
610 Error,
611 TEXT("JsonValueToProperty - PropertyName = `%s` Is a FNYObjectProperty but could not build any valid UObject"),
612 *Property->GetNameCPP()
613 );
614 return false;
615 }
616
617 // Write the json object
618 if (!JsonObjectToUStruct(JsonObject.ToSharedRef(), ObjectClass, *ObjectPtrPtr))
619 {
620 UE_LOG(
621 LogDlgJsonParser,
622 Error,
623 TEXT("JsonValueToProperty - JsonObjectToUStruct failed for property %s"),
624 *Property->GetNameCPP()
625 );
626 return false;
627 }
628
629 return true;
630 }
631
632 // Default to expect a string for everything else
633 check(JsonValue->Type != EJson::Object);
634 const FString Buffer = JsonValue->AsString();
635 if (Property->ImportText(*Buffer, ValuePtr, PPF_None, nullptr) == nullptr)
636 {
637 UE_LOG(
638 LogDlgJsonParser,
639 Error,
640 TEXT("JsonValueToProperty - Unable import property type %s from string value for property %s"),
641 *Property->GetClass()->GetName(), *Property->GetNameCPP()
642 );
643 return false;
644 }
645 return true;
646}
647
649bool FDlgJsonParser::JsonValueToProperty(const TSharedPtr<FJsonValue>& JsonValue, FNYProperty* Property, void* ContainerPtr, void* ValuePtr)
650{
651 check(Property);
652 if (bLogVerbose)
653 {
654 UE_LOG(LogDlgJsonParser, Verbose, TEXT("JsonValueToProperty, Property = `%s`"), *Property->GetPathName());
655 }
656 if (!JsonValue.IsValid())
657 {
658 UE_LOG(LogDlgJsonParser, Error, TEXT("JsonValueToProperty - Invalid value JSON key"));
659 return false;
660 }
661
662 const bool bArrayProperty = Property->IsA<FNYArrayProperty>();
663 const bool bSetProperty = Property->IsA<FNYSetProperty>();
664 const bool bJsonArray = JsonValue->Type == EJson::Array;
665
666 // Scalar only one property
667 if (!bJsonArray)
668 {
669 if (bArrayProperty)
670 {
671 UE_LOG(
672 LogDlgJsonParser,
673 Error,
674 TEXT("JsonValueToProperty - Attempted to import TArray from non-array JSON type = `%s`"),
675 *GetStringForJsonType(JsonValue->Type)
676 );
677 return false;
678 }
679 if (bSetProperty)
680 {
681 UE_LOG(
682 LogDlgJsonParser,
683 Error,
684 TEXT("JsonValueToProperty - Attempted to import TSet from non-array JSON type = `%s`"),
685 *GetStringForJsonType(JsonValue->Type)
686 );
687 return false;
688 }
689
690 if (Property->ArrayDim != 1)
691 {
692 UE_LOG(LogDlgJsonParser, Warning, TEXT("[Property->ArrayDim != 1] Ignoring excess properties when deserializing %s"), *Property->GetNameCPP());
693 }
694
695 return ConvertScalarJsonValueToProperty(JsonValue, Property, ContainerPtr, ValuePtr);
696 }
697
698 // In practice, the ArrayDim == 1 check ought to be redundant, since nested arrays of UPropertys are not supported
699 if ((bArrayProperty || bSetProperty) && Property->ArrayDim == 1)
700 {
701 // Read into TArray/TSet
702 return ConvertScalarJsonValueToProperty(JsonValue, Property, ContainerPtr, ValuePtr);
703 }
704
705 // Array
706 // We're deserializing a JSON array
707 const auto& ArrayValue = JsonValue->AsArray();
708 if (Property->ArrayDim < ArrayValue.Num())
709 {
710 UE_LOG(LogDlgJsonParser, Warning, TEXT("[Property->ArrayDim < ArrayValue.Num()] Ignoring excess properties when deserializing %s"), *Property->GetNameCPP());
711 }
712
713 // Read into Array/Set
714 const int32 ItemsToRead = FMath::Clamp(ArrayValue.Num(), 0, Property->ArrayDim);
715 auto* ValueIntPtr = static_cast<uint8*>(ValuePtr);
716 bool bReturnStatus = true;
717 for (int32 Index = 0; Index < ItemsToRead; ++Index)
718 {
719 // ValuePtr + Index * Property->ElementSize is literally FScriptArrayHelper::GetRawPtr
720 bReturnStatus &= ConvertScalarJsonValueToProperty(ArrayValue[Index], Property, ContainerPtr, ValueIntPtr + Index * Property->ElementSize);
721 }
722 return bReturnStatus;
723}
724
726bool FDlgJsonParser::JsonAttributesToUStruct(const TMap<FString, TSharedPtr<FJsonValue>>& JsonAttributes,
727 const UStruct* StructDefinition, void* ContainerPtr)
728{
729 check(StructDefinition);
730 check(ContainerPtr);
731 if (bLogVerbose)
732 {
733 UE_LOG(LogDlgJsonParser, Verbose, TEXT("JsonAttributesToUStruct, StructDefinition = `%s`"), *StructDefinition->GetPathName());
734 }
735
736 // Json Wrapper, already have an Object
737 if (StructDefinition == FJsonObjectWrapper::StaticStruct())
738 {
739 // Just copy it into the object
740 FJsonObjectWrapper* ProxyObject = (FJsonObjectWrapper *)ContainerPtr;
741 ProxyObject->JsonObject = MakeShared<FJsonObject>();
742 ProxyObject->JsonObject->Values = JsonAttributes;
743 return true;
744 }
745
746 // Handle UObject inheritance (children of class)
747 if (StructDefinition->IsA<UClass>())
748 {
749 // Structure points to the child
750 const UObject* UnrealObject = static_cast<const UObject*>(ContainerPtr);
751 if (!UnrealObject->IsValidLowLevelFast())
752 {
753 UE_LOG(
754 LogDlgJsonParser,
755 Error,
756 TEXT("JsonAttributesToUStruct: StructDefinition = `%s` is a UClass and expected ContainerPtr to be an UObject. Memory corruption?"),
757 *StructDefinition->GetPathName()
758 );
759 return false;
760 }
761 StructDefinition = UnrealObject->GetClass();
762 }
763 if (!StructDefinition->IsValidLowLevelFast())
764 {
765 UE_LOG(
766 LogDlgJsonParser,
767 Error,
768 TEXT("JsonAttributesToUStruct: StructDefinition = `%s` is a UClass and expected ContainerPtr.Class to be valid. Memory corruption?"),
769 *StructDefinition->GetPathName()
770 );
771 return false;
772 }
773
774 // iterate over the struct properties
775 for (TFieldIterator<FNYProperty> PropIt(StructDefinition); PropIt; ++PropIt)
776 {
777 auto* Property = *PropIt;
778 if (!ensure(Property))
779 continue;
780
781 const FString PropertyName = Property->GetName();
782
783 // Check to see if we should ignore this property
784 if (CheckFlags != 0 && !Property->HasAnyPropertyFlags(CheckFlags))
785 {
786 continue;
787 }
788 // TODO skip property
789
790 // Find a JSON value matching this property name
791 TSharedPtr<FJsonValue> JsonValue;
792 for (auto& Elem : JsonAttributes)
793 {
794 // use case insensitive search since FName may change case strangely on us
795 // TODO does this break on struct/classes with properties of similar name?
796 if (PropertyName.Equals(Elem.Key, ESearchCase::IgnoreCase))
797 {
798 JsonValue = Elem.Value;
799 break;
800 }
801 }
802 if (!JsonValue.IsValid())
803 {
804 // we allow values to not be found since this mirrors the typical UObject mantra that all the fields are optional when deserializing
805 continue;
806 }
807
808 void* ValuePtr = nullptr;
809 if (Property->IsA<FNYObjectProperty>())
810 {
811 // Handle pointers, only allowed to be UObjects (are already pointers to the Value)
812 ValuePtr = ContainerPtr;
813 }
814 else
815 {
816 // Normal non pointer property
817 ValuePtr = Property->ContainerPtrToValuePtr<void>(ContainerPtr, 0);
818 }
819
820 // Convert the JsonValue to the Property
821 if (!JsonValueToProperty(JsonValue, Property, ContainerPtr, ValuePtr))
822 {
823 UE_LOG(
824 LogDlgJsonParser,
825 Error,
826 TEXT("JsonObjectToUStruct - Unable to parse %s.%s from JSON"),
827 *StructDefinition->GetName(), *PropertyName
828 );
829 continue;
830 }
831 }
832
833 return true;
834}
835
837bool FDlgJsonParser::JsonObjectStringToUStruct(const UStruct* StructDefinition, void* ContainerPtr)
838{
839 TSharedPtr<FJsonObject> JsonObject;
840 TSharedRef<TJsonReader<>> JsonReader = TJsonReaderFactory<>::Create(JsonString);
841 if (!FJsonSerializer::Deserialize(JsonReader, JsonObject) || !JsonObject.IsValid())
842 {
843 UE_LOG(LogDlgJsonParser, Error, TEXT("JsonObjectStringToUStruct - Unable to parse json=[%s]"), *JsonString);
844 return false;
845 }
846 if (!JsonObjectToUStruct(JsonObject.ToSharedRef(), StructDefinition, ContainerPtr))
847 {
848 UE_LOG(LogDlgJsonParser, Error, TEXT("JsonObjectStringToUStruct - Unable to deserialize. json=[%s]"), *JsonString);
849 return false;
850 }
851 return true;
852}
FString GetStringForJsonType(const EJson Type)
DEFINE_LOG_CATEGORY(LogDlgJsonParser)
bool GetTextFromObject(const TSharedRef< FJsonObject > &Obj, FText &TextOut)
UProperty FNYProperty
UArrayProperty FNYArrayProperty
UObjectProperty FNYObjectProperty
USetProperty FNYSetProperty
UObject * DefaultObjectOuter
bool IsValidFile() const override
bool JsonObjectStringToUStruct(const UStruct *StructDefinition, void *ContainerPtr)
void InitializeParserFromString(const FString &Text) override
static constexpr int64 CheckFlags
bool JsonAttributesToUStruct(const TMap< FString, TSharedPtr< FJsonValue > > &JsonAttributes, const UStruct *StructDefinition, void *ContainerPtr)
void ReadAllProperty(const UStruct *ReferenceClass, void *TargetObject, UObject *DefaultObjectOuter=nullptr) override
bool JsonValueToProperty(const TSharedPtr< FJsonValue > &JsonValue, FNYProperty *Property, void *ContainerPtr, void *ValuePtr)
bool JsonObjectToUStruct(const TSharedRef< const FJsonObject > &JsonObject, const UStruct *StructDefinition, void *ContainerPtr)
bool ConvertScalarJsonValueToProperty(const TSharedPtr< FJsonValue > &JsonValue, FNYProperty *Property, void *ContainerPtr, void *ValuePtr)
void InitializeParser(const FString &FilePath) override
bool bLogVerbose
Definition IDlgParser.h:81
const UClass * GetChildClassFromName(const UClass *ParentClass, const FString &Name)
Definition IDlgParser.h:45
static UObject * CreateNewUObject(const UClass *StructDefinition, UObject *ObjectOuter)
Definition IDlgParser.h:70