RamdaJS transform object and do lookup with another lists - ramda.js

Continuing from RamdaJS groupBy and tranform object
let children = [
{ "name": "Bob", "age": 8, "father": "Mike" },
{ "name": "David", "age": 10, "father": "Mike" },
{ "name": "Amy", "age": 2, "father": "Mike" },
{ "name": "Jeff", "age": 11, "father": "Jack" }
]
let schoolList = [
{ "name": "Bob", "class": "8-A", "school": "School 1" },
{ "name": "David", "class": "10-B", "school": "School 1" },
{ "name": "Amy", "class": "2-A", "school": "School 1" },
{ "name": "Jeff", "class": "11-C", "school": "School 1" }
]
What have i done so far.
R.pipe(
R.groupBy(R.prop('father')),
R.map(R.applySpec({
father: R.pipe(R.head, R.prop('father')),
count: R.length,
kids: R.map(R.dissoc('father')),
class: R.map(R.prop('class'))R.find(R.propEq('name',R.pipe(R.head, R.prop('name'))), schoolList)
})),
R.values()
)(children)
Expected output is class property is appended to nested kids array.
{
"father": "Jack",
"count" : 1,
"kids": [
{ "name": "Jeff", "age": 11, "class" : "11-C" }
]
}

If the actual schoolList is quite long, I would use the answer from OriDrori. But if it's fairly short, then this might be a bit cleaner, even if less efficient:
const expandChild = schoolList => child => ({
...child,
class: defaultTo(
{class: 'unknown'},
find(propEq('name', child.name), schoolList)
).class
})
const groupChildren = schoolList => pipe(
groupBy(prop('father')),
map(applySpec({
father: pipe(head, prop('father')),
count: length,
kids: map(pipe(dissoc('father'), expandChild(schoolList)))
})),
values,
)
let children = [{ name: "Bob", age: 8, father: "Mike" }, { name: "David", age: 10, father: "Mike" }, { name: "Amy", age: 2, father: "Mike" }, { name: "Jeff", age: 11, father: "Jack" }, { name: "Sue", age: 9, father: "Jack" }]
let schoolList = [{ name: "Bob", class: "8-A", school: "School 1" }, { name: "David", class: "10-B", school: "School 1" }, { name: "Amy", class: "2-A", school: "School 1" }, { name: "Jeff", class: "11-C", school: "School 1" }]
console.log (groupChildren (schoolList) (children))
<script src="https://bundle.run/ramda#0.26.1"></script><script>
const {defaultTo, find, propEq, pipe, groupBy, map, applySpec, head, prop, length, dissoc, values} = ramda
</script>
Note the default value given for school in case the child's name is not in schoolList.

Convert the schoolList to a dictionary of objects by name with R.indexBy. Create pickFromLookupByName by using a flipped prop to take from the lookup. Use pick to extract the props you want.
To add the class to the kid object use R.converge that takes the kid object with R.identity, and uses pickClass to get an object that contains the class, and merges both with R.mergeRight.
const { pipe, groupBy, prop, applySpec, head, length, map, dissoc, values, indexBy, converge, mergeRight, flip, pick, identity } = R
const schoolList = [{"name":"Bob","class":"8-A","school":"School 1"},{"name":"David","class":"10-B","school":"School 1"},{"name":"Amy","class":"2-A","school":"School 1"},{"name":"Jeff","class":"11-C","school":"School 1"}]
const schoolLookup = indexBy(prop('name'), schoolList);
const pickFromLookupByName = (propList) => pipe(
flip(prop)(schoolLookup),
pick(propList),
)
const pickClass = pickFromLookupByName(['class']) // convenience method to get an object containing the class
const fn = pipe(
groupBy(prop('father')),
map(applySpec({
father: pipe(head, prop('father')),
count: length,
kids: map(pipe(
dissoc('father'),
converge(mergeRight, [identity, pipe(prop('name'), pickClass)])
))
})),
values
)
const children = [{"name":"Bob","age":8,"father":"Mike"},{"name":"David","age":10,"father":"Mike"},{"name":"Amy","age":2,"father":"Mike"},{"name":"Jeff","age":11,"father":"Jack"}]
const result = fn(children)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

