I'm trying to create the menu in a display linked to arduino by inserting the elements inside an array like the one below in pseudo-code (javascript).
var menu = {
title: 'main menu',
item: [{
txt: 'item1',
action: function() { // function that will be performed in case such an element is activated
// my code
}
},
{
txt: 'item2',
item: [{
txt: 'item4',
action: function() {
// my code
}
},
{
txt: 'item5',
action: function() {
// my code
}
}
],
action: function() {
// my code
}
},
{
txt: 'item3',
action: function() {
// my code
}
}
]
};
Later this array will be read by a recursive function that will print the menu on the liquid crystal display.
How can i do this to arduino?
Using javascript seems like an operation at anyone's reach, but can you do the same in C / C ++?
Thanks to everyone in advance for the help!
Create a struct with an string, pointer to a function and pointers to the next and previous struct
the string is the text that will be displayed for the option, the function is the function called if the user click the item, and the pointers give you the previous and next itens if the user go up or down
example:
in the header file:
const struct item
{
const char name[16];
void (*func)(void); // Pointer to the item function
const struct item * prev; // Pointer to the previous
const struct item * next; // Pointer to the next
};
in the c file:
const struct item item_ON =
{
" 1 On",
fcn_item_turn_on,
&item_OFF,
&item_PARTIAL
};
const struct item item_PARTIAL =
{
" 2 Partial",
fcn_item_partial,
&item_ON,
&item_OFF
};
const struct item item_OFF =
{
" 3 Off",
fcn_item_turn_off,
&item_PARTIAL,
&item_ON
};
then:
void menu_show()
{
putrsXLCD((rom char *)(*ptr_item).name); // or the LCD print function in your code
}
void menu_inc() {
ptr_item = (*ptr_item).prev;
menu_show();
}
void menu_dec() {
ptr_item = (*ptr_item).next;
menu_show();
}
void menu_fwd() {
(*ptr_item).func(); // execute item function
}
don't forget to initialize the ptr_item with the first item:
ptr_item = &item_ON;
From the looks of it you are trying to create a hierarchical menu system. (As the JSON Object is not an array, but more akin to a tree.)
C++ would probably be easier to implement in because of the STL, I'm not sure on your experience but I'll give a general layout. Design-wise anyways.
#include <functional>
#include <vector>
class MenuTreeNode {
std::string title;
std::vector<MenuTreeNode> children;
std::function<void(int)> action;
public:
MenuTreeNode(const std::string& title, std::function<void(int)> action = {});
// ^ Construct the node, with the action item being optional.
// {} is just an empty function block.
/*
** You can construct with a lambda function, which are pretty useful.
*/
void addChild(MenuTreeNode&& childNode); // append a node to the child array.
void displayStuff() {
// However you display stuff to Arduino...
for (auto& child : this->children) {
child.displayStuff();
}
this->action(); // Call the action.
}
};
I hope that gives you some guidance. The older answer in C is good however doesn't allow for child items that you have in your JSON struct. This might be easier to work with IMO.
Related
I have object of class
class MenuItem {
public:
void updateInputText(string text)
{
this->text += text;
}
string getText() const {
return this->text;
}
void trigger(Event event)
{
switch (event) {
case ENTER:
this->onEnterAction();
break;
}
}
function<void(void)> onEnterAction;
private:
Text text;
void onEnter();
};
I create object and set event handler
MenuItem IP;
IP.onEnterAction = eventOnEnter;
// ENTER - element from enum
IP.trigger(ENTER);
Event handler:
function<void(void)> eventOnEnter = [&] () {
auto selected = next(this->currentMenu.begin(), this->selected);
selected->updateInputText("Hello");
};
And second object of MenuItem
MenuItem nextButton;
next.onEnterAction = [&] () {
Log::write("IP: " + IP.getText());
};
// ENTER - element from enum
next.trigger(ENTER);
But IP.getText() always empty. What i do wrong?
All objects created in one scope (in one function)
Whenever I see something like this
MenuItem nextButton;
next.onEnterAction = [&] () { // Should next be nextButton???
Log::write("IP: " + IP.getText());
};
// ENTER - element from enum
next.trigger(ENTER);
I'm waiting for a disaster to occur. And if you had shown a fully working example you would have noted that your example code might have worked ... so what is the problem.
The main problem is that you capture everything by reference, but from your code I can't se if it should work, but I think not in a large system, the reason is that the captured values have gone out of scope.
std::vector<MenuItem> CreateMenu() {
std::vector<MenuItem> res;
function<void(void)> eventOnEnter = [&] () {
auto selected = next(this->currentMenu.begin(), this->selected);
selected->updateInputText("Hello");
};
MenuItem IP;
IP.onEnterAction = eventOnEnter;
res.push_back(IP);
MenuItem nextButton;
nextButton.onEnterAction = [&] () {
Log::write("IP: " + IP.getText());
};
res.push_back(nextButton);
return res;
}
void Call() {
auto buttons = CreateMenu();
// what is captured at this point???
buttons.back().trigger(ENTER);
}
Most of the captured has now gone out of scope.
Surt gave me a hint... Scope.
All MenuItems pushed to vector, and when i use this function
next.onEnterAction = [&] () {
Log::write("IP: " + IP.getText());
};
I try to get text from empty variable, when i changed function to
next.onEnterAction = [&] () {
auto selectedIP = std::next(currentMenu.begin(), 0);
Log::write("IP: " + selectedIP.getText());
};
I'm trying to use a factory pattern to create different types of "State" objects. The objects are returned with a pointer (State*) but shortly after the objects are created, the values they point to disappear (go to NULL or reset to boolean "true").
The code directly below is where it goes awry, but below that is a complete code sample that compiles and runs. Additionally, I've posted pictures of the debugger values before and after the usleep() command.
I feel like it may have something to do with scope and the garbage collector, but I'm not a C++ expert by any stretch of the imagination. I would have thought my pointer would have kept my referenced object alive.
// relevant code
void execute(){
// Calling the constructor directly as an example
State directState = State("temp", false, false, false);
// Using factory pattern to create a state. Just creating the "default" state as an example
State * factoryState = StateFactory::getDefaultState();
// factoryState -> name is "Reading" in the debugger, but when I try to print it out, it's gone
// Grab the names for easy reference
const char * dName = directState.name;
const char * fName = factoryState -> name;
usleep(1000000 / 100);
// factoryState -> name .... it's vanished?
usleep(1000000 / 100);
// TODO we would run the factoryState -> execute() function here
}
// Complete code example
#include <iostream>
#include <zconf.h>
// Main generic "State" class
class State {
public:
const char * name;
bool isReadable;
bool isExecuting;
bool isFinished;
State(const char name[], bool isReadable, bool isExecuting, bool isFinished){
this -> name = name;
this -> isReadable = isReadable;
this -> isExecuting = isExecuting;
this -> isFinished = isFinished;
}
};
// An inherited class. There will be lots of these eventually
class StateReading: public State { ;
public:
StateReading():State((const char *)"Reading", true, false, false) {}
};
// Factory method that will create lots of the different states
// note that it will be returning a pointer to a "State" object
class StateFactory {
public:
static State* getDefaultState(){
StateReading defaultState = StateReading();
State* state = &defaultState;
return state;
}
};
// Runs the various "States" in a template pattern
class StateExecutor {
public:
State * state;
StateExecutor(){
StateReading stateReading = StateReading();
state = &stateReading;
}
void execute(){
// Calling the constructor directly as an example
State directState = State("temp", false, false, false);
// Using factory pattern to create a state. Just creating the "default" state as an example
State * factoryState = StateFactory::getDefaultState();
// factoryState -> name is "Reading" in the debugger, but when I try to print it out, it's gone
// Grab the names for easy reference
const char * dName = directState.name;
const char * fName = factoryState -> name;
usleep(1000000 / 100);
// factoryState -> name .... it's disappeard?
usleep(1000000 / 100);
// TODO we would run the factoryState -> execute() function here
}
};
// The actual
void loop(StateExecutor stateExecutor) {
// Run the "execute" function of whatever the current state is
// The stateExecutor actually runs the state
stateExecutor.execute();
// Slow the loop down a little. Just for effect
usleep(1000000 / 100);
}
// Simple program to recreate an event loop
int main() {
try {
StateExecutor stateExecutor = StateExecutor();
int count = 0;
do {
loop(stateExecutor);
count++;
// Arbitrarily break out of the loop after 100 events.
} while(count < 100);
} catch (std::exception& e){
std::cout << e.what() << '\n';
}
}
Here are the values directly after the factory created them. All looks good.
Gah! I called usleep() and the factoryState's name field is gone and the bools have reverted to true (cout does this as well). Black magic!
Here:
static State* getDefaultState(){
StateReading defaultState = StateReading();
State* state = &defaultState;
return state;
}
You return a pointer to defaultState. This state however is destroyed when the function returns. Using this pointer later is undefined behavior. You can declare defaultState as static, though i would rather make it a static member.
General problem I'm trying to solve
I'm trying to implement a search tree in Google Apps Script, sorted by pkgName attribute, with the end purpose of comparing imported metadata on a software project against a Sheet containing similar data.
To keep the namespace of the constructor function from being polluted with "private" properties, I used closures.
Implementation
The implementation I have thus far is thus:
SheetDataNode.gs
/**
* Constructor for a SheetDataNode. Takes one, three, or four arguments.
* #param { { package : string, files : { complexity : number, name : string, testingStatus : string }[], rowNumber : number } | string } line data or package name
* #param { string } filename : the files contained in package
* #param { number } complexity : the total number of branches in the file
* #param { number } rowNumber : the row number as this appears in the spreadsheet it is being created from
* #param { string } [ testingStatus ] : the status on the testing of this file. Should be one of the following: NOT_TESTED, FULLY_TESTED, IN_PROGRESS, or PARTIAL
* #returns { SheetDataNode }
* #deprecated This function is not working right now
**/
function SheetDataNode(data, filename, complexity, rowNumber, testingStatus) {
var _pkgName = '';
var _leftChild = null;
var _rightChild = null;
var _beenFound = false;
var _rowNumber = rowNumber;
var _files = [];
// if there's only one argument, it better be an object, having the required fields
if (arguments.length === 1) {
// it should have package field
if ((data.package === undefined) || (data.package !== data.package.toString())) {
throw ReferenceError('only one argument was specified, but it is not an object that contains package');
}
// it should have files field
if ((data.files === undefined) || (!Array.isArray(data.files))) {
throw ReferenceError('Called from the one-arg constructor, so files should be Array');
}
// that files field should itself be an object with the following fields: complexity and name
for (var idx in data.files) {
if (data.files[idx].complexity !== parseInt(data.files[idx].complexity)) {
throw TypeError("complexity should be an integer");
}
if (data.files[idx].name !== data.files[idx].name.toString()) {
throw TypeError("name of file should be a string");
}
}
// sort the array of files
data.files.sort(fileSorter)
// call the initialization function
return SheetDataNode._init(data.package, data.files, parseInt(data.rowNumber));
}
// performing argument checking
if (filename !== filename.toString()) throw TypeError("filename is supposed to be a String")
if ((complexity !== undefined) && (complexity !== parseInt(complexity))) {
throw TypeError("complexity must be a number, or undefined")
}
// call the initialization function, constructing a single file object
return SheetDataNode._init(data.toString(), [{
complexity : complexity,
name: filename,
testingStatus : testingStatus
}])
}
// Helper private function that performs initialization
SheetDataNode._init = function(package, files, rowNumber) {
// bring in the variables
var _pkgName = package;
var _files = files;
var _leftChild = null;
var _rightChild = null;
var _beenFound = false;
var _rowNumber = rowNumber;
// providing a function to add file
_addFile = function(file) {
for (var f in _files) {
if (file.name < _files[f].name) {
_files.splice(f, 0, file)
return
}
}
_files.push(file)
}
return {
getRowNumber : function() { return _rowNumber; },
getPackageName : function () { return _pkgName; },
getFiles: function() { return _files; },
addFile : _addFile,
addFiles : function(files) {
if (!Array.isArray(files)) throw TypeError("files should be an Array")
for (var idx in files) {
_addFile(files[idx])
}
},
getLeftChild : function() { return _leftChild; },
setLeftChild : function(node) {
_leftChild = node;
},
getRightChild : function() { return _rightChild; },
setRightChild : function(node) {
_rightChild = node;
},
insertNode : function(node) {
// set the current node as the head node
var currentNode = this;
// while we are on a non-null node
while (currentNode) {
// if the package of node is the same as that of currentNode
if (currentNode.getPackageName() === node.getPackageName()) {
// simply add the files of node to currentNode._files
currentNode.addFiles(node.getFiles())
return
}
// if the package of node "comes before" that of currentNode, move to the left
if (currentNode.getPackageName() > node.getPackageName()) {
// if the left child of node is defined, that becomes the current node
if (currentNode.getLeftChild()) currentNode = currentNode.getLeftChild()
// else construct it, and we're done
else {
currentNode.setLeftChild(node)
return
}
}
// if the package of node "comes after" that of currentNode, move to the right
if (currentNode.getPackageName() < node.getPackageName()) {
// if the right child of node is defined, that becomes the current node
if (currentNode.getRightChild()) currentNode = currentNode.getRightChild()
// else construct it, and we're done
else {
currentNode.setRightChild(node)
return
}
}
throw Error("Whoa, some infinite looping was about to happen!")
}
}
}
}
UtilityFunctions.gs
/**
* Sorts file objects by their name property, alphabetically
* #param { { name : string } } lvalue
* #param { { name : string } } rvalue
* #returns { boolean } the lexical comparison of lvalue.name,rvalue.name
**/
function fileSorter(lvalue, rvalue) {
if (lvalue.name > rvalue.name) return 1;
return (lvalue.name < rvalue.name) ? -1 : 0;
}
Problem
I'm unit-testing the code, with the failing test case consisting of the following steps :
construct a SheetDataNode node
construct another SheetDataNode otherNode with the same package name as the first, but different filename
insert otherNode into node
expectation: it now has two files
actual: it only has one: the original.
expectation: neither left nor right child nodes were set by this operation
actual : neither left nor right child nodes were set by this operation
The code to do the above looks like this:
QUnit.test("inserting a node having the same package as the node it is assigned to",
function() {
// create the base node
var node = SheetDataNode("example", "main.go", 3, 1)
// insert an other node, with identical package name
var otherNode = SheetDataNode(node.getPackageName(), "logUtility.go", 12, 3)
node.insertNode(otherNode)
// node should contain two files, and neither a left child nor a right child
deepEqual(node.getFiles().map(function(val) {
return val.name
}),
["logUtility.go", "main.go"],
"node contains the right file names")
equal(node.getFiles().length, 2, "A package got added to the node")
ok(!node.getLeftChild(), "leftChild still unset")
ok(!node.getRightChild(), "rightChild still unset")
})
Here is screenshot of the failing assertions:
Remember that the method under test is like this:
insertNode : function(node) {
// set the current node as the head node
var currentNode = this;
// while we are on a non-null node
while (currentNode) {
// if the package of node is the same as that of currentNode
if (currentNode.getPackageName() === node.getPackageName()) {
// simply add the files of node to currentNode._files
currentNode.addFiles(node.getFiles())
return
}
// if the package of node "comes before" that of currentNode, move to the left
if (currentNode.getPackageName() > node.getPackageName()) {
// if the left child of node is defined, that becomes the current node
if (currentNode.getLeftChild()) currentNode = currentNode.getLeftChild()
// else construct it, and we're done
else {
currentNode.setLeftChild(node)
return
}
}
// if the package of node "comes after" that of currentNode, move to the right
if (currentNode.getPackageName() < node.getPackageName()) {
// if the right child of node is defined, that becomes the current node
if (currentNode.getRightChild()) currentNode = currentNode.getRightChild()
// else construct it, and we're done
else {
currentNode.setRightChild(node)
return
}
}
throw Error("Whoa, some infinite looping was about to happen!")
}
The test against the method addFiles, which has this code:
QUnit.test("testing method addFiles",
function() {
// create the base node
var node = SheetDataNode("example", "main.go", 3, 1)
// create an array of files to add
const filesToAdd = [{
name : 'aFile.go',
complexity : 10
}, {
name : 'anotherFile.go',
complexity : 10
}, {
name : 'yetAnotherFile.go',
complexity : 10
}]
// is node.getFiles() an array?!
ok(Array.isArray(node.getFiles()), "node.getFiles() is an array")
// add the files
node.addFiles(filesToAdd)
Logger.log(node.getFiles())
// node.getFiles() should be an Array
ok(Array.isArray(node.getFiles()), "node.getFiles() is still an array")
// node.getFiles should now contain filesToAdd
equal(node.getFiles().length, 1 + filesToAdd.length, "node.getFiles().length increased by the length of the files to add")
})
passes:
, as do the other tests against insertNode, meaning the problem might exist with how we try to reference currentNode in insertNode for array property modification. If so, I have no idea how else to reference, in Google Apps Script, the SheetDataNode to undergo state change
I was able to solve the problem, with inspiration from the MDN docs on closures, by changing the private function property declaration from :
_addFile = function(file) {
for (var f in _files) {
if (file.name < _files[f].name) {
_files.splice(f, 0, file)
return
}
}
_files.push(file)
}
to
function _addFile(file) {
for (var f in _files) {
if (file.name < _files[f].name) {
_files.splice(f, 0, file)
return
}
}
_files.push(file)
}
idk why this works, because I forgot the difference between declaring method like a function variable (what I was doing), and preceding the name of the method with function like it's any other function. I'll have to (re-)learn that...
I'm trying to remove object nested in object in JSON file. However, I can not find any examples on the internet or on the official rapidjson page. My code is written on C++.
I have tried with the following code:
const Value& rootObject= document["root"];
const Value& settingsObject = extensionsObject;
settingsObject.RemoveMember();
But I am not sure what parameter to pass or how to initialize MemberIterator for exact element (as I already know the name of the object I want to remove).
Here is example of the JSON structure:
{
"root": {
"settings": {
"item1": {
"someInfo": 123
},
"item2": {
"someInfo": "string"
}
}
}
}
please chek my code.
Value Child_Obj(kObjectType); // creat object to but it inside another object
Child_Obj.SetObject(); // set it
Child_Obj.AddMember("Child Number", Value(15), Document->GetAllocator()); // add to child key and its value
Value Parent_Obj(kObjectType); // creat parent object that will have inside him the child object
Parent_Obj.SetObject(); // set it
Parent_Obj.AddMember("Parent Number", Value(10), Document->GetAllocator()); // add to parent key and its value
Parent_Obj.AddMember("Child", Child_Obj, Document->GetAllocator()); // add child object to parent object , "Child" as key and Child_Obj as value
// now the file looks like this :
/*
{
"Parent":
{
"Parent Number":10,
"Child":
{
"Child Number":15
}
}
}
*/
// let delete this child
Parent_Obj.RemoveMember("Child"); // here you will give it the key for the object you need to delete
// now its look like this :
/*
{
"Parent":
{
"Parent Number":10,
}
}
*/
// and for fun , if you want to iterate through object , you can do this :
Value::MemberIterator it = Parent_Obj.MemberBegin();
for (; it != Parent_Obj.MemberEnd(); ++it)
{
std::string str = it->name.GetString(); // this will give you the key for current child
}
I have a nested JSON object. I'm trying to build it in a function and add the inner object to the original, but I can't extract the result.
void build_object (Poco::JSON::Object * const result)
{
/* Construct some int/bool/string fields here */
Poco::JSON::Object inner;
inner.set("some_number", 5);
inner.set("some_string", "xyz");
/* This is where it breaks down */
std::string key = "new_object";
result->set("new_object", inner);
/* Then some debugging and testing */
// The new object is printed inside the first -> seems like it's working
result->stringify(std::cout);
printf("result has(key): %i\n", result->has(key)); // true
printf("isObject: %i\n", result->isObject(key)); // false - huh?
printf("isNull: %i\n", result->isNull(key)); // false
printf("isArray: %i\n", result->isArray(key)); // false
Poco::JSON::Object::Ptr ptr = result->getObject(key);
// unsurpisingly fails since the above indicates it's not an object
printf("ptr isNull: %i\n", ptr.isNull()); // true
// ptr->has("some_number"); // throws NullPointerException
// if it's not an object/null/array, it must be a value
Poco::Dynamic::Var v = result->get(key);
// at least one of these things should be true, otherwise what is it?
printf("var isString: %i\n", v.isString()); // false
printf("var isStuct: %i\n", v.isStruct()); // false
printf("var isEmpty: %i\n", v.isEmpty()); // false
printf("var isArray: %i\n", v.isArray()); // false
printf("var isSigned: %i\n", v.isSigned()); // false
printf("var isNumeric: %i\n", v.isNumeric());// false
}
So, I have an inner object that is correctly put into the result, it is being printed via stringify with all the correct values and result->has() is successful. But, according to the result, it is not an object, array, or null, so you should be able to get it with var. But, once it's gotten from var, it's not a string, struct, array, or number, and it's also not empty. The inner object seems to exist and not exist at the same time.
So, how do I put this object into my result? And how do I get it out?
Thanks
note: I've seen this thread Correct usage of Poco C++ JSON for parsing data, but it's building the JSON object from string and then parsing it. I suppose I could build everything as a string and convert to the Poco Object at the last step, but I'm still curious why the above is happening. Also, using result->set() and result->get() are a cleaner, less hack-y solution than going through a string.
References: Poco JSON Doc,
Poco Dynamic Var Doc
Poco::JSON Objects and Arrays are held as shared pointers internally by default (optimization to avoid values copying) and everything is Dynamic::Var, so it works for both pointers and values. When you insert an Object as value it works because Dynamic::Var will hold pretty much anything, but the problem you experience when inspecting it comes from the fact that internal comparison does not return true for Object values because it compares only with default type - Poco::SharedPtr<Poco::JSON::Object>.
Here's a workaround:
void build_object (Poco::JSON::Object * const result)
{
// smart pointer, so don't worry about cleaning up
Poco::JSON::Object::Ptr inner = new Poco::JSON::Object;
inner->set("some_number", 5);
inner->set("some_string", "xyz");
std::string key = "new_object";
result->set(key, inner);
printf("isObject: %i\n", result->isObject(key)); // true
}
I have opened a github issue to alleviate this caveat.
I have been trying to create json file having nested object using poco library. Finally able to do with Poco::Json::Array.
Please find the posted code sinippet. Hope it will help. Json output attached with post.
#include "Poco\JSON\JSON.h"
#include "Poco\JSON\Stringifier.h"
#include "Poco\JSON\Object.h"
#include "Poco\Dynamic\Var.h"
using namespace std;
using Poco::JSON::Stringifier;
using Poco::JSON::Object;
using Poco::JSON::Array;
void makeJsonNestedObject()
{
Object RootObj(true);
Array FLArray;
for(int i=0; i<3; i++)
{
Object::Ptr FirstLevelArrayNode = new Poco::JSON::Object(true);
TCHAR strNameBuff[15];
_stprintf(strNameBuff, _T("%s_%d"),_T("Servername"),i);
std::basic_string<TCHAR> strName = strNameBuff;
FirstLevelArrayNode->set("HostName", strName);
FirstLevelArrayNode->set("Overall Impact", "Dummy Data");
Array SLArray;
for(int j=0; j<3;j++)
{
Object::Ptr SecondLevelArrayNode = new Poco::JSON::Object(true);
TCHAR attr1NameBuff[15];
TCHAR attr2NameBuff[15];
_stprintf(attr1NameBuff, _T("%s_%d"),_T("AttrOne"),j);
_stprintf(attr2NameBuff, _T("%s_%d"),_T("AttrTwo"),j);
std::basic_string<TCHAR> attr1Name = attr1NameBuff;
std::basic_string<TCHAR> attr2Name = attr2NameBuff;
SecondLevelArrayNode->set("Attribute", attr1Name);
SecondLevelArrayNode->set("SubAttribute", attr2Name);
Poco::Dynamic::Var obj(SecondLevelArrayNode);
SLArray.add(obj);
}
FirstLevelArrayNode->set("Attribute_Details",SLArray);
Poco::Dynamic::Var FLArrayNodeobj(FirstLevelArrayNode);
FLArray.add(FLArrayNodeobj);
}
std::ostringstream os;
std::cout <<"before stringlify.." << std::endl;
FLArray.stringify(os, 2);
std::cout << os.str() << std::endl;
}
Json output:
[
{
"HostName" : "Servername_0",
"Overall Impact" : "Dummy Data",
"Attribute_Details" : [
{
"Attribute" : "AttrOne_0",
"SubAttribute" : "AttrTwo_0"
},
{
"Attribute" : "AttrOne_1",
"SubAttribute" : "AttrTwo_1"
},
{
"Attribute" : "AttrOne_2",
"SubAttribute" : "AttrTwo_2"
}
]
},
{
"HostName" : "Servername_1",
"Overall Impact" : "Dummy Data",
"Attribute_Details" : [
{
"Attribute" : "AttrOne_0",
"SubAttribute" : "AttrTwo_0"
},
{
"Attribute" : "AttrOne_1",
"SubAttribute" : "AttrTwo_1"
},
{
"Attribute" : "AttrOne_2",
"SubAttribute" : "AttrTwo_2"
}
]
},
{
"HostName" : "Servername_2",
"Overall Impact" : "Dummy Data",
"Attribute_Details" : [
{
"Attribute" : "AttrOne_0",
"SubAttribute" : "AttrTwo_0"
},
{
"Attribute" : "AttrOne_1",
"SubAttribute" : "AttrTwo_1"
},
{
"Attribute" : "AttrOne_2",
"SubAttribute" : "AttrTwo_2"
}
]
}
]