5

I'm going to write a function to sort any JSON with somehow any structure (just know it's an array of objects, e.g. a list of products) by using another object as its argument to determine which sorting is performed according to which key.

// The json that I get might looks like something like this.
// I just write one item of the array, but all of them are the same.
// But the blueprint of the items in each json are different.
const dataArray = [
  {
    id: 100,
    name: 'product_1',
    price: 99.95,
    color: ['#fff', '#f0f', '#00f'],
    category: ['cat_1', 'cat_2'],
    booleanKey: true,
    foo: {
      foo1: 12,
      foo2: 'string value',
      foo3: {
        foo3_1: 'string value',
        foo3_2: 732.342
      },
      foo4: [
        {
          foo4_1_1: 853,
          foo4_1_2: 24,
        },
        {
          foo4_2_1: 'string value',
          foo4_2_2: 435,
          foo4_2_3: 25.35,
        }
      ]
    },
    bar: {
      bar1: {
        bar1_1: 313,
        bar1_2: 32.73
      },
      bar2: 75.3
    }
  }
];

// The function to sort the array
const sortData = obj => {
  return dataArray.sort((a, b) => {
    // Do something to return the correct value
  });
}

// Sort array ascending by value 1, and descending by value -1
const result = sortData({foo: {foo3: {foo3_2: 1}}});
const anotherResult = sortData({bar: {bar2: -1}});
const anotherResult = sortData({price: 1});

Because I must write a function to sort any items with any structure, I have to use the obj they pass to me as the argument to sort the array base on that key of that object.

I found this solution so on:

// The sample array that be used from the other answer
const fooArray = [
  { foo1: { foo2: { foo3: 123 } } },
  { foo1: { foo2: { foo3: 987 } } },
  { foo1: { foo2: { foo3: 456 } } },
  { foo1: { foo2: { foo3: 789 } } },
  { foo1: { foo2: { foo3: 321 } } },
  { foo1: { foo2: { foo3: 654 } } }
];
const sortData = obj => {
  const pathArray = [];
  while(typeof(obj) == 'object') {
    pathArray.push(Object.keys(obj)[0]);
    obj = obj[pathArray[pathArray.length - 1]];
  }
  const order = obj;
  return fooArray.sort((a, b) => {
    for(let pathKey of pathArray) {
      a = a[pathKey];
      b = b[pathKey];
    }
    return (a - b) * order;
  });
}
const foo = sortData({ foo1: { foo2: { foo3: 1 } } });

Any other recommendation or idea?

2
  • It's like mongodb's sort implementation. The mongo's pipeline sort phase's argument is exactly your design. Commented Apr 30, 2019 at 4:05
  • Please share the expected output also Commented Apr 30, 2019 at 4:39

2 Answers 2

5

For the sorting logic, it's quite straightforward:

const sortData = path => {
  const sortOrder = query(path, path)

  return dataArray.sort((a, b) => {
    const valueOfA = query(a, path)
    const valueOfB = query(b, path)

    if (valueOfA < valueOfB) return -sortOrder
    if (valueOfA > valueOfB) return sortOrder
    return 0
  })
}

The only complicated thing here is how to query the sorting path and order from the other argument which is of object type.

So here I defined a helper function to do that job:

function query (target, path) {
  const [key] = Object.keys(path)

  if (typeof path[key] !== 'object') {
    return target[key]
  }

  return query(path[key], target)
}

You can test it here:

let dataArray = [{id: 100}, {id: 200}]

const sortData = path => {
  const sortOrder = query(path, path)

  return dataArray.sort((a, b) => {
    const valueOfA = query(a, path)
    const valueOfB = query(b, path)

    if (valueOfA < valueOfB) return -sortOrder
    if (valueOfA > valueOfB) return sortOrder
    return 0
  })
}

console.log(sortData({id: 1}))        // 100, 200
console.log(sortData({id: -1}))       // 200, 100

dataArray = [
  {foo: {bar: 'bar'}, a:'a', b:'b'},
  {foo: {bar: 'car'}, a:'A', b:'B'}
]

console.log(sortData({foo: {bar: 1}}))     // [...bar, ...car]
console.log(sortData({foo: {bar: -1}}))    // [...car, ...bar]

function query (target, path) {
  const [key] = Object.keys(path)

  if (typeof path[key] !== 'object') {
    return target[key]
  }

  return query(path[key], target)
}

Sign up to request clarification or add additional context in comments.

2 Comments

Yes please go ahead, I learned a lot from your edit, thanks! @PatrickRoberts
Good job. Your code is so similar to what I've done so on (but not completely). But I wonder that might be a more optimal and shorter solution or do some magic with es6 stuff :).
2

Using Sort array of objects by string property value as a reference implementation for sorting by an arbitrarily selected string or number of an object, we just need to implement the transformation from object to selector using the criteria() function below:

const criteria = (o, select = v => v) => {
  const [[key, order]] = Object.entries(o)
  const fn = v => select(v)[key]
  return typeof order === 'object'
    ? criteria(order, fn)
    : { order, fn }
}

const sortData = o => (({ order, fn }) =>
  (a, b) => {
    const fa = fn(a)
    const fb = fn(b)
    return order * (-(fa < fb) || +(fa > fb))
  }
)(criteria(o))

const foo = sortData({ foo1: { foo2: { foo3: 1 } } })
const fooArray = [
  { foo1: { foo2: { foo3: 123 } } },
  { foo1: { foo2: { foo3: 987 } } },
  { foo1: { foo2: { foo3: 456 } } },
  { foo1: { foo2: { foo3: 789 } } },
  { foo1: { foo2: { foo3: 321 } } },
  { foo1: { foo2: { foo3: 654 } } }
]

console.log(fooArray.sort(foo))

const bar = sortData({ bar1: { bar2: { bar3: -1 } } })
const barArray = [
  { bar1: { bar2: { bar3: 'abc' } } },
  { bar1: { bar2: { bar3: 'ihg' } } },
  { bar1: { bar2: { bar3: 'def' } } },
  { bar1: { bar2: { bar3: 'ghi' } } },
  { bar1: { bar2: { bar3: 'cba' } } },
  { bar1: { bar2: { bar3: 'fed' } } }
]

console.log(barArray.sort(bar))
.as-console-wrapper{min-height:100%!important}

2 Comments

Thanks bro. Your code is awesome but I think it's a little complicated to understand (maybe for me).
@Bawbak if you can point to specific parts you're having a hard time grasping I'd be happy to elaborate with a detailed explanation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.