Related

Lodash and merge of Arrays with Key

I am able to merge objects stored in array fine with lodash but what I need is the find a way to merge based on a key which is part of the object. If I don't use key, the merge is not reliable as when a doc is returned out of order, as it will create an invalid merge. So I hope that there is a way to merge based on a key value, in my case its id.
Here is some sample:
Doc 1
[
{
"id": 123,
"Key1": "Test 1",
"Key3": "Test 0"
},
{
"id": 456,
"Key1": "Test 2",
"Key2": "Test 3"
}
]
Doc 2
[
{
"id": 123,
"Key2": "Test 7",
"Key3": "Test 8"
},
{
"id": 789,
"Key1": "Test 5",
"Key2": "Test 6"
}
]
Based on the simple sample above i am looking for an output like this
[
{
"id": 123,
"Key1": "Test 1",
"Key2": "Test 7",
"Key3": "Test 8"
},
{
"id": 456,
"Key1": "Test 2",
"Key2": "Test 3"
},
{
"id": 789,
"Key1": "Test 5",
"Key2": "Test 6"
}
]
const addKeys = (results, keys) => keys.reduce(
(final, item) => {
const id = item.id;
let current = final.find(i => i.id === id);
if (!current) {
current = { id: id };
final.push(current);
}
Object.keys(item).forEach(key => { current[key] = item[key] });
return final;
}, results
);
console.log(addKeys(
[
{
"id": 123,
"Key1": "Test 1",
"Key3": "Test 0"
},
{
"id": 456,
"Key1": "Test 2",
"Key2": "Test 3"
}
],
[
{
"id": 123,
"Key2": "Test 7",
"Key3": "Test 8"
},
{
"id": 789,
"Key1": "Test 5",
"Key2": "Test 6"
}
]
));
Use flow to create a function. Concat the arrays to a single array, group them by the id, and then map the groups, and merge each group to a single object:
const { flow, concat, partialRight: pr, groupBy, map, merge } = _
const mergeArrays = flow(
concat, // concat to a single array
pr(groupBy, 'id'), // group item by id
pr(map, g => merge({}, ...g)) // merge each group to a single object
)
const arr1 = [{"id":123,"Key1":"Test 1","Key3":"Test 0"},{"id":456,"Key1":"Test 2","Key2":"Test 3"}]
const arr2 = [{"id":123,"Key2":"Test 7","Key3":"Test 8"},{"id":789,"Key1":"Test 5","Key2":"Test 6"}]
const result = mergeArrays(arr1, arr2)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
And a terser version of this solution with lodash/fp:
const { flow, concat, groupBy, map, mergeAll } = _
const mergeArrays = flow(
concat, // concat to a single array
groupBy('id'), // group item by id
map(mergeAll) // merge each group to a single object
)
const arr1 = [{"id":123,"Key1":"Test 1","Key3":"Test 0"},{"id":456,"Key1":"Test 2","Key2":"Test 3"}]
const arr2 = [{"id":123,"Key2":"Test 7","Key3":"Test 8"},{"id":789,"Key1":"Test 5","Key2":"Test 6"}]
const result = mergeArrays(arr1, arr2)
console.log(result)
<script src='https://cdn.jsdelivr.net/g/lodash#4(lodash.min.js+lodash.fp.min.js)'></script>

Vue JS2 find array value by id

How can i get the value by id from a array in vue js? Many thanks with solution
list = [
{
"name": "Apple",
"id": 1,
},
{
"name": "Orange",
"id": 2,
}
]
watch: {
food: function (val) {
//Get food name by val(id)
}
}
Use Array.find method, which returns the value of the first element in the array that satisfies the provided testing function:
var food = list.find(food => food.id === val)
var name = food ? null : food.name
let list = [
{
"name": "Apple",
"id": 1,
},
{
"name": "Orange",
"id": 2,
}
]
console.log(
list.find(food => food.id === 1).name
)

find object in nested array with lodash

