Using recursion to modify JSON - arrays

I am using AngularJS template to show a JSON as a list/tree structure.
My JSON depth is unknown. In most of the cases my code works. There is just one case I have encountered so far that does not work.
My JSON sample:
[
{
"type":"image",
"bitmapname":{
"text":"Image_12.bmp"
},
"command":[
{
"type":"close",
"text":"1234"
},
{
"type":"place",
"text":"company"
},
{
"type":"key",
"key": "Tab"
},
{
"type":"keyboard",
"text":"welcome",
"command": [
{
"type":"open",
"text":"1234"
},
{
"type":"home",
"text":"company"
}
]
}
]
},
{
"type":"image",
"bitmapname":{
"text":"image_14.bmp"
},
"command":{
"type":"move",
"text":"right"
}
}
]
The output is below:
AngularJS code:
<script type="text/ng-template" id="xml.html">
<!-- XML is coming from controller as a JSON shown in my sample -->
<ul>
<li ng-repeat="item in XML">
[[ item["type"] ]]
<div ng-include=" 'xml.html' "
onload="XML = item.command">
</div>
</li>
</ul>
</script>
<div ng-include=" 'xml.html' ">
As you may noticed last list item is empty because that "command" is not an Array
I am trying to create a recursive function that will go through the JSON and create arrays on one element "command": [{...}] in places where I have "command": {...}
Can anyone suggest a solution that will convert the object to array of object wherever I have length-one object?
NOTE
I have no control over JSON being sent from Django backed. Python is using xmltodict extension to convert XML to Dictionary. Whenever XML tags have no children it does not create a list. It only creates a dictionary.

This isn't a very generic approach, but should work for your requirement:
function fixCommands(values) {
values.filter(function (v) {
return 'command' in v;
})
.forEach(function (v) {
if(v.command.constructor === Array) {
fixCommands(v.command);
} else {
v.command = [v.command];
}
});
}
jsfiddle example

You can check the first item that is it object or no if it was an object you can do a loop on it otherwise it's in other type that you have mentioned at top.for example
if(arr[i].command.length===undefined) -> so is string part
else -> is array part

Related

Access key: [ { "Key":"Value"} ] inside of Object that is inside of another Array?

I am trying to access the Options key value and get the Name and Value in my jsx but I am unable to use map on field.Options (when I hover over Options there is a question mark next to it).
I have tried field.Options[0].Name , field.Options["Name"], field.map().
I am just confused on how to access those values. An explanation would be appreciated.
I have an Array[] of Objects{} like below and I am trying to get the "Options" objects values.
{
"Type": "rating",
"Key": "professionalism",
"LabelBranding": "api#professionalism#placeholder",
"Label": "Professionalism:",
"Required": false,
"Options": [
{
"Name": "Smiley",
"Value": "0123456789"
}
]
},
I cant use a (for of ) in JSX. So I am wondering how to access this?
I think you were close there. Have you tried this?
{field.Options.map(block => (
<p key={block.Name}>{block.Value}</p>
))}
Since you want to iterate the field.Options array.
This was a case of not checking if the "Options" array was there. The 1st object that was coming back did not have the "Options" key value like the rest of them. So I added a conditional around it and it worked swimmingly.
{field.Options !== undefined
&& field.Options.map((block) => {
if (block.Value !== undefined || block.Name !== undefined) {
return (
<ul>
<li key={block.Name}>{block.Value}</li>
<li key={block.Name}>{block.Name}</li>
</ul>
);
}
return null;
})
}

AngularJS - Using JSON contents to determine name of string for replacement in HTML contents

