javascript中如何使用map reduce来汇总多层嵌套对象中的信息?
How can map reduce be used in javascript to summarize information in a multi-layer nested object?
我正在尝试编写一个函数来有效地汇总来自一系列嵌套 javascript 对象的数据。我相信这应该可以通过 reduce 方法来实现,但我不太精通 reduce 并且我能找到的几乎所有示例都使用嵌套数组或数组和对象的某种组合,而我的数据仅作为一个系列出现嵌套对象(比我能找到的例子嵌套得更深。
这是我的原始数据:
var activity = {
"Network": {
"ID": "Network",
"MLS": {
"ID": "MLS",
"Ports": {
"ID": "Ports",
"GigabitEthernet0/1": {
"ID": "GigabitEthernet0/1",
"Port Mode": {
"ID": "Port Mode",
"Name": "Port Mode",
"Value": "0",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 8
},
"Native VLAN": {
"ID": "Native VLAN",
"Name": "Native VLAN",
"Value": "99",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 5
}
},
"GigabitEthernet0/2": {
"ID": "GigabitEthernet0/2",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "209.165.200.225",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"Subnet Mask": {
"ID": "Subnet Mask",
"Name": "Subnet Mask",
"Value": "255.255.255.252",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"(deprecated) IPv6 Addresses": {
"ID": "Ipv6 Address",
"2001:DB8:ACAD:A::1": {
"ID": "2001:DB8:ACAD:A::1",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "2001:DB8:ACAD:A::1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"Prefix Length": {
"ID": "Prefix",
"Name": "Prefix Length",
"Value": "64",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
}
}
},
"SwitchPort": {
"ID": "SwitchPort",
"Name": "SwitchPort",
"Value": "0",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 8
}
},
"Vlan10": {
"ID": "Vlan10",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "192.168.10.254",
"PointsPossible": 2,
"PointsEarned": 2,
"ComparatorClass": 0
},
"(deprecated) IPv6 Addresses": {
"ID": "Ipv6 Address",
"2001:DB8:ACAD:10::1": {
"ID": "2001:DB8:ACAD:10::1",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "2001:DB8:ACAD:10::1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"Prefix Length": {
"ID": "Prefix",
"Name": "Prefix Length",
"Value": "64",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
}
}
}
},
"Vlan20": {
"ID": "Vlan20",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "192.168.20.254",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"(deprecated) IPv6 Addresses": {
"ID": "Ipv6 Address",
"2001:DB8:ACAD:20::1": {
"ID": "2001:DB8:ACAD:20::1",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "2001:DB8:ACAD:20::1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"Prefix Length": {
"ID": "Prefix",
"Name": "Prefix Length",
"Value": "64",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
}
}
}
},
"Vlan30": {
"ID": "Vlan30",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "192.168.30.254",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"(deprecated) IPv6 Addresses": {
"ID": "Ipv6 Address",
"2001:DB8:ACAD:30::1": {
"ID": "2001:DB8:ACAD:30::1",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "2001:DB8:ACAD:30::1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"Prefix Length": {
"ID": "Prefix",
"Name": "Prefix Length",
"Value": "64",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
}
}
}
},
"Vlan99": {
"ID": "Vlan99",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "192.168.99.254",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
}
}
},
"Routes": {
"ID": "Routes",
"Static Routes": {
"ID": "Static RoutesV2",
"Name": "Static Routes",
"Value": "",
"PointsPossible": 0,
"PointsEarned": 0,
"ComparatorClass": 1
},
"IP Routing": {
"ID": "IP Routing",
"Name": "IP Routing",
"Value": "1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 1
}
},
"Routesv6": {
"ID": "Routesv6",
"IPv6 Unicast Routing": {
"ID": "Ipv6 Unicast Routing",
"Name": "IPv6 Unicast Routing",
"Value": "1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 1
}
},
"VLANS": {
"ID": "VLANS",
"VLAN 10": {
"ID": "10",
"VLAN Name": {
"ID": "VLAN Name",
"Name": "VLAN Name",
"Value": "Staff",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 5
}
},
"VLAN 20": {
"ID": "20",
"VLAN Name": {
"ID": "VLAN Name",
"Name": "VLAN Name",
"Value": "Student",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 5
}
},
"VLAN 30": {
"ID": "30",
"VLAN Name": {
"ID": "VLAN Name",
"Name": "VLAN Name",
"Value": "Faculty",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 5
}
}
}
},
"S1": {
"ID": "S1",
"Ports": {
"ID": "Ports",
"GigabitEthernet0/1": {
"ID": "GigabitEthernet0/1",
"Port Mode": {
"ID": "Port Mode",
"Name": "Port Mode",
"Value": "0",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 8
},
"Native VLAN": {
"ID": "Native VLAN",
"Name": "Native VLAN",
"Value": "99",
"PointsPossible": 2,
"PointsEarned": 1,
"ComparatorClass": 5
}
}
}
}
}
};
我希望将数据汇总为一个整体(totalPointsEarned、totalPointsPossible、totalItemsPossible、totalItemsComplete)以及每个 "ComparatorClass",尽管应该按名称而不是按数字存储。从 ComparatorClass 数字到名称的映射如下所示:
var comparatorClassIdToNameMap = {
0:"Ip",
1:"Routing",
2:"Acl",
3:"Nat",
4:"Physical",
5:"Switching",
6:"Connectivity",
7:"Logical",
8:"All",
9:"Encircling_Head",
10:"Encircling_All"
};
所以最终结果应该是这样的:
{
"totalPointsEarned": 25,
"totalPointsPossible": 26,
"totalItemsComplete": 24,
"totalItemsPossible": 24,
"Ip": {
"pointsEarned": 7,
"pointsPossible": 7,
"itemsComplete": 6,
"itemsPossible": 6,
"ComparatorClassID": 0
},
"Routing": {
"pointsEarned": 2,
"pointsPossible": 2,
"itemsComplete": 2,
"itemsPossible": 2,
"ComparatorClassID": 1
},
"Acl": {
"pointsEarned": 0,
"pointsPossible": 0,
"itemsComplete": 0,
"itemsPossible": 0,
"ComparatorClassID": 2
},
"Nat": {
"pointsEarned": 0,
"pointsPossible": 0,
"itemsComplete": 0,
"itemsPossible": 0,
"ComparatorClassID": 3
},
"Physical": {
"pointsEarned": 0,
"pointsPossible": 0,
"itemsComplete": 0,
"itemsPossible": 0,
"ComparatorClassID": 4
},
"Switching": {
"pointsEarned": 5,
"pointsPossible": 6,
"itemsComplete": 5,
"itemsPossible": 5,
"ComparatorClassID": 5
},
"Connectivity": {
"pointsEarned": 0,
"pointsPossible": 0,
"itemsComplete": 0,
"itemsPossible": 0,
"ComparatorClassID": 6
},
"Logical": {
"pointsEarned": 0,
"pointsPossible": 0,
"itemsComplete": 0,
"itemsPossible": 0,
"ComparatorClassID": 7
},
"All": {
"pointsEarned": 0,
"pointsPossible": 0,
"itemsComplete": 0,
"itemsPossible": 0,
"ComparatorClassID": 8
},
"Encircling_Head": {
"pointsEarned": 0,
"pointsPossible": 0,
"itemsComplete": 0,
"itemsPossible": 0,
"ComparatorClassID": 9
},
"Encircling_All": {
"pointsEarned": 0,
"pointsPossible": 0,
"itemsComplete": 0,
"itemsPossible": 0,
"ComparatorClassID": 10
}
}
pointsEarned/pointsPossible 只是从原始数据中求和,itemsComplete 是每片叶子的计数,它有 > 0 pointsEarned,itemsPossible 是每片叶子的计数,它有 > 0 pointsPossible。
任何有关我如何使用 reduce 来有效计算这些值(或其他方法)的帮助都将不胜感激。我可以用一系列 for/ifs 来解决这个问题,但这似乎非常低效。
谢谢!
这绝不是漂亮的(5 个嵌套的 forEach 循环)并且需要清理。但是基本前提就在那里,应该可以带您到达需要去的地方。
const comparatorClassIdToNameMap = {
0:"Ip",
1:"Routing",
2:"Acl",
3:"Nat",
4:"Physical",
5:"Switching",
6:"Connectivity",
7:"Logical",
8:"All",
9:"Encircling_Head",
10:"Encircling_All"
}, activity = {
"Network": {
"ID": "Network",
"MLS": {
"ID": "MLS",
"Ports": {
"ID": "Ports",
"GigabitEthernet0/1": {
"ID": "GigabitEthernet0/1",
"Port Mode": {
"ID": "Port Mode",
"Name": "Port Mode",
"Value": "0",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 8
},
"Native VLAN": {
"ID": "Native VLAN",
"Name": "Native VLAN",
"Value": "99",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 5
}
},
"GigabitEthernet0/2": {
"ID": "GigabitEthernet0/2",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "209.165.200.225",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"Subnet Mask": {
"ID": "Subnet Mask",
"Name": "Subnet Mask",
"Value": "255.255.255.252",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"(deprecated) IPv6 Addresses": {
"ID": "Ipv6 Address",
"2001:DB8:ACAD:A::1": {
"ID": "2001:DB8:ACAD:A::1",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "2001:DB8:ACAD:A::1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"Prefix Length": {
"ID": "Prefix",
"Name": "Prefix Length",
"Value": "64",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
}
}
},
"SwitchPort": {
"ID": "SwitchPort",
"Name": "SwitchPort",
"Value": "0",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 8
}
},
"Vlan10": {
"ID": "Vlan10",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "192.168.10.254",
"PointsPossible": 2,
"PointsEarned": 2,
"ComparatorClass": 0
},
"(deprecated) IPv6 Addresses": {
"ID": "Ipv6 Address",
"2001:DB8:ACAD:10::1": {
"ID": "2001:DB8:ACAD:10::1",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "2001:DB8:ACAD:10::1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"Prefix Length": {
"ID": "Prefix",
"Name": "Prefix Length",
"Value": "64",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
}
}
}
},
"Vlan20": {
"ID": "Vlan20",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "192.168.20.254",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"(deprecated) IPv6 Addresses": {
"ID": "Ipv6 Address",
"2001:DB8:ACAD:20::1": {
"ID": "2001:DB8:ACAD:20::1",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "2001:DB8:ACAD:20::1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"Prefix Length": {
"ID": "Prefix",
"Name": "Prefix Length",
"Value": "64",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
}
}
}
},
"Vlan30": {
"ID": "Vlan30",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "192.168.30.254",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"(deprecated) IPv6 Addresses": {
"ID": "Ipv6 Address",
"2001:DB8:ACAD:30::1": {
"ID": "2001:DB8:ACAD:30::1",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "2001:DB8:ACAD:30::1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"Prefix Length": {
"ID": "Prefix",
"Name": "Prefix Length",
"Value": "64",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
}
}
}
},
"Vlan99": {
"ID": "Vlan99",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "192.168.99.254",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
}
}
},
"Routes": {
"ID": "Routes",
"Static Routes": {
"ID": "Static RoutesV2",
"Name": "Static Routes",
"Value": "",
"PointsPossible": 0,
"PointsEarned": 0,
"ComparatorClass": 1
},
"IP Routing": {
"ID": "IP Routing",
"Name": "IP Routing",
"Value": "1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 1
}
},
"Routesv6": {
"ID": "Routesv6",
"IPv6 Unicast Routing": {
"ID": "Ipv6 Unicast Routing",
"Name": "IPv6 Unicast Routing",
"Value": "1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 1
}
},
"VLANS": {
"ID": "VLANS",
"VLAN 10": {
"ID": "10",
"VLAN Name": {
"ID": "VLAN Name",
"Name": "VLAN Name",
"Value": "Staff",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 5
}
},
"VLAN 20": {
"ID": "20",
"VLAN Name": {
"ID": "VLAN Name",
"Name": "VLAN Name",
"Value": "Student",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 5
}
},
"VLAN 30": {
"ID": "30",
"VLAN Name": {
"ID": "VLAN Name",
"Name": "VLAN Name",
"Value": "Faculty",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 5
}
}
}
},
"S1": {
"ID": "S1",
"Ports": {
"ID": "Ports",
"GigabitEthernet0/1": {
"ID": "GigabitEthernet0/1",
"Port Mode": {
"ID": "Port Mode",
"Name": "Port Mode",
"Value": "0",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 8
},
"Native VLAN": {
"ID": "Native VLAN",
"Name": "Native VLAN",
"Value": "99",
"PointsPossible": 2,
"PointsEarned": 1,
"ComparatorClass": 5
}
}
}
}
}
};
toActivityObjectsArr(activity);
function toActivityObjectsArr (obj) {
const arr = [], searchKey = 'ComparatorClass';
Object.keys(obj.Network).forEach(function (networkChild) {
Object.keys(obj.Network[networkChild]).forEach(function (networkGrandChild) {
networkGrandChild == searchKey && arr.push(obj.Network[networkChild]);
Object.keys(obj.Network[networkChild][networkGrandChild]).forEach(function (networkGreatGrandChild) {
networkGreatGrandChild == searchKey && arr.push(obj.Network[networkChild][networkGrandChild]);
Object.keys(obj.Network[networkChild][networkGrandChild][networkGreatGrandChild]).forEach(function (networkGreatGreatGrandChild) {
networkGreatGreatGrandChild == searchKey && arr.push(obj.Network[networkChild][networkGrandChild][networkGreatGrandChild]);
Object.keys(obj.Network[networkChild][networkGrandChild][networkGreatGrandChild][networkGreatGreatGrandChild]).forEach(function (networkGreatGreatGreatGrandChild) {
networkGreatGreatGreatGrandChild == searchKey && arr.push(obj.Network[networkChild][networkGrandChild][networkGreatGrandChild][networkGreatGreatGrandChild]);
Object.keys(obj.Network[networkChild][networkGrandChild][networkGreatGrandChild][networkGreatGreatGrandChild][networkGreatGreatGreatGrandChild]).forEach(function (networkGreatGreatGreatGreatGrandChild) {
networkGreatGreatGreatGreatGrandChild == searchKey && arr.push(obj.Network[networkChild][networkGrandChild][networkGreatGrandChild][networkGreatGreatGrandChild][networkGreatGreatGreatGrandChild]);
Object.keys(obj.Network[networkChild][networkGrandChild][networkGreatGrandChild][networkGreatGreatGrandChild][networkGreatGreatGreatGrandChild][networkGreatGreatGreatGreatGrandChild]).forEach(function (networkGreatGreatGreatGreatGreatGrandChild) {
networkGreatGreatGreatGreatGreatGrandChild == searchKey && arr.push(obj.Network[networkChild][networkGrandChild][networkGreatGrandChild][networkGreatGreatGrandChild][networkGreatGreatGreatGrandChild][networkGreatGreatGreatGreatGrandChild]);
});
});
});
});
});
});
});
createAnswerObj(arr);
}
function createAnswerObj(arr) {
const answerObj = {};
answerObj.totalPossiblePoints = getTotalPossiblePoints(arr);
answerObj.totalPointsEarned = getTotalPointsEarned(arr);
Object.keys(comparatorClassIdToNameMap).forEach(function (k) {
answerObj[comparatorClassIdToNameMap[k]] = {
pointsEarned: getPointsEarnedByComparator(arr, k),
pointsPossible: getPointsPossibleByComparator(arr, k),
ComparatorClassID: k
};
});
console.log(answerObj);
}
function getTotalPossiblePoints(arr) {
return arr.reduce(function (total, obj) {
return total += obj.PointsPossible;
}, 0);
}
function getPointsPossibleByComparator(arr, k) {
return arr.reduce(function (total, obj) {
return obj.ComparatorClass == k ? (total += obj.PointsPossible) : total;
}, 0);
}
function getTotalPointsEarned(arr) {
return arr.reduce(function (total, obj) {
return total += obj.PointsEarned;
}, 0);
}
function getPointsEarnedByComparator(arr, k) {
return arr.reduce(function (total, obj) {
return obj.ComparatorClass == k ? (total += obj.PointsEarned) : total;
}, 0);
}
更新
在我写下下面的解决方案之后,我意识到这真的可以按照您建议的方式更简单地完成,映射,然后减少结果。它不是精确的映射,更多的是树叶的提取。但随后我们可以将您的 comparatorClassIdToNameMap
转换为初始输出对象,然后简单地将这些叶子减少到其中。
虽然下面的代码不是很漂亮,但它确实按照您要求的方式解决了问题,这比我第一次写的解决方案要多。
// utility function
const findLeaves = (isLeaf) => (obj) =>
isLeaf (obj)
? [obj]
: Object .values (obj) .flatMap (
(v) => typeof v == 'object' ? findLeaves (isLeaf) (v) : []
)
// main function
const transform = (actvity, nameMapping) => findLeaves (obj => 'PointsPossible' in obj) (activity)
.reduce((
{totalPointsPossible, totalPointsEarned, ...rest},
{PointsPossible, PointsEarned, ComparatorClass},
_, __, // index and array passed to `reduce` are ignored
{PointsPossible: pp, PointsEarned: pe} = rest[nameMapping[ComparatorClass]]
) => ({ // line 7
totalPointsPossible: totalPointsPossible + PointsPossible,
totalPointsEarned: totalPointsEarned + PointsEarned,
...rest,
[nameMapping[ComparatorClass]]: {
PointsPossible: PointsPossible + pp,
PointsEarned: PointsEarned + pe,
ComparatorClassId: ComparatorClass
}
}), // line 16
Object.entries(comparatorClassIdToNameMap).reduce((a, [k, v]) => ({ // line 17
... a,
[v]: {
PointsPossible: 0,
PointsEarned: 0,
ComparatorClassId: Number(k)
}
}), {totalPointsPossible: 0, totalPointsEarned: 0}) // line 24
)
// data
const activity = {Network: {ID: "Network", MLS: {ID: "MLS", Ports: {ID: "Ports", "GigabitEthernet0/1": {ID: "GigabitEthernet0/1", "Port Mode": {ID: "Port Mode", Name: "Port Mode", Value: "0", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 8}, "Native VLAN": {ID: "Native VLAN", Name: "Native VLAN", Value: "99", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}, "GigabitEthernet0/2": {ID: "GigabitEthernet0/2", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "209.165.200.225", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Subnet Mask": {ID: "Subnet Mask", Name: "Subnet Mask", Value: "255.255.255.252", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: A: : 1": {ID: "2001: DB8: ACAD: A: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: A: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}, SwitchPort: {ID: "SwitchPort", Name: "SwitchPort", Value: "0", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 8}}, Vlan10: {ID: "Vlan10", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.10.254", PointsPossible: 2, PointsEarned: 2, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: 10: : 1": {ID: "2001: DB8: ACAD: 10: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: 10: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}}, Vlan20: {ID: "Vlan20", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.20.254", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: 20: : 1": {ID: "2001: DB8: ACAD: 20: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: 20: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}}, Vlan30: {ID: "Vlan30", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.30.254", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: 30: : 1": {ID: "2001: DB8: ACAD: 30: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: 30: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}}, Vlan99: {ID: "Vlan99", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.99.254", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}, Routes: {ID: "Routes", "Static Routes": {ID: "Static RoutesV2", Name: "Static Routes", Value: "", PointsPossible: 0, PointsEarned: 0, ComparatorClass: 1}, "IP Routing": {ID: "IP Routing", Name: "IP Routing", Value: "1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 1}}, Routesv6: {ID: "Routesv6", "IPv6 Unicast Routing": {ID: "Ipv6 Unicast Routing", Name: "IPv6 Unicast Routing", Value: "1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 1}}, VLANS: {ID: "VLANS", "VLAN 10": {ID: "10", "VLAN Name": {ID: "VLAN Name", Name: "VLAN Name", Value: "Staff", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}, "VLAN 20": {ID: "20", "VLAN Name": {ID: "VLAN Name", Name: "VLAN Name", Value: "Student", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}, "VLAN 30": {ID: "30", "VLAN Name": {ID: "VLAN Name", Name: "VLAN Name", Value: "Faculty", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}}}, S1: {ID: "S1", Ports: {ID: "Ports", "GigabitEthernet0/1": {ID: "GigabitEthernet0/1", "Port Mode": {ID: "Port Mode", Name: "Port Mode", Value: "0", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 8}, "Native VLAN": {ID: "Native VLAN", Name: "Native VLAN", Value: "99", PointsPossible: 2, PointsEarned: 1, ComparatorClass: 5}}}}}};
const comparatorClassIdToNameMap = {0: "Ip", 1: "Routing", 2: "Acl", 3: "Nat", 4: "Physical", 5: "Switching", 6: "Connectivity", 7: "Logical", 8: "All", 9: "Encircling_Head", 10: "Encircling_All"}
// demo
console .log (
transform (activity, comparatorClassIdToNameMap)
)
.as-console-wrapper { max-height: 100% !important; top: 0; }
第 17 - 24 行使用 comparatorClassIdToNameMap
并添加 total*
属性创建一个初始对象,其中包含您想要输出的所有属性。
第 7 - 14 行向其中添加了一个叶节点。这发生在 reduce
内部,因此添加所有叶节点并返回结果对象。
附录
有评论说你JS不精通。这是上述函数的替代版本,以 ES5 风格编写。对于那些来自其他主流语言的人来说,这一篇可能更容易阅读:
function transform (actvity, nameMapping) {
var baseObj = {totalPointsPossible: 0, totalPointsEarned: 0}
for (var id in nameMapping) {
if (nameMapping .hasOwnProperty (id)) {
var name = nameMapping [id]
baseObj [name] = {
PointsPossible: 0,
PointsEarned: 0,
ComparatorClassId: Number(id)
}
}
}
function isLeaf(obj) {
return 'PointsPossible' in obj
}
var leaves = findLeaves (isLeaf) (activity)
return leaves .reduce (
function (acc, item) {
acc .totalPointsPossible += item .PointsPossible
acc .totalPointsEarned += item .PointsEarned
var key = nameMapping [item .ComparatorClass]
acc [key] .PointsPossible += item .PointsPossible
acc [key] .PointsEarned += item .PointsEarned
return acc
},
baseObj
)
}
这个和上面的完全一样。但它充满了赋值、变更和控制流语句,所有这些都是我个人试图避免的。上面的版本通过使用解构和其他更现代的技术避免了所有这些。
附录 2
通过使用 ES5 工具填充 Object.prorotype.flatMap
和 Object.values
并将箭头函数替换为函数表达式,将 findLeaves
转换为 ES5:
const values = function (obj) {
return Object .keys (obj) .map (k => obj [k])
}
const flatMap = function (fn) {
return function (xs) {
return xs .reduce ((a, x) => a .concat (fn (x)), [])
}
}
const findLeaves = function (isLeaf) {
return function(obj) {
return isLeaf (obj)
? [obj]
: flatMap (
(v) => typeof v == 'object' ? findLeaves (isLeaf) (v) : []
) (values (obj))
}
}
原解
我不再推荐以下两个建议,但我认为它们可能仍然有价值。
使用库
我是函数式编程库的忠实粉丝Ramda(免责声明:我是它的主要作者之一),我经常通过 Ramda 的眼睛看到这样的问题。所以首先,一个相当大量使用 Ramda 的解决方案:
// utility function
const findLeaves = (isLeaf) => (obj) =>
isLeaf (obj)
? [obj]
: Object .values (obj) .flatMap (
(v) => typeof v == 'object' ? findLeaves (isLeaf) (v) : []
)
// helper functions
const summarize = pipe (
findLeaves (has('PointsPossible')),
groupBy (prop ('ComparatorClass')),
map ( applySpec ({
PointsPossible: pipe (pluck ('PointsPossible'), sum),
PointsEarned: pipe (pluck ('PointsEarned'), sum),
ComparatorClassID: pipe (head, prop ('ComparatorClass'))
}))
)
const extractTotals = (activity, nameMapping, summary = summarize(activity)) =>
reduce (
(a, [id, name]) =>
({...a, [name]: summary [id] ||
{PointsPossible: 0, PointsEarned: 0, ComparatorClassID: id}}),
{},
toPairs (nameMapping)
)
// main function
const transform = (activity, nameMapping, totals = extractTotals (activity, nameMapping)) =>
Object .assign (applySpec ({
totalPointsPossible: pipe (pluck ('PointsPossible'), sum),
totalPointsEarned: pipe (pluck ('PointsEarned'), sum),
}) (values (totals)), totals)
// data
const activity = {Network: {ID: "Network", MLS: {ID: "MLS", Ports: {ID: "Ports", "GigabitEthernet0/1": {ID: "GigabitEthernet0/1", "Port Mode": {ID: "Port Mode", Name: "Port Mode", Value: "0", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 8}, "Native VLAN": {ID: "Native VLAN", Name: "Native VLAN", Value: "99", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}, "GigabitEthernet0/2": {ID: "GigabitEthernet0/2", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "209.165.200.225", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Subnet Mask": {ID: "Subnet Mask", Name: "Subnet Mask", Value: "255.255.255.252", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: A: : 1": {ID: "2001: DB8: ACAD: A: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: A: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}, SwitchPort: {ID: "SwitchPort", Name: "SwitchPort", Value: "0", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 8}}, Vlan10: {ID: "Vlan10", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.10.254", PointsPossible: 2, PointsEarned: 2, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: 10: : 1": {ID: "2001: DB8: ACAD: 10: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: 10: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}}, Vlan20: {ID: "Vlan20", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.20.254", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: 20: : 1": {ID: "2001: DB8: ACAD: 20: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: 20: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}}, Vlan30: {ID: "Vlan30", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.30.254", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: 30: : 1": {ID: "2001: DB8: ACAD: 30: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: 30: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}}, Vlan99: {ID: "Vlan99", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.99.254", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}, Routes: {ID: "Routes", "Static Routes": {ID: "Static RoutesV2", Name: "Static Routes", Value: "", PointsPossible: 0, PointsEarned: 0, ComparatorClass: 1}, "IP Routing": {ID: "IP Routing", Name: "IP Routing", Value: "1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 1}}, Routesv6: {ID: "Routesv6", "IPv6 Unicast Routing": {ID: "Ipv6 Unicast Routing", Name: "IPv6 Unicast Routing", Value: "1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 1}}, VLANS: {ID: "VLANS", "VLAN 10": {ID: "10", "VLAN Name": {ID: "VLAN Name", Name: "VLAN Name", Value: "Staff", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}, "VLAN 20": {ID: "20", "VLAN Name": {ID: "VLAN Name", Name: "VLAN Name", Value: "Student", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}, "VLAN 30": {ID: "30", "VLAN Name": {ID: "VLAN Name", Name: "VLAN Name", Value: "Faculty", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}}}, S1: {ID: "S1", Ports: {ID: "Ports", "GigabitEthernet0/1": {ID: "GigabitEthernet0/1", "Port Mode": {ID: "Port Mode", Name: "Port Mode", Value: "0", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 8}, "Native VLAN": {ID: "Native VLAN", Name: "Native VLAN", Value: "99", PointsPossible: 2, PointsEarned: 1, ComparatorClass: 5}}}}}};
const comparatorClassIdToNameMap = {0: "Ip", 1: "Routing", 2: "Acl", 3: "Nat", 4: "Physical", 5: "Switching", 6: "Connectivity", 7: "Logical", 8: "All", 9: "Encircling_Head", 10: "Encircling_All"}
// demo
console .log (
transform (activity, comparatorClassIdToNameMap)
)
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
<script>
const {pipe, has, groupBy, prop, map, applySpec, pluck, sum, head, reduce, toPairs, values} = R
</script>
我们的主要 transform
函数依赖于 extractTotals
,后者依赖于 summarize
,后者又依赖于 findLeaves
。显然,由于 extractTotals
和 summarize
仅在内部使用,因此您可以将它们内部化;我不推荐它。 findLeaves
是递归的,所以不能那样处理。
findLeaves
接受一个谓词函数来测试一个节点是否是叶子和对象,并递归地找到所有被报告为叶子的节点。它不处理数组,但这不是您要求的一部分;如果需要,扩展并不难。
如果我们调用 findLeaves(obj => 'PointsPossible' in obj)(activity)
,我们会得到一个包含所有叶节点的平面数组,如下所示:
[
{
"ID": "Port Mode",
"Name": "Port Mode",
"Value": "0",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 8
},
{
"ID": "Native VLAN",
"Name": "Native VLAN",
"Value": "99",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 5
},
// ...
]
summarize
使用 findLeaves
和一些 Ramda 函数将你的 activity
变成
{
"0": {
"PointsPossible": 15,
"PointsEarned": 15,
"ComparatorClassID": 0
},
"1": {
"PointsPossible": 2,
"PointsEarned": 2,
"ComparatorClassID": 1
},
// ...
}
我们通过 extractTotals
几乎一路走来,它将上述内容与您的 comparatorClassIdToNameMap
结合起来得到这个:
{
"Ip": {
"PointsPossible": 15,
"PointsEarned": 15,
"ComparatorClassID": 0
},
"Routing": {
"PointsPossible": 2,
"PointsEarned": 2,
"ComparatorClassID": 1
},
// ...
}
唯一缺少的是总计。留给 transform
,它采用上述结果并将这些总数添加到结果对象中:
{
"totalPointsPossible": 26,
"totalPointsEarned": 25,
"Ip": {
"PointsPossible": 15,
"PointsEarned": 15,
"ComparatorClassID": 0
},
"Routing": {
"PointsPossible": 2,
"PointsEarned": 2,
"ComparatorClassID": 1
},
// ...
}
(注意 这里没有任何东西可以处理你的 itemsComplete
或 itemsPossible
。我在数据中看不到任何帮助,但我我猜是对上述内容的简单扩展。)
这个问题的细分是我通常的技术,一次做一个小的转换,总是试图朝着所需的解决方案前进。可能有一些有用的方法来组合步骤。我把它留给你。
没有图书馆
像 Ramda 这样的库没有魔法。我们可以自己编写其功能的可重用版本。这样做通常会改进我们代码的许多部分。
因此,我们可以通过编写这些 Ramda 函数的自定义版本,自行完成相同的分解。大多数都是微不足道的。只有 applySpec
、groupBy
和 mapObj
有任何复杂性。注意,在上面的 Ramda 版本中,我们使用 Ramda 的 map
来代替这里的 mapObj
。虽然我们可以编写 map
来处理数组或对象,但这里只创建两个函数更简单。
// utility functions
const pipe = (...fns) => (arg) =>
fns.reduce((a, fn) => fn(a), arg)
const map = (fn) => (xs) =>
xs .map (x => fn (x))
const mapObj = (fn) => (obj) =>
Object .fromEntries (map (([k, v]) => [k, fn (v)]) (Object .entries (obj)))
const has = (name) => (obj) =>
name in obj
const prop = (name) => (obj) =>
obj [name]
const pluck = (name) =>
map (prop (name))
const head = (xs) =>
xs [0]
const sum = (ns) =>
ns .reduce ((a, b) => a + b, 0)
const applySpec = (spec) => (obj) =>
Object .entries (spec) .reduce ((a, [k, fn]) => ({...a, [k]: fn (obj)}), {})
const groupBy = (fn) => (xs) =>
xs .reduce ((a, x, _, __, k = fn(x)) => ({...a, [k]: [...(a[k] || []), x]}), {})
const findLeaves = (isLeaf) => (obj) =>
isLeaf (obj)
? [obj]
: Object .values (obj) .flatMap (
(v) => typeof v == 'object' ? findLeaves (isLeaf) (v) : []
)
// helper functions
const summarize = pipe (
findLeaves (has ('PointsPossible')),
groupBy (prop ('ComparatorClass')),
mapObj ( applySpec ({
PointsPossible: pipe (pluck ('PointsPossible'), sum),
PointsEarned: pipe (pluck ('PointsEarned'), sum),
ComparatorClassID: pipe (head, prop ('ComparatorClass'))
}))
)
const extractTotals = (activity, nameMapping, summary = summarize(activity)) =>
Object .entries (nameMapping)
.reduce (
(a, [id, name]) => ({...a, [name]: summary[id] || {PointsPossible: 0, PointsEarned: 0, ComparatorClassID: id}}),
{}
)
// main function
const transform = (activity, nameMapping) => {
const totals = extractTotals (activity, nameMapping)
return Object .assign (applySpec ({
totalPointsPossible: pipe (pluck ('PointsPossible'), sum),
totalPointsEarned: pipe (pluck ('PointsEarned'), sum),
}) (Object .values (totals)), totals)
}
// data
const activity = {Network: {ID: "Network", MLS: {ID: "MLS", Ports: {ID: "Ports", "GigabitEthernet0/1": {ID: "GigabitEthernet0/1", "Port Mode": {ID: "Port Mode", Name: "Port Mode", Value: "0", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 8}, "Native VLAN": {ID: "Native VLAN", Name: "Native VLAN", Value: "99", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}, "GigabitEthernet0/2": {ID: "GigabitEthernet0/2", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "209.165.200.225", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Subnet Mask": {ID: "Subnet Mask", Name: "Subnet Mask", Value: "255.255.255.252", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: A: : 1": {ID: "2001: DB8: ACAD: A: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: A: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}, SwitchPort: {ID: "SwitchPort", Name: "SwitchPort", Value: "0", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 8}}, Vlan10: {ID: "Vlan10", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.10.254", PointsPossible: 2, PointsEarned: 2, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: 10: : 1": {ID: "2001: DB8: ACAD: 10: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: 10: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}}, Vlan20: {ID: "Vlan20", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.20.254", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: 20: : 1": {ID: "2001: DB8: ACAD: 20: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: 20: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}}, Vlan30: {ID: "Vlan30", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.30.254", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: 30: : 1": {ID: "2001: DB8: ACAD: 30: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: 30: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}}, Vlan99: {ID: "Vlan99", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.99.254", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}, Routes: {ID: "Routes", "Static Routes": {ID: "Static RoutesV2", Name: "Static Routes", Value: "", PointsPossible: 0, PointsEarned: 0, ComparatorClass: 1}, "IP Routing": {ID: "IP Routing", Name: "IP Routing", Value: "1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 1}}, Routesv6: {ID: "Routesv6", "IPv6 Unicast Routing": {ID: "Ipv6 Unicast Routing", Name: "IPv6 Unicast Routing", Value: "1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 1}}, VLANS: {ID: "VLANS", "VLAN 10": {ID: "10", "VLAN Name": {ID: "VLAN Name", Name: "VLAN Name", Value: "Staff", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}, "VLAN 20": {ID: "20", "VLAN Name": {ID: "VLAN Name", Name: "VLAN Name", Value: "Student", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}, "VLAN 30": {ID: "30", "VLAN Name": {ID: "VLAN Name", Name: "VLAN Name", Value: "Faculty", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}}}, S1: {ID: "S1", Ports: {ID: "Ports", "GigabitEthernet0/1": {ID: "GigabitEthernet0/1", "Port Mode": {ID: "Port Mode", Name: "Port Mode", Value: "0", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 8}, "Native VLAN": {ID: "Native VLAN", Name: "Native VLAN", Value: "99", PointsPossible: 2, PointsEarned: 1, ComparatorClass: 5}}}}}};
const comparatorClassIdToNameMap = {0: "Ip", 1: "Routing", 2: "Acl", 3: "Nat", 4: "Physical", 5: "Switching", 6: "Connectivity", 7: "Logical", 8: "All", 9: "Encircling_Head", 10: "Encircling_All"}
// demo
console .log (
transform (activity, comparatorClassIdToNameMap)
)
.as-console-wrapper { max-height: 100% !important; top: 0; }
这使用与上述相同的问题分解,只需编写 Ramda 函数的自定义版本。如果您想了解更多关于它们的信息(mapObj
除外,它在 Ramda 中由 map
涵盖),请参阅 Ramda documentation。这些实现比 Ramda 的更简单,通常不太灵活,但它们适用于我们的案例。
备注
我发现这是一个值得尝试的有趣问题。但是因为数据墙,我几乎马上就放弃了。滚动浏览那么多数据以试图辨别需求是非常困难的。下次,请在发帖前查看 How to create a Minimal, Reproducible Example。您应该能够制作一个更小的测试用例,但仍能证明目标。
我正在尝试编写一个函数来有效地汇总来自一系列嵌套 javascript 对象的数据。我相信这应该可以通过 reduce 方法来实现,但我不太精通 reduce 并且我能找到的几乎所有示例都使用嵌套数组或数组和对象的某种组合,而我的数据仅作为一个系列出现嵌套对象(比我能找到的例子嵌套得更深。
这是我的原始数据:
var activity = {
"Network": {
"ID": "Network",
"MLS": {
"ID": "MLS",
"Ports": {
"ID": "Ports",
"GigabitEthernet0/1": {
"ID": "GigabitEthernet0/1",
"Port Mode": {
"ID": "Port Mode",
"Name": "Port Mode",
"Value": "0",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 8
},
"Native VLAN": {
"ID": "Native VLAN",
"Name": "Native VLAN",
"Value": "99",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 5
}
},
"GigabitEthernet0/2": {
"ID": "GigabitEthernet0/2",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "209.165.200.225",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"Subnet Mask": {
"ID": "Subnet Mask",
"Name": "Subnet Mask",
"Value": "255.255.255.252",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"(deprecated) IPv6 Addresses": {
"ID": "Ipv6 Address",
"2001:DB8:ACAD:A::1": {
"ID": "2001:DB8:ACAD:A::1",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "2001:DB8:ACAD:A::1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"Prefix Length": {
"ID": "Prefix",
"Name": "Prefix Length",
"Value": "64",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
}
}
},
"SwitchPort": {
"ID": "SwitchPort",
"Name": "SwitchPort",
"Value": "0",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 8
}
},
"Vlan10": {
"ID": "Vlan10",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "192.168.10.254",
"PointsPossible": 2,
"PointsEarned": 2,
"ComparatorClass": 0
},
"(deprecated) IPv6 Addresses": {
"ID": "Ipv6 Address",
"2001:DB8:ACAD:10::1": {
"ID": "2001:DB8:ACAD:10::1",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "2001:DB8:ACAD:10::1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"Prefix Length": {
"ID": "Prefix",
"Name": "Prefix Length",
"Value": "64",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
}
}
}
},
"Vlan20": {
"ID": "Vlan20",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "192.168.20.254",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"(deprecated) IPv6 Addresses": {
"ID": "Ipv6 Address",
"2001:DB8:ACAD:20::1": {
"ID": "2001:DB8:ACAD:20::1",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "2001:DB8:ACAD:20::1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"Prefix Length": {
"ID": "Prefix",
"Name": "Prefix Length",
"Value": "64",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
}
}
}
},
"Vlan30": {
"ID": "Vlan30",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "192.168.30.254",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"(deprecated) IPv6 Addresses": {
"ID": "Ipv6 Address",
"2001:DB8:ACAD:30::1": {
"ID": "2001:DB8:ACAD:30::1",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "2001:DB8:ACAD:30::1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"Prefix Length": {
"ID": "Prefix",
"Name": "Prefix Length",
"Value": "64",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
}
}
}
},
"Vlan99": {
"ID": "Vlan99",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "192.168.99.254",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
}
}
},
"Routes": {
"ID": "Routes",
"Static Routes": {
"ID": "Static RoutesV2",
"Name": "Static Routes",
"Value": "",
"PointsPossible": 0,
"PointsEarned": 0,
"ComparatorClass": 1
},
"IP Routing": {
"ID": "IP Routing",
"Name": "IP Routing",
"Value": "1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 1
}
},
"Routesv6": {
"ID": "Routesv6",
"IPv6 Unicast Routing": {
"ID": "Ipv6 Unicast Routing",
"Name": "IPv6 Unicast Routing",
"Value": "1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 1
}
},
"VLANS": {
"ID": "VLANS",
"VLAN 10": {
"ID": "10",
"VLAN Name": {
"ID": "VLAN Name",
"Name": "VLAN Name",
"Value": "Staff",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 5
}
},
"VLAN 20": {
"ID": "20",
"VLAN Name": {
"ID": "VLAN Name",
"Name": "VLAN Name",
"Value": "Student",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 5
}
},
"VLAN 30": {
"ID": "30",
"VLAN Name": {
"ID": "VLAN Name",
"Name": "VLAN Name",
"Value": "Faculty",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 5
}
}
}
},
"S1": {
"ID": "S1",
"Ports": {
"ID": "Ports",
"GigabitEthernet0/1": {
"ID": "GigabitEthernet0/1",
"Port Mode": {
"ID": "Port Mode",
"Name": "Port Mode",
"Value": "0",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 8
},
"Native VLAN": {
"ID": "Native VLAN",
"Name": "Native VLAN",
"Value": "99",
"PointsPossible": 2,
"PointsEarned": 1,
"ComparatorClass": 5
}
}
}
}
}
};
我希望将数据汇总为一个整体(totalPointsEarned、totalPointsPossible、totalItemsPossible、totalItemsComplete)以及每个 "ComparatorClass",尽管应该按名称而不是按数字存储。从 ComparatorClass 数字到名称的映射如下所示:
var comparatorClassIdToNameMap = {
0:"Ip",
1:"Routing",
2:"Acl",
3:"Nat",
4:"Physical",
5:"Switching",
6:"Connectivity",
7:"Logical",
8:"All",
9:"Encircling_Head",
10:"Encircling_All"
};
所以最终结果应该是这样的:
{
"totalPointsEarned": 25,
"totalPointsPossible": 26,
"totalItemsComplete": 24,
"totalItemsPossible": 24,
"Ip": {
"pointsEarned": 7,
"pointsPossible": 7,
"itemsComplete": 6,
"itemsPossible": 6,
"ComparatorClassID": 0
},
"Routing": {
"pointsEarned": 2,
"pointsPossible": 2,
"itemsComplete": 2,
"itemsPossible": 2,
"ComparatorClassID": 1
},
"Acl": {
"pointsEarned": 0,
"pointsPossible": 0,
"itemsComplete": 0,
"itemsPossible": 0,
"ComparatorClassID": 2
},
"Nat": {
"pointsEarned": 0,
"pointsPossible": 0,
"itemsComplete": 0,
"itemsPossible": 0,
"ComparatorClassID": 3
},
"Physical": {
"pointsEarned": 0,
"pointsPossible": 0,
"itemsComplete": 0,
"itemsPossible": 0,
"ComparatorClassID": 4
},
"Switching": {
"pointsEarned": 5,
"pointsPossible": 6,
"itemsComplete": 5,
"itemsPossible": 5,
"ComparatorClassID": 5
},
"Connectivity": {
"pointsEarned": 0,
"pointsPossible": 0,
"itemsComplete": 0,
"itemsPossible": 0,
"ComparatorClassID": 6
},
"Logical": {
"pointsEarned": 0,
"pointsPossible": 0,
"itemsComplete": 0,
"itemsPossible": 0,
"ComparatorClassID": 7
},
"All": {
"pointsEarned": 0,
"pointsPossible": 0,
"itemsComplete": 0,
"itemsPossible": 0,
"ComparatorClassID": 8
},
"Encircling_Head": {
"pointsEarned": 0,
"pointsPossible": 0,
"itemsComplete": 0,
"itemsPossible": 0,
"ComparatorClassID": 9
},
"Encircling_All": {
"pointsEarned": 0,
"pointsPossible": 0,
"itemsComplete": 0,
"itemsPossible": 0,
"ComparatorClassID": 10
}
}
pointsEarned/pointsPossible 只是从原始数据中求和,itemsComplete 是每片叶子的计数,它有 > 0 pointsEarned,itemsPossible 是每片叶子的计数,它有 > 0 pointsPossible。
任何有关我如何使用 reduce 来有效计算这些值(或其他方法)的帮助都将不胜感激。我可以用一系列 for/ifs 来解决这个问题,但这似乎非常低效。
谢谢!
这绝不是漂亮的(5 个嵌套的 forEach 循环)并且需要清理。但是基本前提就在那里,应该可以带您到达需要去的地方。
const comparatorClassIdToNameMap = {
0:"Ip",
1:"Routing",
2:"Acl",
3:"Nat",
4:"Physical",
5:"Switching",
6:"Connectivity",
7:"Logical",
8:"All",
9:"Encircling_Head",
10:"Encircling_All"
}, activity = {
"Network": {
"ID": "Network",
"MLS": {
"ID": "MLS",
"Ports": {
"ID": "Ports",
"GigabitEthernet0/1": {
"ID": "GigabitEthernet0/1",
"Port Mode": {
"ID": "Port Mode",
"Name": "Port Mode",
"Value": "0",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 8
},
"Native VLAN": {
"ID": "Native VLAN",
"Name": "Native VLAN",
"Value": "99",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 5
}
},
"GigabitEthernet0/2": {
"ID": "GigabitEthernet0/2",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "209.165.200.225",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"Subnet Mask": {
"ID": "Subnet Mask",
"Name": "Subnet Mask",
"Value": "255.255.255.252",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"(deprecated) IPv6 Addresses": {
"ID": "Ipv6 Address",
"2001:DB8:ACAD:A::1": {
"ID": "2001:DB8:ACAD:A::1",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "2001:DB8:ACAD:A::1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"Prefix Length": {
"ID": "Prefix",
"Name": "Prefix Length",
"Value": "64",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
}
}
},
"SwitchPort": {
"ID": "SwitchPort",
"Name": "SwitchPort",
"Value": "0",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 8
}
},
"Vlan10": {
"ID": "Vlan10",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "192.168.10.254",
"PointsPossible": 2,
"PointsEarned": 2,
"ComparatorClass": 0
},
"(deprecated) IPv6 Addresses": {
"ID": "Ipv6 Address",
"2001:DB8:ACAD:10::1": {
"ID": "2001:DB8:ACAD:10::1",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "2001:DB8:ACAD:10::1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"Prefix Length": {
"ID": "Prefix",
"Name": "Prefix Length",
"Value": "64",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
}
}
}
},
"Vlan20": {
"ID": "Vlan20",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "192.168.20.254",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"(deprecated) IPv6 Addresses": {
"ID": "Ipv6 Address",
"2001:DB8:ACAD:20::1": {
"ID": "2001:DB8:ACAD:20::1",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "2001:DB8:ACAD:20::1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"Prefix Length": {
"ID": "Prefix",
"Name": "Prefix Length",
"Value": "64",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
}
}
}
},
"Vlan30": {
"ID": "Vlan30",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "192.168.30.254",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"(deprecated) IPv6 Addresses": {
"ID": "Ipv6 Address",
"2001:DB8:ACAD:30::1": {
"ID": "2001:DB8:ACAD:30::1",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "2001:DB8:ACAD:30::1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
},
"Prefix Length": {
"ID": "Prefix",
"Name": "Prefix Length",
"Value": "64",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
}
}
}
},
"Vlan99": {
"ID": "Vlan99",
"IP Address": {
"ID": "IP Address",
"Name": "IP Address",
"Value": "192.168.99.254",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 0
}
}
},
"Routes": {
"ID": "Routes",
"Static Routes": {
"ID": "Static RoutesV2",
"Name": "Static Routes",
"Value": "",
"PointsPossible": 0,
"PointsEarned": 0,
"ComparatorClass": 1
},
"IP Routing": {
"ID": "IP Routing",
"Name": "IP Routing",
"Value": "1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 1
}
},
"Routesv6": {
"ID": "Routesv6",
"IPv6 Unicast Routing": {
"ID": "Ipv6 Unicast Routing",
"Name": "IPv6 Unicast Routing",
"Value": "1",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 1
}
},
"VLANS": {
"ID": "VLANS",
"VLAN 10": {
"ID": "10",
"VLAN Name": {
"ID": "VLAN Name",
"Name": "VLAN Name",
"Value": "Staff",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 5
}
},
"VLAN 20": {
"ID": "20",
"VLAN Name": {
"ID": "VLAN Name",
"Name": "VLAN Name",
"Value": "Student",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 5
}
},
"VLAN 30": {
"ID": "30",
"VLAN Name": {
"ID": "VLAN Name",
"Name": "VLAN Name",
"Value": "Faculty",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 5
}
}
}
},
"S1": {
"ID": "S1",
"Ports": {
"ID": "Ports",
"GigabitEthernet0/1": {
"ID": "GigabitEthernet0/1",
"Port Mode": {
"ID": "Port Mode",
"Name": "Port Mode",
"Value": "0",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 8
},
"Native VLAN": {
"ID": "Native VLAN",
"Name": "Native VLAN",
"Value": "99",
"PointsPossible": 2,
"PointsEarned": 1,
"ComparatorClass": 5
}
}
}
}
}
};
toActivityObjectsArr(activity);
function toActivityObjectsArr (obj) {
const arr = [], searchKey = 'ComparatorClass';
Object.keys(obj.Network).forEach(function (networkChild) {
Object.keys(obj.Network[networkChild]).forEach(function (networkGrandChild) {
networkGrandChild == searchKey && arr.push(obj.Network[networkChild]);
Object.keys(obj.Network[networkChild][networkGrandChild]).forEach(function (networkGreatGrandChild) {
networkGreatGrandChild == searchKey && arr.push(obj.Network[networkChild][networkGrandChild]);
Object.keys(obj.Network[networkChild][networkGrandChild][networkGreatGrandChild]).forEach(function (networkGreatGreatGrandChild) {
networkGreatGreatGrandChild == searchKey && arr.push(obj.Network[networkChild][networkGrandChild][networkGreatGrandChild]);
Object.keys(obj.Network[networkChild][networkGrandChild][networkGreatGrandChild][networkGreatGreatGrandChild]).forEach(function (networkGreatGreatGreatGrandChild) {
networkGreatGreatGreatGrandChild == searchKey && arr.push(obj.Network[networkChild][networkGrandChild][networkGreatGrandChild][networkGreatGreatGrandChild]);
Object.keys(obj.Network[networkChild][networkGrandChild][networkGreatGrandChild][networkGreatGreatGrandChild][networkGreatGreatGreatGrandChild]).forEach(function (networkGreatGreatGreatGreatGrandChild) {
networkGreatGreatGreatGreatGrandChild == searchKey && arr.push(obj.Network[networkChild][networkGrandChild][networkGreatGrandChild][networkGreatGreatGrandChild][networkGreatGreatGreatGrandChild]);
Object.keys(obj.Network[networkChild][networkGrandChild][networkGreatGrandChild][networkGreatGreatGrandChild][networkGreatGreatGreatGrandChild][networkGreatGreatGreatGreatGrandChild]).forEach(function (networkGreatGreatGreatGreatGreatGrandChild) {
networkGreatGreatGreatGreatGreatGrandChild == searchKey && arr.push(obj.Network[networkChild][networkGrandChild][networkGreatGrandChild][networkGreatGreatGrandChild][networkGreatGreatGreatGrandChild][networkGreatGreatGreatGreatGrandChild]);
});
});
});
});
});
});
});
createAnswerObj(arr);
}
function createAnswerObj(arr) {
const answerObj = {};
answerObj.totalPossiblePoints = getTotalPossiblePoints(arr);
answerObj.totalPointsEarned = getTotalPointsEarned(arr);
Object.keys(comparatorClassIdToNameMap).forEach(function (k) {
answerObj[comparatorClassIdToNameMap[k]] = {
pointsEarned: getPointsEarnedByComparator(arr, k),
pointsPossible: getPointsPossibleByComparator(arr, k),
ComparatorClassID: k
};
});
console.log(answerObj);
}
function getTotalPossiblePoints(arr) {
return arr.reduce(function (total, obj) {
return total += obj.PointsPossible;
}, 0);
}
function getPointsPossibleByComparator(arr, k) {
return arr.reduce(function (total, obj) {
return obj.ComparatorClass == k ? (total += obj.PointsPossible) : total;
}, 0);
}
function getTotalPointsEarned(arr) {
return arr.reduce(function (total, obj) {
return total += obj.PointsEarned;
}, 0);
}
function getPointsEarnedByComparator(arr, k) {
return arr.reduce(function (total, obj) {
return obj.ComparatorClass == k ? (total += obj.PointsEarned) : total;
}, 0);
}
更新
在我写下下面的解决方案之后,我意识到这真的可以按照您建议的方式更简单地完成,映射,然后减少结果。它不是精确的映射,更多的是树叶的提取。但随后我们可以将您的 comparatorClassIdToNameMap
转换为初始输出对象,然后简单地将这些叶子减少到其中。
虽然下面的代码不是很漂亮,但它确实按照您要求的方式解决了问题,这比我第一次写的解决方案要多。
// utility function
const findLeaves = (isLeaf) => (obj) =>
isLeaf (obj)
? [obj]
: Object .values (obj) .flatMap (
(v) => typeof v == 'object' ? findLeaves (isLeaf) (v) : []
)
// main function
const transform = (actvity, nameMapping) => findLeaves (obj => 'PointsPossible' in obj) (activity)
.reduce((
{totalPointsPossible, totalPointsEarned, ...rest},
{PointsPossible, PointsEarned, ComparatorClass},
_, __, // index and array passed to `reduce` are ignored
{PointsPossible: pp, PointsEarned: pe} = rest[nameMapping[ComparatorClass]]
) => ({ // line 7
totalPointsPossible: totalPointsPossible + PointsPossible,
totalPointsEarned: totalPointsEarned + PointsEarned,
...rest,
[nameMapping[ComparatorClass]]: {
PointsPossible: PointsPossible + pp,
PointsEarned: PointsEarned + pe,
ComparatorClassId: ComparatorClass
}
}), // line 16
Object.entries(comparatorClassIdToNameMap).reduce((a, [k, v]) => ({ // line 17
... a,
[v]: {
PointsPossible: 0,
PointsEarned: 0,
ComparatorClassId: Number(k)
}
}), {totalPointsPossible: 0, totalPointsEarned: 0}) // line 24
)
// data
const activity = {Network: {ID: "Network", MLS: {ID: "MLS", Ports: {ID: "Ports", "GigabitEthernet0/1": {ID: "GigabitEthernet0/1", "Port Mode": {ID: "Port Mode", Name: "Port Mode", Value: "0", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 8}, "Native VLAN": {ID: "Native VLAN", Name: "Native VLAN", Value: "99", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}, "GigabitEthernet0/2": {ID: "GigabitEthernet0/2", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "209.165.200.225", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Subnet Mask": {ID: "Subnet Mask", Name: "Subnet Mask", Value: "255.255.255.252", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: A: : 1": {ID: "2001: DB8: ACAD: A: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: A: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}, SwitchPort: {ID: "SwitchPort", Name: "SwitchPort", Value: "0", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 8}}, Vlan10: {ID: "Vlan10", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.10.254", PointsPossible: 2, PointsEarned: 2, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: 10: : 1": {ID: "2001: DB8: ACAD: 10: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: 10: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}}, Vlan20: {ID: "Vlan20", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.20.254", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: 20: : 1": {ID: "2001: DB8: ACAD: 20: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: 20: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}}, Vlan30: {ID: "Vlan30", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.30.254", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: 30: : 1": {ID: "2001: DB8: ACAD: 30: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: 30: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}}, Vlan99: {ID: "Vlan99", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.99.254", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}, Routes: {ID: "Routes", "Static Routes": {ID: "Static RoutesV2", Name: "Static Routes", Value: "", PointsPossible: 0, PointsEarned: 0, ComparatorClass: 1}, "IP Routing": {ID: "IP Routing", Name: "IP Routing", Value: "1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 1}}, Routesv6: {ID: "Routesv6", "IPv6 Unicast Routing": {ID: "Ipv6 Unicast Routing", Name: "IPv6 Unicast Routing", Value: "1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 1}}, VLANS: {ID: "VLANS", "VLAN 10": {ID: "10", "VLAN Name": {ID: "VLAN Name", Name: "VLAN Name", Value: "Staff", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}, "VLAN 20": {ID: "20", "VLAN Name": {ID: "VLAN Name", Name: "VLAN Name", Value: "Student", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}, "VLAN 30": {ID: "30", "VLAN Name": {ID: "VLAN Name", Name: "VLAN Name", Value: "Faculty", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}}}, S1: {ID: "S1", Ports: {ID: "Ports", "GigabitEthernet0/1": {ID: "GigabitEthernet0/1", "Port Mode": {ID: "Port Mode", Name: "Port Mode", Value: "0", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 8}, "Native VLAN": {ID: "Native VLAN", Name: "Native VLAN", Value: "99", PointsPossible: 2, PointsEarned: 1, ComparatorClass: 5}}}}}};
const comparatorClassIdToNameMap = {0: "Ip", 1: "Routing", 2: "Acl", 3: "Nat", 4: "Physical", 5: "Switching", 6: "Connectivity", 7: "Logical", 8: "All", 9: "Encircling_Head", 10: "Encircling_All"}
// demo
console .log (
transform (activity, comparatorClassIdToNameMap)
)
.as-console-wrapper { max-height: 100% !important; top: 0; }
第 17 - 24 行使用 comparatorClassIdToNameMap
并添加 total*
属性创建一个初始对象,其中包含您想要输出的所有属性。
第 7 - 14 行向其中添加了一个叶节点。这发生在 reduce
内部,因此添加所有叶节点并返回结果对象。
附录
有评论说你JS不精通。这是上述函数的替代版本,以 ES5 风格编写。对于那些来自其他主流语言的人来说,这一篇可能更容易阅读:
function transform (actvity, nameMapping) {
var baseObj = {totalPointsPossible: 0, totalPointsEarned: 0}
for (var id in nameMapping) {
if (nameMapping .hasOwnProperty (id)) {
var name = nameMapping [id]
baseObj [name] = {
PointsPossible: 0,
PointsEarned: 0,
ComparatorClassId: Number(id)
}
}
}
function isLeaf(obj) {
return 'PointsPossible' in obj
}
var leaves = findLeaves (isLeaf) (activity)
return leaves .reduce (
function (acc, item) {
acc .totalPointsPossible += item .PointsPossible
acc .totalPointsEarned += item .PointsEarned
var key = nameMapping [item .ComparatorClass]
acc [key] .PointsPossible += item .PointsPossible
acc [key] .PointsEarned += item .PointsEarned
return acc
},
baseObj
)
}
这个和上面的完全一样。但它充满了赋值、变更和控制流语句,所有这些都是我个人试图避免的。上面的版本通过使用解构和其他更现代的技术避免了所有这些。
附录 2
通过使用 ES5 工具填充 Object.prorotype.flatMap
和 Object.values
并将箭头函数替换为函数表达式,将 findLeaves
转换为 ES5:
const values = function (obj) {
return Object .keys (obj) .map (k => obj [k])
}
const flatMap = function (fn) {
return function (xs) {
return xs .reduce ((a, x) => a .concat (fn (x)), [])
}
}
const findLeaves = function (isLeaf) {
return function(obj) {
return isLeaf (obj)
? [obj]
: flatMap (
(v) => typeof v == 'object' ? findLeaves (isLeaf) (v) : []
) (values (obj))
}
}
原解
我不再推荐以下两个建议,但我认为它们可能仍然有价值。
使用库
我是函数式编程库的忠实粉丝Ramda(免责声明:我是它的主要作者之一),我经常通过 Ramda 的眼睛看到这样的问题。所以首先,一个相当大量使用 Ramda 的解决方案:
// utility function
const findLeaves = (isLeaf) => (obj) =>
isLeaf (obj)
? [obj]
: Object .values (obj) .flatMap (
(v) => typeof v == 'object' ? findLeaves (isLeaf) (v) : []
)
// helper functions
const summarize = pipe (
findLeaves (has('PointsPossible')),
groupBy (prop ('ComparatorClass')),
map ( applySpec ({
PointsPossible: pipe (pluck ('PointsPossible'), sum),
PointsEarned: pipe (pluck ('PointsEarned'), sum),
ComparatorClassID: pipe (head, prop ('ComparatorClass'))
}))
)
const extractTotals = (activity, nameMapping, summary = summarize(activity)) =>
reduce (
(a, [id, name]) =>
({...a, [name]: summary [id] ||
{PointsPossible: 0, PointsEarned: 0, ComparatorClassID: id}}),
{},
toPairs (nameMapping)
)
// main function
const transform = (activity, nameMapping, totals = extractTotals (activity, nameMapping)) =>
Object .assign (applySpec ({
totalPointsPossible: pipe (pluck ('PointsPossible'), sum),
totalPointsEarned: pipe (pluck ('PointsEarned'), sum),
}) (values (totals)), totals)
// data
const activity = {Network: {ID: "Network", MLS: {ID: "MLS", Ports: {ID: "Ports", "GigabitEthernet0/1": {ID: "GigabitEthernet0/1", "Port Mode": {ID: "Port Mode", Name: "Port Mode", Value: "0", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 8}, "Native VLAN": {ID: "Native VLAN", Name: "Native VLAN", Value: "99", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}, "GigabitEthernet0/2": {ID: "GigabitEthernet0/2", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "209.165.200.225", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Subnet Mask": {ID: "Subnet Mask", Name: "Subnet Mask", Value: "255.255.255.252", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: A: : 1": {ID: "2001: DB8: ACAD: A: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: A: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}, SwitchPort: {ID: "SwitchPort", Name: "SwitchPort", Value: "0", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 8}}, Vlan10: {ID: "Vlan10", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.10.254", PointsPossible: 2, PointsEarned: 2, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: 10: : 1": {ID: "2001: DB8: ACAD: 10: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: 10: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}}, Vlan20: {ID: "Vlan20", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.20.254", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: 20: : 1": {ID: "2001: DB8: ACAD: 20: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: 20: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}}, Vlan30: {ID: "Vlan30", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.30.254", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: 30: : 1": {ID: "2001: DB8: ACAD: 30: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: 30: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}}, Vlan99: {ID: "Vlan99", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.99.254", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}, Routes: {ID: "Routes", "Static Routes": {ID: "Static RoutesV2", Name: "Static Routes", Value: "", PointsPossible: 0, PointsEarned: 0, ComparatorClass: 1}, "IP Routing": {ID: "IP Routing", Name: "IP Routing", Value: "1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 1}}, Routesv6: {ID: "Routesv6", "IPv6 Unicast Routing": {ID: "Ipv6 Unicast Routing", Name: "IPv6 Unicast Routing", Value: "1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 1}}, VLANS: {ID: "VLANS", "VLAN 10": {ID: "10", "VLAN Name": {ID: "VLAN Name", Name: "VLAN Name", Value: "Staff", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}, "VLAN 20": {ID: "20", "VLAN Name": {ID: "VLAN Name", Name: "VLAN Name", Value: "Student", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}, "VLAN 30": {ID: "30", "VLAN Name": {ID: "VLAN Name", Name: "VLAN Name", Value: "Faculty", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}}}, S1: {ID: "S1", Ports: {ID: "Ports", "GigabitEthernet0/1": {ID: "GigabitEthernet0/1", "Port Mode": {ID: "Port Mode", Name: "Port Mode", Value: "0", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 8}, "Native VLAN": {ID: "Native VLAN", Name: "Native VLAN", Value: "99", PointsPossible: 2, PointsEarned: 1, ComparatorClass: 5}}}}}};
const comparatorClassIdToNameMap = {0: "Ip", 1: "Routing", 2: "Acl", 3: "Nat", 4: "Physical", 5: "Switching", 6: "Connectivity", 7: "Logical", 8: "All", 9: "Encircling_Head", 10: "Encircling_All"}
// demo
console .log (
transform (activity, comparatorClassIdToNameMap)
)
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
<script>
const {pipe, has, groupBy, prop, map, applySpec, pluck, sum, head, reduce, toPairs, values} = R
</script>
我们的主要 transform
函数依赖于 extractTotals
,后者依赖于 summarize
,后者又依赖于 findLeaves
。显然,由于 extractTotals
和 summarize
仅在内部使用,因此您可以将它们内部化;我不推荐它。 findLeaves
是递归的,所以不能那样处理。
findLeaves
接受一个谓词函数来测试一个节点是否是叶子和对象,并递归地找到所有被报告为叶子的节点。它不处理数组,但这不是您要求的一部分;如果需要,扩展并不难。
如果我们调用 findLeaves(obj => 'PointsPossible' in obj)(activity)
,我们会得到一个包含所有叶节点的平面数组,如下所示:
[
{
"ID": "Port Mode",
"Name": "Port Mode",
"Value": "0",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 8
},
{
"ID": "Native VLAN",
"Name": "Native VLAN",
"Value": "99",
"PointsPossible": 1,
"PointsEarned": 1,
"ComparatorClass": 5
},
// ...
]
summarize
使用 findLeaves
和一些 Ramda 函数将你的 activity
变成
{
"0": {
"PointsPossible": 15,
"PointsEarned": 15,
"ComparatorClassID": 0
},
"1": {
"PointsPossible": 2,
"PointsEarned": 2,
"ComparatorClassID": 1
},
// ...
}
我们通过 extractTotals
几乎一路走来,它将上述内容与您的 comparatorClassIdToNameMap
结合起来得到这个:
{
"Ip": {
"PointsPossible": 15,
"PointsEarned": 15,
"ComparatorClassID": 0
},
"Routing": {
"PointsPossible": 2,
"PointsEarned": 2,
"ComparatorClassID": 1
},
// ...
}
唯一缺少的是总计。留给 transform
,它采用上述结果并将这些总数添加到结果对象中:
{
"totalPointsPossible": 26,
"totalPointsEarned": 25,
"Ip": {
"PointsPossible": 15,
"PointsEarned": 15,
"ComparatorClassID": 0
},
"Routing": {
"PointsPossible": 2,
"PointsEarned": 2,
"ComparatorClassID": 1
},
// ...
}
(注意 这里没有任何东西可以处理你的 itemsComplete
或 itemsPossible
。我在数据中看不到任何帮助,但我我猜是对上述内容的简单扩展。)
这个问题的细分是我通常的技术,一次做一个小的转换,总是试图朝着所需的解决方案前进。可能有一些有用的方法来组合步骤。我把它留给你。
没有图书馆
像 Ramda 这样的库没有魔法。我们可以自己编写其功能的可重用版本。这样做通常会改进我们代码的许多部分。
因此,我们可以通过编写这些 Ramda 函数的自定义版本,自行完成相同的分解。大多数都是微不足道的。只有 applySpec
、groupBy
和 mapObj
有任何复杂性。注意,在上面的 Ramda 版本中,我们使用 Ramda 的 map
来代替这里的 mapObj
。虽然我们可以编写 map
来处理数组或对象,但这里只创建两个函数更简单。
// utility functions
const pipe = (...fns) => (arg) =>
fns.reduce((a, fn) => fn(a), arg)
const map = (fn) => (xs) =>
xs .map (x => fn (x))
const mapObj = (fn) => (obj) =>
Object .fromEntries (map (([k, v]) => [k, fn (v)]) (Object .entries (obj)))
const has = (name) => (obj) =>
name in obj
const prop = (name) => (obj) =>
obj [name]
const pluck = (name) =>
map (prop (name))
const head = (xs) =>
xs [0]
const sum = (ns) =>
ns .reduce ((a, b) => a + b, 0)
const applySpec = (spec) => (obj) =>
Object .entries (spec) .reduce ((a, [k, fn]) => ({...a, [k]: fn (obj)}), {})
const groupBy = (fn) => (xs) =>
xs .reduce ((a, x, _, __, k = fn(x)) => ({...a, [k]: [...(a[k] || []), x]}), {})
const findLeaves = (isLeaf) => (obj) =>
isLeaf (obj)
? [obj]
: Object .values (obj) .flatMap (
(v) => typeof v == 'object' ? findLeaves (isLeaf) (v) : []
)
// helper functions
const summarize = pipe (
findLeaves (has ('PointsPossible')),
groupBy (prop ('ComparatorClass')),
mapObj ( applySpec ({
PointsPossible: pipe (pluck ('PointsPossible'), sum),
PointsEarned: pipe (pluck ('PointsEarned'), sum),
ComparatorClassID: pipe (head, prop ('ComparatorClass'))
}))
)
const extractTotals = (activity, nameMapping, summary = summarize(activity)) =>
Object .entries (nameMapping)
.reduce (
(a, [id, name]) => ({...a, [name]: summary[id] || {PointsPossible: 0, PointsEarned: 0, ComparatorClassID: id}}),
{}
)
// main function
const transform = (activity, nameMapping) => {
const totals = extractTotals (activity, nameMapping)
return Object .assign (applySpec ({
totalPointsPossible: pipe (pluck ('PointsPossible'), sum),
totalPointsEarned: pipe (pluck ('PointsEarned'), sum),
}) (Object .values (totals)), totals)
}
// data
const activity = {Network: {ID: "Network", MLS: {ID: "MLS", Ports: {ID: "Ports", "GigabitEthernet0/1": {ID: "GigabitEthernet0/1", "Port Mode": {ID: "Port Mode", Name: "Port Mode", Value: "0", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 8}, "Native VLAN": {ID: "Native VLAN", Name: "Native VLAN", Value: "99", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}, "GigabitEthernet0/2": {ID: "GigabitEthernet0/2", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "209.165.200.225", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Subnet Mask": {ID: "Subnet Mask", Name: "Subnet Mask", Value: "255.255.255.252", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: A: : 1": {ID: "2001: DB8: ACAD: A: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: A: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}, SwitchPort: {ID: "SwitchPort", Name: "SwitchPort", Value: "0", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 8}}, Vlan10: {ID: "Vlan10", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.10.254", PointsPossible: 2, PointsEarned: 2, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: 10: : 1": {ID: "2001: DB8: ACAD: 10: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: 10: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}}, Vlan20: {ID: "Vlan20", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.20.254", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: 20: : 1": {ID: "2001: DB8: ACAD: 20: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: 20: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}}, Vlan30: {ID: "Vlan30", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.30.254", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "(deprecated) IPv6 Addresses": {ID: "Ipv6 Address", "2001: DB8: ACAD: 30: : 1": {ID: "2001: DB8: ACAD: 30: : 1", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "2001: DB8: ACAD: 30: : 1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}, "Prefix Length": {ID: "Prefix", Name: "Prefix Length", Value: "64", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}}, Vlan99: {ID: "Vlan99", "IP Address": {ID: "IP Address", Name: "IP Address", Value: "192.168.99.254", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 0}}}, Routes: {ID: "Routes", "Static Routes": {ID: "Static RoutesV2", Name: "Static Routes", Value: "", PointsPossible: 0, PointsEarned: 0, ComparatorClass: 1}, "IP Routing": {ID: "IP Routing", Name: "IP Routing", Value: "1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 1}}, Routesv6: {ID: "Routesv6", "IPv6 Unicast Routing": {ID: "Ipv6 Unicast Routing", Name: "IPv6 Unicast Routing", Value: "1", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 1}}, VLANS: {ID: "VLANS", "VLAN 10": {ID: "10", "VLAN Name": {ID: "VLAN Name", Name: "VLAN Name", Value: "Staff", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}, "VLAN 20": {ID: "20", "VLAN Name": {ID: "VLAN Name", Name: "VLAN Name", Value: "Student", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}, "VLAN 30": {ID: "30", "VLAN Name": {ID: "VLAN Name", Name: "VLAN Name", Value: "Faculty", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 5}}}}, S1: {ID: "S1", Ports: {ID: "Ports", "GigabitEthernet0/1": {ID: "GigabitEthernet0/1", "Port Mode": {ID: "Port Mode", Name: "Port Mode", Value: "0", PointsPossible: 1, PointsEarned: 1, ComparatorClass: 8}, "Native VLAN": {ID: "Native VLAN", Name: "Native VLAN", Value: "99", PointsPossible: 2, PointsEarned: 1, ComparatorClass: 5}}}}}};
const comparatorClassIdToNameMap = {0: "Ip", 1: "Routing", 2: "Acl", 3: "Nat", 4: "Physical", 5: "Switching", 6: "Connectivity", 7: "Logical", 8: "All", 9: "Encircling_Head", 10: "Encircling_All"}
// demo
console .log (
transform (activity, comparatorClassIdToNameMap)
)
.as-console-wrapper { max-height: 100% !important; top: 0; }
这使用与上述相同的问题分解,只需编写 Ramda 函数的自定义版本。如果您想了解更多关于它们的信息(mapObj
除外,它在 Ramda 中由 map
涵盖),请参阅 Ramda documentation。这些实现比 Ramda 的更简单,通常不太灵活,但它们适用于我们的案例。
备注
我发现这是一个值得尝试的有趣问题。但是因为数据墙,我几乎马上就放弃了。滚动浏览那么多数据以试图辨别需求是非常困难的。下次,请在发帖前查看 How to create a Minimal, Reproducible Example。您应该能够制作一个更小的测试用例,但仍能证明目标。