I have json data similar to this:
{
"Sections": [
{
"Categories": [
{
"Name": "Book",
"Id": 1,
"Options": [
{
"Name": "AAAA",
"OptionId": 111
},
"Selected": 0
},
{
"Name": "Car",
"Id": 2,
"Options": [
{
"Name": "BBB",
"OptionId": 222
},
"Selected": 0
},
],
"SectionName": "Main"
},
... more sections like the one above
]
}
Given this data, I want to find a category inside a section based on its (Category) Id, and set its selected option, I tried this, but couldn't get it to work....Note Category Id will be unique in the whole data set.
_.find(model.Sections, { Categories: [ { Id: catId } ]});
According to your data model, it looks like you're trying to find an element that is inside a matrix: Sections can have multiple Categories and a Category can have multiple types (car, book...).
I'm afraid there isn't a function in lodash that allows a deep find, you'll have to implement it the 'traditional' way (a couple of fors).
I provide this solution that is a bit more 'functional flavoured' than the traditional nested fors. It also takes advantage of the fact that when you explicitly return false inside a forEach, the loop finishes. Thus, once an element with the provided id is found, the loop is ended and the element returned (if it's not found, undefined is returned instead).
Hope it helps.
const findCategoryById = (sections, id) => {
var category;
_.forEach(sections, (section) => {
category = _.find(section.Categories, ['Id', id]);
return _.isUndefined(category);
});
return category;
};
const ex = {
"Sections": [{
"Categories": [{
"Name": "Book",
"Id": 1,
"Options": [{
"Name": "AAAA",
"OptionId": 111
}],
"Selected": 0
},
{
"Name": "Car",
"Id": 2,
"Options": [{
"Name": "BBB",
"OptionId": 222
}],
"Selected": 0
}
],
"SectionName": "Main"
}]
};
console.log(findCategoryById(ex.Sections, 2));
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.5/lodash.min.js"></script>

Datatables with nested array

I've posed a question about Bootstrap Tables but meanwhile I moved to Datatables as I was feeling blocked. My problem, however, is the same.
None of the two can easily handle nested JSON results. For instance if I choose "field: author", it processes the following as "[Object Object], [Object Object]".
"author": [
{
"family": "Obama",
"given": "Barack"
},
{
"family": "Obama",
"given": "Michelle"
}
I can select the results individually, say "field: author[, ].family", which returns a list like "Obama, Obama". But I want an output like "given+family1, given+family2, ..".
You can use custom rendering. DataTables allows you to define custom rendering for each column.
Here is a sample that I worked out. I am doing custom rendering for Author column.
$(document).ready(function() {
var dataSet = [
{ "name": "How to DataTables", "author": [{ "firstname": "jack", lastname: "d" }, { "firstname": "dick", lastname: "j" }] },
{ "name": "How to Custom Render", "author": [{ "firstname": "bill", lastname: "g" }, { "firstname": "scott", lastname: "g" }] }
];
$('#example').DataTable({
data: dataSet,
columns: [
{ title:"Book Name",
data: "name" },
{
title: "Authors",
data: "author",
render: function(data, type, row) {
//return data.length;
var txt = '';
data.forEach(function(item) {
if (txt.length > 0) {
txt += ', '
}
txt += item.firstname + ' ' + item.lastname;
});
return txt;
}
}
]
});
});

Transform JSON response with lodash

I'm new in lodash (v3.10.1), and having a hard time understanding.
Hope someone can help.
I have an input something like this:
{
{"id":1,"name":"Matthew","company":{"id":1,"name":"abc","industry":{"id":5,"name":"Medical"}}},
{"id":2,"name":"Mark","company":{"id":1,"name":"abc","industry":{"id":5,"name":"Medical"}}},
{"id":3,"name":"Luke","company":{"id":1,"name":"abc","industry":{"id":5,"name":"Medical"}}},
{"id":4,"name":"John","company":{"id":1,"name":"abc","industry":{"id":5,"name":"Medical"}}},
{"id":5,"name":"Paul","company":{"id":1,"name":"abc","industry":{"id":5,"name":"Medical"}}}
];
I would like to output this or close to this:
{
"industries": [
{
"industry":{
"id":5,
"name":"Medical",
"companies": [
{
"company":{
"id":1,
"name":"abc",
"employees": [
{"id":1,"name":"Matthew"},
{"id":2,"name":"Mark"},
{"id":3,"name":"Luke"},
{"id":4,"name":"John"},
{"id":5,"name":"Paul"}
]
}
}
]
}
}
]
}
Here's something that gets you close to what you want. I structured the output to be an object instead of an array. You don't need the industries or industry properties in your example output. The output structure looks like this:
{
"industry name": {
"id": "id of industry",
"companies": [
{
"company name": "name of company",
"id": "id of company",
"employees": [
{
"id": "id of company",
"name": "name of employee"
}
]
}
]
}
}
I use the _.chain function to wrap the collection with a lodash wrapper object. This enables me to explicitly chain lodash functions.
From there, I use the _.groupBy function to group elements of the collection by their industry name. Since I'm chaining, I don't have to pass in the array again to the function. It's implicitly passed via the lodash wrapper. The second argument of the _.groupBy is the path to the value I want to group elements by. In this case, it's the path to the industry name: company.industry.name. _.groupBy returns an object with each employee grouped by their industry (industries are keys for this object).
I then do use _.transform to transform each industry object. _.transform is essentially _.reduce except that the results returned from the _.transform function is always an object.
The function passed to the _.transform function gets executed against each key/value pair in the object. In the function, I use _.groupBy again to group employees by company. Based off the results of _.groupBy, I map the values to the final structure I want for each employee object.
I then call the _.value function because I want to unwrap the output collection from the lodash wrapper object.
I hope this made sense. If it doesn't, I highly recommend reading Lo-Dash Essentials. After reading the book, I finally got why lodash is so useful.
"use strict";
var _ = require('lodash');
var emps = [
{ "id": 1, "name": "Matthew", "company": { "id": 1, "name": "abc", "industry": { "id": 5, "name": "Medical" } } },
{ "id": 2, "name": "Mark", "company": { "id": 1, "name": "abc", "industry": { "id": 5, "name": "Medical" } } },
{ "id": 3, "name": "Luke", "company": { "id": 1, "name": "abc", "industry": { "id": 5, "name": "Medical" } } },
{ "id": 4, "name": "John", "company": { "id": 1, "name": "abc", "industry": { "id": 5, "name": "Medical" } } },
{ "id": 5, "name": "Paul", "company": { "id": 1, "name": "abc", "industry": { "id": 5, "name": "Medical" } } }
];
var result = _.chain(emps)
.groupBy("company.industry.name")
.transform(function(result, employees, industry) {
result[industry] = {};
result[industry].id = _.get(employees[0], "company.industry.id");
result[ industry ][ 'companies' ] = _.map(_.groupBy(employees, "company.name"), function( employees, company ) {
return {
company: company,
id: _.get(employees[ 0 ], 'company.id'),
employees: _.map(employees, _.partialRight(_.pick, [ 'id', 'name' ]))
};
});
return result;
})
.value();
Results from your example are as follows:
{
"Medical": {
"id": 5,
"companies": [
{
"company": "abc",
"id": 1,
"employees": [
{
"id": 1,
"name": "Matthew"
},
{
"id": 2,
"name": "Mark"
},
{
"id": 3,
"name": "Luke"
},
{
"id": 4,
"name": "John"
},
{
"id": 5,
"name": "Paul"
}
]
}
]
}
}
If you ever wanted the exact same structure as in the questions, I solved it using the jsonata library:
(
/* lets flatten it out for ease of accessing the properties*/
$step1 := $ ~> | $ |
{
"employee_id": id,
"employee_name": name,
"company_id": company.id,
"company_name": company.name,
"industry_id": company.industry.id,
"industry_name": company.industry.name
},
["company", "id", "name"] |;
/* now the magic begins*/
$step2 := {
"industries":
[($step1{
"industry" & $string(industry_id): ${
"id": $distinct(industry_id)#$I,
"name": $distinct(industry_name),
"companies": [({
"company" & $string(company_id): {
"id": $distinct(company_id),
"name": $distinct(company_name),
"employees": [$.{
"id": $distinct(employee_id),
"name": $distinct(employee_name)
}]
}
} ~> $each(function($v){ {"company": $v} }))]
}
} ~> $each(function($v){ {"industry": $v} }))]
};
)
You can see it in action on the live demo site: https://try.jsonata.org/VvW4uTRz_