[JavaScript] Sort ข้อมูลใน Object ทำยังไง?

JavaScript Apr 5, 2023

ก่อนหน้านี้เราพูดถึง JavaScript sort() เบื้องต้นกันไปแล้ว วันนี้จะมาลองใช้งาน sort() ในระดับลึกขึ้นนั่นก็คือ การ sort ข้อมูลในรูปแบบที่เป็น Object ใน Array กันนะครับ

หลายๆ คนอาจจะเคยใช้งาน orderBy หรือ sortBy ของ Lodash หรือ Underscore มา แต่วันนี้มาลองทำ sortBy ของเราเองแบบไม่ต้องใช้ third party library

JavaScript sort ข้อมูลตัวเลข ไม่ถูกต้อง?
พอดีวันนี้ได้มีโอกาส นั่ง sort และ merge พวกข้อมูลจาก Array ก็เลยได้ไอเดีย นำมาเขียนเป็นบทความซะเลย เกี่ยวกับการ Sort ใน JavaScript ปกติเราสามารถเรียงข้อมูลใน Array ได้ง่ายๆ ด้วย sort() ใช่มั้ย? แต่บางครั้ง ก็มีปัญหา

สมมติ เรามีข้อมูลเริ่มต้น เป็น Array ที่มีข้อมูล Object ดังนี้

const items = [
  { name: "Edward", value: 21 },
  { name: "Sharpe", value: 37 },
  { name: "And", value: 45 },
  { name: "The", value: -12 },
  { name: "Magnetic", value: 13 },
  { name: "Zeros", value: 37 },
];

ถ้าเราอยาก sort จากข้อมูล Object ใน Array ทำได้ยังไง?

เรียงค่า value

ถ้าอยากให้เรียงจากค่า value ซึ่ง value เป็น number เราสามารถ sort ได้ง่ายๆ แบบนี้

items.sort((a, b) => a.value - b.value)

เรียงค่า name

ถ้าอยาก sort ค่า name ซึ่งเป็น string ทำได้โดย เริ่มจาก เราเช็คให้เป็น lowercase หรือ uppercase ก่อน จากนั้นก็เปรียบเทียบค่า ถ้า a น้อย กว่า b ก็ return -1 a มากกว่า return 1 เท่ากัน ก็ 0 แบบนี้

items.sort((a, b) => {
  const nameA = a.name.toUpperCase(); // ignore upper and lowercase
  const nameB = b.name.toUpperCase(); // ignore upper and lowercase
  
  if (nameA < nameB) {
    return -1;
  }
  if (nameA > nameB) {
    return 1;
  }

  // names must be equal
  return 0;
});

ลอง reuse function ให้รองรับการ sort แบบน้อยไปมาก หรือมากไปน้อยได้ โดยใช้ ASC หรือ DESC แบบนี้

const createCompareFn = (direction = "ASC") => {
  return (a, b) => {
    if (direction === "ASC") {
      return a.value - b.value;
    } else if (direction === "DESC") {
      return b.value - a.value;
    }
  }
}

ผมสร้าง function ที่รับ items และ direction จากนั้น ก็ return function ที่เป็น compareFn (มี argument คือ a, b) เวลาเราใช้งานก็เรียกแบบนี้

const sortByDesc = createCompareFn('DESC')
const sortBy = createCompareFn()

// เวชา sort ก็เรียก
items.sort(sortByDesc)
items.sort(sortBy)

ทีนี้ ข้อจำกัดของมัน คือ function compare เรา sort แค่ number ไม่ได้ sort string จะ sort string เราก็ต้องเปลี่ยน function อีกเป็นแบบนี้

const createCompareFn = (direction = 'ASC') => {
  return (a, b) => {
    const nameA = a.name.toUpperCase() // ignore upper and lowercase
    const nameB = b.name.toUpperCase() // ignore upper and lowercase
    if (nameA < nameB) {
      return -1
    }
    if (nameA > nameB) {
      return 1
    }

    return 0
  }
}

การใช้งาน ก็เหมือนเดิม

const sortByName = createCompareFn()

items.sort(sortByName)

ดูแล้ว ถ้าเราอยาก sort ค่าอื่นๆ เราก็ต้องมา สร้าง function compare ใหม่ ทุกครั้ง ก็รู้สึกว่ามันยังไม่ตอบโจทย์​ ทำไมเราไม่ทำ function ให้มันรับค่า key ที่เราต้องการซะเลย

สร้าง function ให้รับ key อะไรก็ได้

