Converting json into member accesses and function calls C++ - c++

I have a custom list class capable of storing recursive lists. There is a json file that is parsed during runtime which will contain information about what functions to call on what lists. I am using nlohmann::json library
For example:
class Game{
CustomListElement setup;
}
class CustomListElement{
CustomListElement getElementAt(std::string){
//returns element
}
CustomListElement upfrom(int start){
//create CustomList from int
}
}
This is the json:
{
"setup": {
"game-info": {
"Rounds": 10,
"Players": 5
}
}
"list": "setup.Rounds.upfrom(1)",
}
I can already parse the first part of the json. So the CustomList setup field in the game class, already stores a map of { "game-info", { {"Rounds", 10}, {"Players", 5} } }. Each of these objects is also of type CustomList and they can be accessed through getElementAt().
But the json is parsed during runtime so I don't know what the names would be further down and what functions I would need to call. These are determined by "list" in the json. So how do I dynamically convert something of the format "setup.Rounds.upfrom(1)" into actual member accesses and calls?

Related

How to modify a json object using json11 library?

I have a already json11 object build:
Json my_json = Json::object {
{ "key1", "value1" },
{ "key2", false },
{ "key3", Json::array { 1, 2, 3 } },
};
And I want to add a new value to key3 array like this:
my_json["keys3"].push_back(4);
How I can achieve that? I can't see anything to modify objects (all operator to access values are const!)
Unfortunately it seems you cannot modify directly an instance of Json.
It's an opaque wrapper around a JsonValue that is inaccessible.
Anyway, note that a Json::object is a std::map<std::string, Json>. You can create a copy of your original Json::object as it follows:
Json::object json_obj = my_json.object_items();
Then the key keys3 contains a Json::array, that is nothing more than a std::vector<Json>.
You can modify it as it follows:
json_obj["keys3"].push_back(4);
Finally you must create a new Json from your Json::object and that's all:
Json another_json = json_obj;
Quite expensive an operation.
I suspect the right way is to create your objects step by step and at the very end of your process create an instance of a Json.
I found next issues on github about this question:
[https://github.com/dropbox/json11/issues/20]: more o less the same that skypjack explain
The Json type is immutable, but the Json::object type is just a
std::map, so your code would work if the first line created a
Json::object instead. You can use that map to build whatever data you
want, then wrap it in as Json(data) when you're done modifying it. You
can also extract the map from a Json using object_items(), copy it,
mutate it, and use it to create a new Json, similar to a builder
pattern.
[https://github.com/dropbox/json11/issues/75]: This one is very interesting because explain why it's not possible to modify a json
The Json type is intended to be an immutable value type, which has a
number of advantages including thread safety and the ability to share
data across copies. If you want a mutable array you can use a
Json::array (which is just a typedef for a vector) and mutate it
freely before putting it into a Json object.
If you are using json11 you can do it like this:
Json json = Json::object
{
{
"num_neurons_in_each_layer", Json::array{ 1000, 1000, 10, 10 }
},
{
"non_editable_data",
Json::object
{
{"train_error", -1.0 },
{"validation_error", -1.0 }
}
}
};
Json* p_error = const_cast<Json*>(&json["non_editable_data"].
object_items().find("validation_error")->second);
*p_error = Json(2.0); //"validation_error" has been modified to 2.0
p_error = nullptr;
delete p_error;

Proper placement of "toCSV" function: In each object, or in a generic class?

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; }
}
}

C++ Nested JSON in Unreal Engine 4

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"

Looking for testable design in described case

I have a system, which gets lists of objects from external system in some ABC-format, converts it to internal representation and passes to external service:
class ABCService() {
public ABCService(ExtService extService) {
this.extService = extService;
}
public void do(ABCData [] abcObjs) throws NoDataException {
if (abcObjs.length == 0) {
throw NoDataException();
} else {
List<Data> objs = new ArrayList<>();
for (ABCData abcObj : abcObjs) {
Data obj = Parser.parse(abcObj); // static call
objs.add(obj);
}
extService.do(objs);
}
}
}
When it comes to testing ABCService, we can test two things:
If no data is passed to "do", service throws an exception;
If some data is passed to "do", service should call extService and pass exactly the same number of objects, it has received from test caller.
But, though Parser factory is also tested, there is no guarantee, that output "objs" array is somehow connected to input abcObjs (e.g. method has created list with the predefined length, but method "forgets" to populate the list).
I my opinion those two test cases don't fully cover method's workflow leaving some of it dangerously untested.
How to modify ABCService design to increase it's testability?
The major testing difficulty in this code is that you have two collaborators and one of them is static.
If you can convert your Parser to a non-static (or perhaps wrap it in a non-static) and inject that as you do the extService, you could test that the parser is called the right number of times with the right arguments. Stubbing in the return values from the parser, you could also verify that your extService is called with the appropriately transformed objects instead of just the correct number of objects.
The problem you encountered is trying to handle two tasks in one function. The function do can be logically separated into two different member functions, so that you can use unittest for each of them.
By using refactoring, you can extract out the parsing and populating logic into another member function.
class ABCService() {
public void do(ABCData [] abcObjs) throws NoDataException {
extService.do(populateList(abcObjs));
}
List<Data> popuateList(ABCData[] abcObjs) {
if (abcObjs.length == 0) {
throw NoDataException();
} else {
List<Data> objs = new ArrayList<>();
for (ABCData abcObj : abcObjs) {
Data obj = Parser.parse(abcObj); // static call
objs.add(obj);
return objs;
}
}
}
while your current unittest can still remain for the "do" function, and additionally, you can add a unittest case for "populateList" function to ensure it generate correct data list

Trying to create a List of user-defined objects, getting NullPointerException

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.