使用lodash.groupBy()标准化对象

时间:2018-11-24 11:39:23

标签: javascript lodash ramda.js

我有一个由三个不同对象组成的非规范化数组-country, school, student

const input= 
 [
    {
      countryID: 12,
      country: "US",
      schoolID: 122,
      schoolZone: "Highlands",
      studentID: 142,
      studentName: "Mary"
    },
    {
      countryID: 12,
      country: "US",
      schoolID: 122,
      schoolZone: "Highlands",
      studentID: 145,
      studentName: "John"
    },
    {
      countryID: 14,
      country: "UK",
      schoolID: 222,
      schoolZone: "Leeds",
      studentID: 223,
      studentName: "Peter"
    }
  ];

我想像这样规范他们

const result= 
 [
    {
      countryID: 12,
      country: "US",
      schools: [
        {
          schoolID: 122,
          schoolZone: "Highlands",
          students: [
            {
              studentID: 142,
              studentName: "Mary"
            },
            {
              studentID: 145,
              studentName: "John"
            }
          ]
        }
      ]
    },
    {
      countryID: 14,
      country: "UK",
      schools: [
        {
          schoolID: 222,
          schoolZone: "Leeds",
          students: [
            {
              studentID: 223,
              studentName: "Peter"
            }
          ]
        }
      ]
    }
  ];

我了解lodashramda可用于实现嵌套归一化

但是我对这两个库都是新手。

您能帮我实施吗?还想知道如果考虑到schoolsstudents

的嵌套,是否会考虑使用currying

3 个答案:

答案 0 :(得分:1)

不使用lodash或ramda的方法可能是这样。希望这会有所帮助。

const input= [{
    countryID: 12,
    country: "US",
    schoolID: 122,
    schoolZone: "Highlands",
    studentID: 142,
    studentName: "Mary"
}, {
    countryID: 12,
    country: "US",
    schoolID: 122,
    schoolZone: "Highlands",
    studentID: 145,
    studentName: "John"
}, {
    countryID: 14,
    country: "UK",
    schoolID: 222,
    schoolZone: "Leeds",
    studentID: 223,
    studentName: "Peter"
}];

const normalize = (data) =>

    data.reduce((acc, { studentID, studentName, schoolID, schoolZone, countryID, country }) => {

        const studentToAdd = { studentID, studentName };

        const schoolToAdd = { schoolID, schoolZone, students: [studentToAdd] };

        const countryToAdd = { countryID, country, schools: [schoolToAdd] };

        const existingCountry = acc.find(item => item.countryID === countryID);

        if (existingCountry) {

            const existingSchool = existingCountry.schools.find(item => item.schoolID === schoolID);

            if (existingSchool) {

                const existingStudent = existingSchool.students.find(item => item.studentID === studentID);

                if (!existingStudent) existingSchool.students.push(studentToAdd);
            }

            else existingCountry.schools.push(schoolToAdd);
        }

        else acc.push(countryToAdd);

        return acc;
    }, []);

console.log(normalize(input));

答案 1 :(得分:1)

使用lodash,您可以通过以下简洁的方法解决此问题:

const data = [{ countryID: 12, country: "US", schoolID: 122, schoolZone: "Highlands", studentID: 142, studentName: "Mary" }, { countryID: 12, country: "US", schoolID: 122, schoolZone: "Highlands", studentID: 145, studentName: "John" }, { countryID: 14, country: "UK", schoolID: 222, schoolZone: "Leeds", studentID: 223, studentName: "Peter" } ];

const grp = (arr, key, field, group, o=0) => _(arr)
   .groupBy(key)
   .mapValues(x => ({ 
     [key]: _.head(x)[key], 
     [field]: _.head(x)[field],
     [group]: o 
       ? _.map(x, s => _.pick(s, ['studentID', 'studentName'])) 
       : grp(x, 'schoolID', 'schoolZone', 'students', 1)
     }))
    .values()
   .value()
console.log(grp(data, 'countryID', 'country', 'schools'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>

该想法是使用递归方法,其中两次调用同一分组函数。在其中,我们将_.groupBy进行_.mapValues

现在,使用紧凑的 ES6 代码的想法完全相同:

const data = [{ countryID: 12, country: "US", schoolID: 122, schoolZone: "Highlands", studentID: 142, studentName: "Mary" }, { countryID: 12, country: "US", schoolID: 122, schoolZone: "Highlands", studentID: 145, studentName: "John" }, { countryID: 14, country: "UK", schoolID: 222, schoolZone: "Leeds", studentID: 223, studentName: "Peter" } ];

const groupBy = (arr, k) => arr.reduce((r,c) => (r[c[k]] = [...r[c[k]] || [], c], r), {})
const pick = (obj, fields) => fields.reduce((r,f) => (r[f] = obj[f], r), {})

const grp = (arr, k, f, b, o=0) => 
   Object.values(Object.values(groupBy(arr, k)).map(x => ({ 
     [k]: x[0][k], 
     [f]: x[0][f],
     [b]: o 
      ? x.map(s => pick(s, ['studentID', 'studentName'])) 
      : grp(x, 'schoolID', 'schoolZone', 'students', 1)
   })))

console.log(grp(data, 'countryID', 'country', 'schools'))

答案 2 :(得分:0)

首先将每个对象重塑为最终的数据结构:

reshape({
  countryID: 12,
  country: "US",
  schoolID: 122,
  schoolZone: "Highlands",
  studentID: 142,
  studentName: "Mary" });

//=> {
//=>     country: "US",
//=>     countryID: 12,
//=>     schools: [
//=>         {
//=>             schoolID: 122,
//=>             schoolZone: "Highlands",
//=>             students: [
//=>                 {
//=>                     studentID: 142,
//=>                     studentName: "Mary"
//=>                 }
//=>             ]
//=>         }
//=>     ]
//=> }

那么这仅仅是一个问题,首先要按国家分组,然后按学校分组,并使用地图和归约功能合并对象。

const {pick, map, groupBy, values, lensProp, over, reduce, mergeWithKey, pipe, prop, applySpec, converge, set, of} = R;

const input = [
  { countryID: 12,
    country: "US",
    schoolID: 122,
    schoolZone: "Highlands",
    studentID: 142,
    studentName: "Mary" },
  
  { countryID: 12,
    country: "US",
    schoolID: 122,
    schoolZone: "Highlands",
    studentID: 145,
    studentName: "John" },
  
  { countryID: 14,
    country: "UK",
    schoolID: 222,
    schoolZone: "Leeds",
    studentID: 223,
    studentName: "Peter" }];

const reshape = applySpec({
  countryID: prop('countryID'),
  country: prop('country'),
  schools: converge(
    pipe(set(lensProp('students')), of), [
      pipe(pick(['studentID', 'studentName']), of),
      pick(['schoolID', 'schoolZone'])])
});

const mergeArrays = key => mergeWithKey((k, a, b) => k === key ? [a[0], b[0]] : a);

const groupStudents = pipe(
  groupBy(prop('schoolID')),
  values,
  map(reduce(mergeArrays('students'), {})));

const process = pipe(
  map(reshape),
  groupBy(prop('countryID')),
  map(reduce(mergeArrays('schools'), {})),
  map(over(lensProp('schools'), groupStudents)),
  values);

console.log(process(input));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>