JavaScript · 5장 자료구조와 자료형
객체를 배열로, 다시 객체로: keys · values · entries · fromEntries
배열에는 map · filter · reduce가 있지만 객체에는 없어요. 그래서 객체의 모든 값에 할인을 적용하거나, 빈 칸만 빼거나, 가장 비싼 항목을 찾으려면 먼저 객체를 배열로 바꿔야 해요. 바꾸고, 다루고, 다시 객체로 돌리는 한 바퀴를 정리해요.
01. 객체를 배열로: keys · values · entries
세 함수는 객체에서 각각 키 배열, 값 배열, [키, 값] 짝의 배열을 만들어요. 객체 자신의 속성만 담고, 심볼 키는 빠져요(심볼 키). 순서는 객체의 속성 순서 규칙 그대로예요. 정수 같은 키가 먼저 오름차순으로 오고, 나머지는 넣은 순서예요(속성 순서).
const prices = { 연필: 500, 공책: 2000, 지우개: 800 };
console.log(Object.keys(prices)); // ['연필', '공책', '지우개']
console.log(Object.values(prices)); // [500, 2000, 800]
console.log(Object.entries(prices)); // [['연필', 500], ['공책', 2000], ['지우개', 800]]
for (const [name, price] of Object.entries(prices)) {
console.log(`${name}: ${price}원`); // 짝을 구조 분해해서 바로 받아요
}
for (const [name, price] of …)는 짝 배열 ['연필', 500]을 두 변수로 나눠 받는 구조 분해예요(구조 분해에서 자세히). 객체를 돌 때 for...in보다 이 모양을 더 많이 써요. 상속받은 속성이 섞이지 않고, 키와 값을 한 번에 받기 때문이에요.
02. 배열이 되면 배열 메서드를 쓸 수 있어요
배열로 바꾸고 나면 앞 글들의 메서드가 다 열려요. 합계는 values + reduce, 조건에 맞는 키 고르기는 entries + filter + map, 가장 큰 값 찾기는 entries + 정렬이에요. 짝을 받는 함수에서는 ([name, price]) => …처럼 매개변수 자리에서 바로 나눠요.
const prices = { 연필: 500, 공책: 2000, 지우개: 800 };
const total = Object.values(prices).reduce((sum, p) => sum + p, 0);
console.log(total); // 3300
const over800 = Object.entries(prices)
.filter(([, price]) => price >= 800) // 키는 안 쓰니 비워 둬요
.map(([name]) => name);
console.log(over800); // ['공책', '지우개']
const [topName, topPrice] = Object.entries(prices).toSorted(([, a], [, b]) => b - a)[0];
console.log(topName, topPrice); // 공책 2000
console.log(Object.keys({}).length === 0); // true — 빈 객체인지 확인
마지막 줄은 실무에서 자주 쓰는 "객체가 비었나?" 검사예요. 객체는 if (obj)로 확인하면 빈 객체도 참이라서, 키 개수를 세요.
03. 다시 객체로: Object.fromEntries
Object.fromEntries(짝들)은 entries의 반대예요. [키, 값] 짝의 목록을 받아 객체를 만들어요. 그래서 entries → 배열 메서드 → fromEntries가 "객체에 map · filter 하기"의 기본 모양이 돼요.
const prices = { 연필: 500, 공책: 2000, 지우개: 800 };
const sale = Object.fromEntries(
Object.entries(prices).map(([name, price]) => [name, Math.round(price * 0.9)])
);
console.log(sale); // { 연필: 450, 공책: 1800, 지우개: 720 }
console.log(prices); // 원본은 그대로예요
fromEntries는 짝을 하나씩 꺼낼 수 있는 이터러블이면 무엇이든 받아요. Map이나 주소의 쿼리 문자열(URLSearchParams)을 보통 객체로 바꿀 때도 써요. 쿼리 값은 늘 문자열이라는 점만 기억해요.
console.log(Object.fromEntries(new Map([['a', 1], ['b', 2]]))); // { a: 1, b: 2 }
console.log(Object.fromEntries(new URLSearchParams('q=js&page=2'))); // { q: 'js', page: '2' }
회원 정보 수정 화면에서, 비워 둔 칸('')과 값이 없는 칸(null)은 빼고 서버로 보내려고 해요. 그런데 나이 0까지 빠졌어요(갓 태어난 아기 회원이라고 해요). 0은 남기도록 고쳐서 { name: '하늘', city: '서울', age: 0 }이 나오게 해 보세요.
const form = { name: '하늘', email: '', phone: null, city: '서울', age: 0 };
const payload = Object.fromEntries(
Object.entries(form).filter(([, value]) => value)
);
console.log(payload);
해답 보기
filter(([, value]) => value)는 "참 같은 값"만 남겨서, 거짓 같은 값인 0도 함께 빠졌어요(참 같은 값). 빼고 싶은 값을 정확히 적어요. value != null은 null과 undefined를 한 번에 걸러요.
const payload = Object.fromEntries(
Object.entries(form).filter(([, value]) => value !== '' && value != null)
);
// { name: '하늘', city: '서울', age: 0 }
04. 실전 포인트
- 언제 쓰나요? 객체를 돌 때는
Object.entries+for...of, 객체의 값 바꾸기 · 걸러 내기는 entries → map · filter → fromEntries, 빈 객체 확인은Object.keys(o).length === 0, Map · 쿼리를 객체로 바꿀 땐fromEntries를 써요. - 대신 쓰는 것: 키가 계속 늘고 줄어서 자주 돌고 세야 한다면 처음부터 Map(객체와 Map)이 나아요. 속성 하나만 바꿀 때는
{ ...obj, price: 900 }처럼 펼치기가 더 짧아요. - 흔한 실수: 객체에 바로
.map을 부르는 것, 참 · 거짓으로 걸러서0·false· 빈 문자열까지 빠지는 것, 숫자처럼 생긴 키의 순서가 바뀌는 걸 모르는 것, 쿼리 값을 숫자로 바꾸지 않고 계산하는 것이에요.
정리
Object.keys·values·entries는 객체를 키 · 값 · [키, 값] 짝 배열로 바꿔요.- 배열이 되면 map · filter · reduce · toSorted를 그대로 쓸 수 있어요.
Object.fromEntries는 짝 목록을 다시 객체로 묶어요. entries → 배열 메서드 → fromEntries가 기본 모양이에요.
Object.entries({ a: 1, b: 2 })의 결과는?
[['a', 1], ['b', 2]]예요. [키, 값] 짝을 항목으로 하는 배열이라 for (const [k, v] of …)로 바로 나눠 받을 수 있어요.
객체의 모든 값을 두 배로 만든 새 객체는 어떻게 만들까요?
Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, v * 2]))예요. 짝 배열로 바꿔 값만 바꾼 뒤 다시 객체로 묶어요. 원래 객체는 그대로예요.
빈 객체인지 확인할 때 if (!obj)를 쓰면 안 되는 이유는?
객체는 비어 있어도 참 같은 값이라 !{}는 false예요. Object.keys(obj).length === 0처럼 키 개수로 확인해요.