I am trying to use Tabulator to create a list of tickets, The data is imported via AJAX url from the ticket system as a JSON as below.
{
"results": [
{
"cc_emails": [
"ram#freshdesk.com",
"diana#freshdesk.com"
],
"fwd_emails": [],
"reply_cc_emails": [
"ram#freshdesk.com",
"diana#freshdesk.com"
],
"ticket_cc_emails": [
"ram#freshdesk.com",
"diana#freshdesk.com"
],
"fr_escalated": false,
"spam": false,
"email_config_id": null,
"group_id": 35000204315,
"priority": 1,
"requester_id": 35020281588,
"responder_id": 35004154466,
"source": 2,
"company_id": null,
"status": 2,
"subject": "Support Needed...",
"association_type": null,
"to_emails": null,
"product_id": null,
"id": 188261,
"type": null,
"due_by": "2019-09-17T15:12:07Z",
"fr_due_by": "2019-07-01T15:12:07Z",
"is_escalated": false,
"description": "<div>Details about the issue...</div>",
"description_text": "Details about the issue...",
"custom_fields": {
"cf_category": null,
"cf_firstname": null,
"cf_surname": null,
"cf_user_trainging": null,
"cf_email_address": null,
"cf_office_365": null,
"cf_start_date": null,
"cf_permission_level": null,
"cf_hardware_type": null,
"cf_additional_information_specsoftware_etc": null,
"cf_vpn_access_required": false,
"cf_securitydistribution_group_membership": null,
"cf_mapped_network_driveslogin_script": null,
"cf_printers": null,
"cf_phone_extension": null,
"cf_ddi": null,
"cf_phone_group_membership": null,
"cf_user_who_requires_the_equipment": null,
"cf_requirment_date": null,
"cf_correctclosureused": null,
"cf_location": "A1"
},
"created_at": "2019-06-24T15:11:47Z",
"updated_at": "2019-06-24T15:59:00Z",
"associated_tickets_count": null,
"tags": []
}
],
"total": 1
}
The problem is the "custom_fields" is a JSON Object inside the main JSON object, is there a way to flatten this data out and display this as all one row in Tabulator? Any help appreciated?
My current result in Tabulator is it returns [object Object] for the custom_fields column. I would like to be able to see each of custom_fields in the row.
Handling Nested Data
There is no need to flatten the object, Tabulator can handle nested data for columns, if you use dot notation in the field name:
var table = new Tabulator("#example-table", {
columns:[
{title:"Category", field:"custom_fields.cf_category"}, //link column to nested field
],
});
Full details about nested data handling can be found in the Columns Documentation
Column Grouping
If you wanted to you could also use column grouping to show that the fields are a subset of a another property, for example we could define the top level columns as usual and then add column group to hold the custom columns
var table = new Tabulator("#example-table", {
columns:[
{title:"Subject", field:"subject"}, //standard column
{title:"Priorty", field:"priority"}, //standard column
{title:"Custom", columns:[ //column group to hold columns in custom_fields property
{title:"Category", field:"custom_fields.cf_category"},
{title:"First Name", field:"custom_fields.cf_firstname"},
]},
],
});
Full details can be found in the Column Grouping Documentation
If you're using es6+, you could easily achieve this by using rest in object destructuring and object spread.
const input = {
"results": [
{
"custom_fields": {...},
...
}
],
"total": 1
}
const expanded = input.results.map(result => {
const { custom_fields, ...rest } = result;
return { ...rest, ...custom_fields };
})
Here is a slightly different solution relying on function generators to traverse the original object, giving the possibility to eventually detect some duplicated keys.
This example can, of course, be altered by adding further checks (like whether you want to traverse all objects inside the main object and so on).
The current example takes care of:
Traversing the original object by excluding primitives and arrays.
Providing a flattenObject method that accepts an object as an argument and a callback as an eventual second argument that will be raised when a duplicated key is met. In that case, the default behavior is to take the "next" nested value as the new one. If false is returned in the callback, the current value is kept. The callback will provide the key and the value of the new value.
So, in a nutshell, the "real" code to acquire the desired result is this one:
// Case usage:
// Map the existing values.
input.results = input.results.map(i => flattenObject(i, (duplicatedKeyValuePair) => {
return false; // <-- keep the existing value if a duplicate is matched.
}));
console.log(input.results)
Of course, it's slightly more complicated then just flattening the desired property, but I wanted to give a more elastic flavour to it.
const input = {
"results": [
{
"cc_emails": [
"ram#freshdesk.com",
"diana#freshdesk.com"
],
"fwd_emails": [],
"reply_cc_emails": [
"ram#freshdesk.com",
"diana#freshdesk.com"
],
"ticket_cc_emails": [
"ram#freshdesk.com",
"diana#freshdesk.com"
],
"fr_escalated": false,
"spam": false,
"email_config_id": null,
"group_id": 35000204315,
"priority": 1,
"requester_id": 35020281588,
"responder_id": 35004154466,
"source": 2,
"company_id": null,
"status": 2,
"subject": "Support Needed...",
"association_type": null,
"to_emails": null,
"product_id": null,
"id": 188261,
"type": null,
"due_by": "2019-09-17T15:12:07Z",
"fr_due_by": "2019-07-01T15:12:07Z",
"is_escalated": false,
"description": "<div>Details about the issue...</div>",
"description_text": "Details about the issue...",
"test_duplicated_key": "hello! I should keep this!",
"custom_fields": {
"cf_category": null,
"cf_firstname": null,
"cf_surname": null,
"cf_user_trainging": null,
"cf_email_address": null,
"cf_office_365": null,
"cf_start_date": null,
"cf_permission_level": null,
"cf_hardware_type": null,
"cf_additional_information_specsoftware_etc": null,
"cf_vpn_access_required": false,
"cf_securitydistribution_group_membership": null,
"cf_mapped_network_driveslogin_script": null,
"cf_printers": null,
"cf_phone_extension": null,
"cf_ddi": null,
"cf_phone_group_membership": null,
"cf_user_who_requires_the_equipment": null,
"cf_requirment_date": null,
"cf_correctclosureused": null,
"cf_location": "A1",
"test_duplicated_key": "You should not see that."
},
"created_at": "2019-06-24T15:11:47Z",
"updated_at": "2019-06-24T15:59:00Z",
"associated_tickets_count": null,
"tags": []
}
],
"total": 1
}
/**
Traverse every property of the desired object, by returning the currently key-value pair looped. If the value is an object, it keeps traversing.
*/
function* traverseObject(obj) {
for ([key, value] of Object.entries(obj)) {
if (value && typeof(value) === 'object' && !Array.isArray(value)) {
yield* traverseObject(obj[key]);
}
else yield {key: key, value: value};
}
}
/**
Flattens the object by traversing every object inside it.
*/
function flattenObject(obj, onDuplicatedKey) {
let res = {};
for (keyValuePair of traverseObject(obj)) {
let add = true;
if (res.hasOwnProperty(keyValuePair.key)) {
add = onDuplicatedKey ? onDuplicatedKey.call(onDuplicatedKey, keyValuePair) : true; // default behavior: override with nested propeties.
}
if (add) res[keyValuePair.key] = keyValuePair.value;
}
return res;
}
/*
Sample usage.
const flattened = flattenObject(input.results[0], (record) => {
console.log('detected key value pair duplicate. Key:', record.key, ' value: ', record.value);
// true will override the value, false will not override the value.
return false;
});
*/
//console.log(flattened);
// Case usage:
// Map the existing values.
input.results = input.results.map(i => flattenObject(i, (duplicatedKeyValuePair) => {
return false; // <-- keep the existing value if a duplicate is matched.
}));
console.log(input.results);
Please note that the above case is just an example, I didn't spend much time testing every single property type, hence it can (of course) be reviewed and code quality and performances can be improved. It was just an example to show a different approach relying on different operators and logics.
As a (final) side note, I Think you can handle that with tabulator in some way, though I'm not sure you can render multiple columns out of a single property, which leads me to believe that altering the original object is probably the desired solution.
I've following object and I need to access
let tempID= options._baseid;
options->_metadata->98bb74e9-b168-4b83-8019-306af8fed977->name
However I'm unable to access this and value is always undefined.
This is my object:
This is the the javascript data variable. Same as the screenshot.
{
"_parent": null,
"_metadata": {
"sTID": null,
"sID": null,
"iType": null,
"lIDList": null,
"lEList": null,
"SfS": null,
"sSrcID": null,
"sSrcType": null,
"98bb74e9-b168-4b83-8019-306af8fed977": {
"id": "98bb74e9-b168-4b83-8019-306af8fed977",
"type": 2,
"name": "newTemplate0508"
}
},
"_baseid": "98bb74e9-b168-4b83-8019-306af8fed977",
"_sourcetype": "2"
}
try to access as following
yourObj._metadata[yourObj._baseid].name;
Use the bracket notation instead of dot.
let tempID = options['_baseid'];
options['_metadata']['98bb74e9-b168-4b83-8019-306af8fed977']['name']
I have control Owners json object so i am trying to get worker from it , How can i assign worker json object to $scope.controlOwnerObj.worker ?
ctrl.js
if ($state.is('app.editControl')) {
angular.forEach($scope.controlDTO.controlOwners,function(owner){
$scope.selectedOwners = owner
})
// $scope.selectedOwners = $scope.controlDTO.controlOwners[0].worker;
// $scope.controlDTO.controlOwners[0].worker.fullName;
console.log('EDIT CONTROL OWNERS DATA', $scope.selectedOwners);
}
json.js
"controlOwners": [{
"worker": {
"workerKey": -1093,
"sourceFeed": null,
"statusLookUpCode": null,
"externalId": null,
"createdUserText": null,
"createdTimestamp": null,
"modifiedUserText": null,
"modifiedTimestamp": null,
"stdId": "ZK84T1N",
"ccId": null,
"empClasId": null,
"deptId": null,
"fullName": "Rajasekaran, Shanmuga",
}
}],
Specify the array index you want:
$scope.controlOwnerObj.worker = $scope.controlOwners[0].worker;
Try to use like this
$scope.controlOwners[0].worker.fullName = 'Rajasekaran, Shanmuga';
i got this structure:
{
"parent": {
"child-parent-1": {
"att1": null,
"att2": null,
},
"child-parent-2": {
"att1": null,
"att2": null,
}
}}
What i need to get, is the "child-parent-1" and "child-parent-2" names without knowing their names... since they are dynamically generated, like a hash code (askdl1km2lkaskjdnzkj2138).
Tried iterating but i couldn't make it work. I always get the child attributes (key/value pair). Or the entire parent object with all the objects in it. Need to get the parent names i've mentioned above.
How can I do this?
Thanks in advance.
Iterating the object should work:
var parents = {
"parent": {
"child-parent-1": {
"att1": null,
"att2": null,
},
"child-parent-2": {
"att1": null,
"att2": null,
}
}
}
for(var key in parents.parent){ // parents.parent because the children are in a object in `parents`
console.log(key); // child-parent-1 / child-parent-2
console.log(parents.parent[key]); // The objects themselves.
}
For me, this logs:
// child-parent-1
// Object {att1: null, att2: null}
// child-parent-2
// Object {att1: null, att2: null}
I have an Array nested withing an Object that's inside an Object which is also in an Object.
EDIT: My original data structure was malformed, so here is a screen cap of console.log(data):
This is being returned from an AJAX request and I'm having issue with getting to the array. The trick is that it will not always return 3 arrays per parent object, the amount of arrays is dependent on "length", or rather, "length" is a reflection of how many arrays to expect in each object that contains arrays. "OID" is the name of each array for both the "th" and "uh" objects.
There also happen to be several main objects, which are iterated with for(var i in data)
I've tried something like:
for(var i in data) {
for(var x in data[i].clicks) {
//And now I'm lost here
//I've tried getting the name of the Array from "OID"
//and going from there, that failed miserably.
}
}
How can I get at the contents of each of those arrays, it would be best if this can be done without knowing the name, quantity or length of said arrays.
Thanks in advance.
Your question and data structure is not very clear and I'm not familiar with the syntax you've used to declare your arrays, it seems incorrect to me.
If you intended your data structure be
data={
"Clicks": {
"length": 3, //Can be between 0-3
"OID": {
1: "1",
2: "2",
3: "3"
},
"th": {
1: [
null,
null,
null,
null
],
2: [
null,
null,
null,
null
],
3: [
null,
null,
null,
null
]
},
"uh": {
1: [
null,
null,
null,
null
],
2: [
null,
null,
null,
null
],
3: [
null,
null,
null,
null
]
}
}
};
and what you want to do is to iterate over all the elements in th and uh where the key comes from the entries in OID, you should be doing something like
for(var i = 1; i <= data.Clicks.length; i++){
data.Clicks.th[data.Clicks.OID[i]];
data.Clicks.uh[data.Clicks.OID[i]];
}
However, if your keys are not going to be anything but numbers, it seems like you might be better served by returning an array of arrays for each of th and uh:
data={
"Clicks": {
"th": [
[
null,
null,
null,
null
],
[
null,
null,
null,
null
],
[
null,
null,
null,
null
]
],
"uh": [
[
null,
null,
null,
null
],
[
null,
null,
null,
null
],
[
null,
null,
null,
null
]
]
}
};
and access it as
//assuming th and uh are of the same length always
for(var i = 1; i <= data.Clicks.th.length; i++){
data.Clicks.th[i];
data.Clicks.uh[i];
}