I'm creating a new Web application using AngularJS. it consists of a main page with a side menu, whose structure is pre-defined via a JSON file. The JSON would look something like the following (highly simplified!!):
G_Main_Menu = {
"Management":
[{"Command":"DoThis","Label":"Label_DoThis"},
{"Command":"DoThat","Label":"Label_DoThat"}],
"Others":
[{"Command":"DoOther","Label":"Label_DoOther"}]
}
On the other hand, within the HTML page I would be deploying labels extracted from the database (it is a multi-lingual application and hence the contents of the labels would depend on the language selected by the user):
...{{ThisIstheLabelFor_DoThis}}...
...
...{{ThisIstheLabelFor_DoThat}}...
...
...{{ThisIstheLabelFor_DoOther}}...
The JSON as received from the database would look like:
{"Management":
{"Label_DoThis":"This is the explicit contents of label DoThis",
:
"Label_DoThat":"This is the explicit contents of label DoThat",
:
},
"Others":
{"Label_DoOther":"This is the explicit contents of label DoOther"
}
}
So, I have a JSON that contains a string specifying the name of the element contained in a second JSON.
How could I implement such indirect extraction?
Thanks in advance.
You can search for the label in the translation JSON using a function
{{::translate(ThisIstheLabelFor_DoThis, category)}}
where category could be "Management" etc.
And the translate function could be implemented like this:
$scope.translate = function(label_name, category){
return TranslateJson[category][label_name];
}
let gMainMenu = {
"Management": [{
"Command": "DoThis",
"Label": "Label_DoThis"
},
{
"Command": "DoThat",
"Label": "Label_DoThat"
}
],
"Others": [{
"Command": "DoOther",
"Label": "Label_DoOther"
}]
};
let db = {
"Management": {
"Label_DoThis": "This is the explicit contents of label DoThis",
"Label_DoThat": "This is the explicit contents of label DoThat",
},
"Others": {
"Label_DoOther": "This is the explicit contents of label DoOther"
}
};
Object.values(gMainMenu).forEach(menu => {
menu.forEach(item => {
let explicitContent = null;
for (let d of Object.values(db)) {
let key = Object.keys(d).find(k => k === item.Label);
if(key){
explicitContent = d[key];
}
}
item.Label = explicitContent;
});
});
console.log(gMainMenu);

angularjs filter multidimensional object json

I wnt to use ng-repeat to display a list filtered by an object value. Here is a plukr of my attempt https://plnkr.co/edit/vD4UfzM4Qg7c0WGTeY18?p=preview
The following returns all of my JSON names as expected.
<li ng-repeat="item in collection_data">{{navitem.name}}</li>
now i want to filter and only show the names of the items that have "foreign_lang": "es", like in this json snippet
{
"id": "ddb06ba2-6348-4d45-9e63-a6fa3632e5c2",
"created_at": "2015-10-12T18:34:15.668Z",
"updated_at": "2016-04-14T15:55:37.433Z",
"custom_attributes": {
"Display Name": "Activos en EspaƱol",
"foreign_lang": "es",
"display_boxes": "false"
},
},
so i made this filter function
$scope.filterByDisplay = function() {
$filter('filter')($scope.collection_data, ['foreign_lang', 'es']);
}
and called it like this.
<li ng-repeat="item in collection_data" | filter: filterByDisplay>{{navitem.name}}</li>
I did not get any console errors but i got nothing returned.
How do I properly filter through this collection to only return items with 'foreign_lang', 'es' as a value in the json? See the plunkr to see a working example https://plnkr.co/edit/vD4UfzM4Qg7c0WGTeY18?p=preview
Third attempt (since the question was revised). Use the filter function to check each object individually, and returning only those that pass the truth test.
$scope.filterByDisplay = function(value) {
return (value.content)
&& (value.content.custom_attributes)
&& (value.content.custom_attributes.foreign_lang === "es");
}
Updated Plunk - Using Filter Function

ng-repeat and nested array in json