ก็คือ เราอยากให้มัน sort ด้วย key อะไรใน object ก็แค่ใส่ค่า key เข้าไป ข้างในผมก็ เช็คก่อนว่ามันเป็น string หรือ number แบบนี้ (logic compare ก็แบบเดียวกันกับก่อนหน้า)

const createCompareFn = (key, direction = 'ASC') => {
  const isString = typeof a[key] === 'string' && typeof b[key] === 'string'
  
  if (isString) {
    // logic compare string
  } else {
    // logic compare number
  }
  
}

ทีนี้ตรงเงื่อนไข sort น้อยไปมาก หรือมากไปน้อย ถ้าเรามาเช็ค แบบนี้ มันดูจะไม่สวย และซับซ้อนกับ check string

const nameA = a.name.toUpperCase() // ignore upper and lowercase
const nameB = b.name.toUpperCase() // ignore upper and lowercase
    
if (direction === 'ASC') {
  if (nameA < nameB) {
    return -1
  }
  if (nameA > nameB) {
    return 1
  }
  return 0
} else {
  if (nameA < nameB) {
    return 1
  }
  if (nameA > nameB) {
    return -1
  }
  return 0
}

เราใช้เป็น ค่าๆ นึงขึ้นมา เพื่อให้เป็น เครื่องหมายตรงข้าม นั่นก็คือ 1 และ -1 แบบนี้

const directionValue = direction === 'ASC' ? 1 : -1

ผลลัพธ์ function ที่ได้ ก็ได้แบบนี้

const createCompareFn = (key, direction = 'ASC') => {
  return (a, b) => {
    const isString = typeof a[key] === 'string' && typeof b[key] === 'string'
    const directionValue = direction === 'ASC' ? 1 : -1

    if (isString) {
      const A = a[key].toUpperCase()
      const B = b[key].toUpperCase()

      if (A < B) return -1 * directionValue
      if (A > B) return 1 * directionValue
      return 0
    } else {
      return (a[key] - b[key]) * directionValue
    }
  }
}

ตัวอย่าง ผมเพิ่ม parameter key เพื่อให้ user เลือกได้ว่าจะ sort ด้วย property อะไรของ Object จากนั้น ใน compareFn เราก็ เข้าถึงได้จาก a[key] เราจะรู้ว่าเป็น string หรือ number และก็ reuse function ก่อนหน้ามาใช้

ทีนี้ ส่วน directionValue ก็เป็นสิ่งที่เอาไว้บอก ถ้า asc ผมก็ให้มัน 1 และ desc = -1 เพื่อให้มันมีความหมายตรงกันข้ามกัน

ทีนี้ในการเช็ค compare number return 1, -1 และ 0 มันก็จะสลับกันเป็น -1, 1, 0 นั่นเอง

เวลาใช้งานก็แค่ง่ายๆ แบบนี้

const sortByName = createCompareFn('name')
const sortByNameDesc = createCompareFn('name', 'DESC')
const sortByValue = createCompareFn('value')

items.sort(sortByName)
items.sort(sortByNameDesc)

จบแล้ว หวังว่าจะเห็นแนวทาง และลองนำไปใช้กันดูนะครับ

ซึ่ง function ที่ผมยกตัวอย่างมาก็เป็นเพียงแค่ function ง่ายๆ ไม่ได้ซับซ้อน ถ้าเราจะใช้งานจริงๆ เราอาจจะต้องเช็คว่า property key มีค่ามั้ย และค่าเป็นอะไร เพื่อจะ compare ถูก หรือรองรับ nest object มั้ย? หรือค่า direction หรือ key เป็น case sensitive หรือ case insensitive

ลองไปฝึกทำ ฝึกเล่น และปรับแก้ พัฒนาเพิ่มกันดูนะครับ

หรือถ้าใครไม่ซีเรียสเรื่อง bundle file size ก็สามารถใช้ lodash, underscore orderBy , sortBy ก็สะดวกและใช้งานง่ายอยู่เหมือนกัน

Lodash Documentation

Happy Coding ❤️

References

Array.prototype.sort() - JavaScript | MDN
The sort() method sorts the elements of an array in place and returns the reference to the same array, now sorted. The default sort order is ascending, built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values.

Tags

Chai Phonbopit

เป็น Web Dev ทำงานมา 10 ปีหน่อยๆ ด้วยภาษา JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจ Web3, Crypto และ Blockchain เขียนบล็อกที่ https://devahoy.com