I have a JSON object that I am getting from my server that looks something like this:
{
"state":"1",
"player1": {
"alias":"Player Name",
"ready":"0"
}
}
I am able to get the JSON, parse it into a FJsonObject, and retrieve any number or string in the first level of the JSON object using this code to serialize:
TSharedPtr<FJsonObject> JsonParsed;
TSharedRef<TJsonReader<TCHAR>> JsonReader = TJsonReaderFactory<TCHAR>::Create(json);
if (FJsonSerializer::Deserialize(JsonReader, JsonParsed))
//Use JsonParsed
And this code to read strings:
FString AJSONContainer::getStringWithKey(FString key)
{
return storedJSON->GetStringField(key);
}
Side Note:
AJSONContainer is just an Actor class that I use to call these functions from Blueprints.
That's all fine and dandy, but when I try to read things from the second level, things don't work.
I wrote this code to get the next level down:
TSharedPtr<FJsonObject> nested = storedJSON->GetObjectField(key);
But all calls to get fields of nested return nothing.
nested->GetStringField(anotherKey); //Nothing
So, for example, with the above JSON, this:
TSharedPtr<FJsonObject> nested = storedJSON->GetObjectField("player1");
FString alias = nested->GetStringField("alias");
alias has no value when I print it to the console.
Am I doing something wrong? Why isn't the second-level JSON working?
Don't know if you got it sorted out, but I found a pretty nasty function that works for nested objects and, also, for arrays altogether. And it gives you a USTRUCT, so you don't have to use the functions that get values by Keys (I don't like them since they're very error prone). Instead, you'll have type safety!
FJsonObjectConverter::JsonObjectStringToUStruct
Here are the docs and another question answered on UE4 AnswerHub
Basically, you create the target USTRUCT (or USTRUCTs for nested JSONs), mark all properties with UPROPERTY, so Unreal knows their names, and use this function. It will copy the values by matchmaking them. It copies even the arrays! =D
Example
I'll call the JSON FString to be deserialized Json and it's structure is like the one below. It contains a nested object and an array, to make things interesting.
{
"nested" : {
"id" : "654asdf",
"name" : "The Name"
},
"foo" : "foobar",
"bar_arr" : [
{ "barfoo" : "asdf" },
{ "barfoo" : "qwer" }
]
}
Before converting, we need to create the USTRUCTs from inside out (so we can reference inner on the outer). Remember to always use F for struct names.
USTRUCT()
struct FNested
{
GENERATED_USTRUCT_BODY()
UPROPERTY()
FString id;
UPROPERTY()
FString name;
};
USTRUCT()
struct FBar
{
GENERATED_USTRUCT_BODY()
UPROPERTY()
FString barfoo;
};
USTRUCT()
struct FJsonData
{
GENERATED_USTRUCT_BODY()
UPROPERTY()
FNested nested;
UPROPERTY()
FString foo;
UPROPERTY()
TArray<FBar> bar_arr;
};
The conversion will go like this:
FJsonData JsonData;
FJsonObjectConverter::JsonObjectStringToUStruct<FJsonData>(
Json,
&JsonData,
0, 0);
Now, you are able to access all the properties as in standard C++ structs. Eg., to access one of the barfoos:
FString barfoo0 = JsonData.bar_arr[0].barfoo;
I have not tested it with int and float in the JSON, but since it copies even arrays, I believe that would work also.
for (auto currJsonValue = JsonObject->Values.CreateConstIterator(); currJsonValue; ++currJsonValue)
{
// Get the key name
const FString Name = (*currJsonValue).Key;
// Get the value as a FJsonValue object
TSharedPtr< FJsonValue > Value = (*currJsonValue).Value;
TSharedPtr<FJsonObject> JsonObjectIn = Value->AsObject();
}
The Json Object nested can be accessed by GetObjectField or the code I posted.
As I commented calling GetField<EJson::Object> instead of GetObjectField is the solution.
So this code will get your nested json:
TSharedPtr<FJsonValue> nested = storedJSON->GetField<EJson::Object>("player1");
TSharedPtr<FJsonObject> nestedParsed = nested->AsObject();
FString alias = nestedParsed->GetStringField("alias"); // alias == "Player Name"
Related
I have a "fixed list" in a class, I know all the objects that are in that list and I can refer to the objects through the names. The list is immutable, except for the parameters of every object contained in this list.
Something like this
class Object {
String name; // Know prior to build time
String color; // Can change
Object(this.name, this.color);
}
class MyClass {
List<Object> myList = [Object('apple', 'red'),
Object('banana', 'yellow'),
Object('mango', 'orange')];
}
I would like to access the members of the list like
final test = MyClass();
test.myList.apple.color = 'green'
Is there some "special list" that consent this type of access?
I tried using ENUM but they are not suitable for this problem.
A Map<String, String> would be better suited for what you are trying to do.
final colors = HashMap<String, String>();
colors['apple'] = 'green';
With an enum like say:
public enum Authors
{
KirkPatrick,
JasmineLliki,
NatashaKakuvi
}
Now using this helper method I can easily get the corresponding List<string> values:
public class EnumHelper<T> where T : Enum
{
public static List<string> ToList() => Enum.GetNames(typeof(T)).ToList();
}
My desired output would be something like:
new List<string>() {"Kirk Patrick", "Jasmine Lliki", "Natasha Kakuvi"};
instead of
new List<string>() {"KirkPatrick", "JasmineLliki", "NatashaKakuvi"};
that the method outputs.
It seems that you are trying to, effectively, assign string values to Enums.
If that is your intention, check out this StackOverflow question, may be one of the cases there will be helpful.
If you want to proceed with your current Enums instead, you can try using answers from this question to add spaces in a string with capital letters.
There is a set of classA objects, containing several data fields, and inside each classA object, there is a set of classB objects containing additional data fields.
At some point I want to generate a CSV file.
My initial approach is to implement a .toCSV() in both classA and classB and do the following in main.cpp:
string completecsv;
foreach(classA ca, setOfClassA)
completecsv.append(ca.toCSV());
And inside classA.toCSV()
string csv;
csv.append(field1);
csv.append(field2);
csv.append(...);
foreach(classB cb, setOfClassB)
csv.append(cb.toCSV());
return csv;
And finally in classB.toCSV()
string csv;
csv.append(field1);
csv.append(field2);
csv.append(...);
return csv;
Now, my other approach was to create a class named something like OutputManager, that is in charge of everything regarding the CSV generation, keeping the MVC pattern more clearly separated.
Any thoughts regarding this two approaches?
Many thanks.
If there are lots of properties of classA and classB that make sense to include in a report, regardless of the report format (CSV, XML, Json, etc.), then it sounds like classA and classB are actually data classes without much logic.
If that is the case, I'd keep the report generation separate from them to easily make it possible to extend the reporting mechanism with other output formats if needed.
To cater for a hierarchical output format (like XML or Json), it might make sense to let types that you read properties from to also expose a 'children' property, so that it can be looped through and applied recursively.
For each type that gets output, it could expose a name-value collection of its 'outputtable' data that the OutputManager then chooses what to do with.
Something like this, where the OutputManager would get the 'root' IOutputtable (classB in this case) and just loop over its name-values and then do the same with its children, recursively.
interface IOutputtable
{
NameValueCollection Items { get; }
IEnumerable<IOutputtable> Children { get; }
}
class A : IOutputtable
{
private int _baz;
public NameValueCollection Items {
get {
return new NameValueCollection() {
{ "baz", _baz.ToString() }
};
}
}
public IEnumerable<IOutputtable> Children {
get {
return Enumerable.Empty<IOutputtable>();
}
}
}
class B : IOutputtable
{
private int _foo;
private string _bar;
private List<A> _as = new List<A>();
public NameValueCollection Items {
get {
return new NameValueCollection() {
{ "foo", _foo.ToString() },
{ "bar", _bar }
};
}
}
IEnumerable<IOutputtable> Children {
get { return _as; }
}
}
I have a custom template ~/Views/Shared/EditorTemplate/String.cshtml and it seems to be causing the Exception:
The model item passed into the dictionary is of type 'Proj.Models.EnumType', but this dictionary requires a model item of type 'System.String'.
It seems to only happen to Enums. It also goes away if I remove the template. The template doesn't seem to cause it, I don't think it's even making it that far. I can put ANYTHING in there and the exception is the same.
So... can I not use an #Html.EditorFor with a model with an enum if I have a custom template?
Some context:
Model:
namespace Proj.Models
{
public enum EnumType
{
A = 0,
B = 1,
C = 2,
}
public class Mod
{
[Required]
public String Name;
[Required]
public EnumType Letter;
}
}
View:
#model Proj.Models.Mod
#Html.EditorFor(m=>m) // Exception happens here
Here is what I found to work for me.
In your template, make sure you declare your model as nullable enum type. Then, in code, check to see if it has a value and based on that do appropriate formatting.
#inherits System.Web.Mvc.WebViewPage<Proj.Models.EnumType?>
#{
Proj.Models.EnumType v = Model.HasValue ? Model.Value : {*some-default-value*};
#Html.{*Your-formatter-here*};
}
I'm working on an Android app. I created (in a separate .java file) an object like so:
class RRS_Location {
String tagname;
String href;
// Constructor
public RRS_Location(String tagname, String href) {
this.tagname = tagname;
this.href = href;
}
public String getTagname() {
return tagname;
}
public String getHref() {
return href;
}
}
Within an activity, I've declared a List of these items
List<RRS_Location> rrs_list;
I'm getting a NullPointerException when I try to add an RRS_Location object to the list. I'm doing so using this code
rrs_list.add(new RRS_Location(e1, e2));
I've used Toast to echo back to me that I have valid Strings e1 and e2. Any ideas on why I'm getting the exception? TIA!
Are you instantiating rrs_list before making the call to add?
List<RRS_Location> rrs_list = new ArrayList<RRS_Location>();
If not, this is why you are getting a NullPointerException, you are attempting to invoke a method on a null object.