i have some problems accessing some values stored in a json with the directive ng-repeat.
the json is formatted properly (checked it with a json checker) and it's called via $http service. unfortunately i can't change the format of json, retrieved through the wordpress-json-api plugin.
the json (with some cuts):
{
"status": "ok",
"count": 10,
"count_total": 24,
"pages": 3,
"posts": [
{
"id": 108,
"type": "sensor",
"slug": "ert",
"custom_fields": {
"sensor_id": [
"er"
],
"coords": [
"{\"address\":\"\",\"lat\":55.39979700000003,\"lng\":10.430533275390644,\"zoom\":12}"
]
}
}
//etc other posts following, json correct
Now i have a problem accessing the single values inside coords. i complained too about serializing data in db, but i'd like anyway to get lng and lat out of that array
<ul>
<li ng-repeat="post in sensor.posts">
<h4>name : {{post.id}}</h4> //ok
<h4>all custom fields : {{post.custom_fields}}</h4> //good-this-too
<h4>custom field lat : {{post.custom_fields.prova_coords[0]}}</h4> //works but can't go on
</li>
</ul>
the ouput of last line is:
custom field lat : {"address":"","lat":55.39979700000003,"lng":10.430533275390644,"zoom":12}
but now i'm stuck..can't retrieve single lat and long values
You need to use $scope.$eval(coordsString)
Here's a test: http://jsfiddle.net/mikeeconroy/Rnc7R/
var objString = "{\"address\":\"\",\"lat\":55.39979700000003,\"lng\":10.430533275390644,\"zoom\":12}";
$scope.coords = $scope.$eval(objString);
Then you can refer to coords.lat or coords.lng in your template like so:
<div ng-controller="myCoordsCtrl">
Lattitude: {{coords.lat}}<br>
Longitude: {{coords.lng}}
</div>
Since this is happening within an ngRepeat you may need to use an Angular filter to rectify the string on the fly
<li ng-repeat="post in sensor.posts | myRectifyCoordsFilter">
EDIT:
Don't use the filter like in the above example, using filters to modify $scope is not a good situation. You'll need to modify your $scope prior to using it in the ng-repeat
$http.get().success(function(data){
angular.forEach(data,function(val,key){
this.coords = $scope.$eval(val.coords[0]);
},data);
$scope.sensor = data;
});

Wrap items in backbone collection?

I keep running into some confusing solutions and unclear ways to wrap items that match into a div using backbone.
I am just building a simple example for myself, and would like to nest all models in a collection that have the same attribute team, using a comparator works well in organizing the list, but for the life of me I can't find a clear solution to wrapping each so that I have more control over the list of players inside the team.
There has to be a clear easy solution for a beginner like me. I really just want to keep things as clean and simple as possible. My desired html result looks like below.
<div class="pacers">
<li>Paul</li>
<li>Roy</li>
</div>
<div class="bulls">
<li>Kirk</li>
<li>Taj</li>
</div>
Based on a backbone friendly json array like below.
[
{
"name": "Paul",
"team": "pacers"
},
{
"name": "Kirk",
"team": "bulls"
},
{
"firstname": "George",
"team": "pacers"
},
{
"name": "Taj",
"team": "bulls"
}
]
So using a comparator is awesome I just write this comparator : 'team' and it handles the list order for me, cool, but I dont have much control I would like to wrap the list in a more hierarchical system.
Another approach:
If you are using underscore's templates this could be one way of doing it. You can use underscore's groupBy function to group the list based on teams.
var teams = [
{
"name": "Paul",
"team": "pacers"
},
{
"name": "Kirk",
"team": "bulls"
},
{
"firstname": "George",
"team": "pacers"
},
{
"name": "Taj",
"team": "bulls"
}
];
var groupedList = _.groupBy(list, function(l){
return l.team;
});
console.log(JSON.stringify(groupedList));
This is how it would be grouped.
{
"pacers": [
{
"name": "Paul",
"team": "pacers"
},
{
"firstname": "George",
"team": "pacers"
}
],
"bulls": [
{
"name": "Kirk",
"team": "bulls"
},
{
"name": "Taj",
"team": "bulls"
}
]
}
You can then use for each loop and in template and generate HTML in following way. The groupedList is passed as teams to below template.
<%
_.each(teams, function(team, teamName){
%>
<div class="<%=teamName%>">
<%
_.each(team, function(player){
%>
<li><%=player.name%></li>
<%
});
%>
</div>
<%
});
%>
This would generate the HTML the way you expected.
NOTE:
The code snippets are given considering underscore templating, you might have to make changes based on what you use. Hope it helps.
Correct me if I am wrong the problem being described relates more to controlling the contents of each item in relation to it's model as well as how to simply render them in groups.
1) Niranjan has covered grouping out the data into separate lists but remember that this list returned is not a Backbone construct.
2) As per the manual the '_.groupBy' method should be available to you via the collection i.e.:
myCollection.groupBy(etc);
3) I would personally consider mapping the results of the groupBy back into models and pass each and every model into a separate view and render them from within the main list view.
var CollectionView = Backbone.View.extend({
initialize : function () {
// Note: I am pretending that you have a real collection.
this.collection.fetch().then(
this.addAll(true);
);
}
addOne : function (model) {
// call .render individual template items here for each model.
var view = new ItemView(model);
this.$el.append(view.render();
},
addAll : function (groupOpts) {
var col = this.collection;
if(groupOpts === true) {
// Do grouping (or do it in the model). Maybe put back into new collection?
}
_.each(col, function(model) {
this.addOne(model);
}, this);
},
render : function () {
// Render your template here.
}
});
var ItemView = Backbone.View.extend({
render : function () {
}
});
Not a complete example but that's the general pattern I would follow when attempting the same thing. Having an individual view/model for each item, in my opinion, gives you more control.
This could be handled in a pretty crazy view template (depends on your template language)... or you could use a simpler template/view and just make some more crazy collection queries (first using a pluck to get the team, de-dupping that array, then running some where's for each of the teams... but you can see how this gets crazy)
I'd vote for the view and view template should handle this... what are you using? Jade? Mustache?
Something like this - logical psuedo code here since I don't know your template language:
var team;
forEach player in players
if(!team) {
set team = player.team
print open holder and then the first row
} (team !== player.team {
set team = player.team
print close of previous holder, then open holder and then the first row of new team
} else {
print just the player row
}
Even so, you can see how this is a bit dirty in and of itself... but what you are describing is a view/presentation concern, and you can do it here with no new additional loops and maps and wheres (like you'd have to do if you did it in the data layer before calling the views)

Resources