배열과 객체 다루기

2017년 03월 17일


객체 다루기 1

객체는 배열과 마찬가지로 자바스크립트에서 제공하는 기본 자료형중 하나입니다.

자바스크립트는 이 객체를 이용해 데이터를 표현하는 일이 많기 때문에 객체는 데이터 처리의 핵심이 됩니다.

순서쌍

객체는 원소가 key value 순서쌍인 특별한 형태의 배열로 생각할 수 있습니다.

객체의 모든 key나 value에 접근해서 바꿔야 하는 경우, 객체를 순서쌍의 배열로 간주하고 다루는 것이 편할때가 있습니다.

Ramda에서는 객체와 순서쌍의 배열을 넘나들 수 있는 함수를 제공합니다.

순서쌍과 배열에 관련된 함수만 잘 다뤄도 객체에 대한 함수 없이 객체를 다룰 수 있습니다.

toPairs, keys, values

R.toPairs를 통해 객체를 순서쌍으로 바꾸거나, R.keys, R.values를 통해 일부만 추출할 수 있습니다.

이 함수들은 각각 Object.entries, Object.keys, Object.values 함수에 대응합니다.

import * as R from 'ramda';

const object = { a: 10, b: 20 };
R.toPairs(object); // [['a', 10], ['b', 20]]

const deepObject = { a: { b: 20 }, c: { d: 40 } };
R.toPairs(deepObject); // [['a', { b: 20 }], ['c', { d: 40 }]]

const ks = R.keys(object);   // ['a', 'b']
const vs = R.values(object); // [10, 20]

fromPairs

R.toPairs와 반대의 동작을 합니다. 배열로 표현된 순서쌍을 객체의 형태로 바꿉니다.

import * as R from 'ramda';

const object = { a: 10, b: 20 };
const pairs  = R.toPairs(object); // [['a', 10], ['b', 20]]

R.fromPairs(pairs); // { a: 10, b: 20 };

순서쌍으로 객체를 해체한 뒤, fromPairs를 통해 다시 객체로 되돌릴때 주로 사용합니다.

key의 뒤에 merong이라는 문자열을 추가해보겠습니다.

import * as R from 'ramda';

const object = { a: 10, b: 20 };
const ks = R.keys(object);   // ['a', 'b']
const vs = R.values(object); // [10, 20]

const prefixer = (str) => `${str}Merong`;
const kks = R.map(prefixer, ks);

const newObject = R.fromPairs(R.zip(kks, vs)); // { aMerong: 10, bMerong: 20 };

읽기

객체를 읽는 것은 대부분 []를 사용합니다.

이번에는 좀 더 정밀하게 객체를 함수와, 읽는 동작을 함수로 만들어 조합하는 방법에 대해 알아봅니다.

prop

prop은 모든 읽기 동작의 기본이 되는 함수입니다. 단순히 []를 함수로 바꾼 것입니다.

import * as R from 'ramda';

const objectA = { a: 10, b: 20 };
R.prop('a', objectA); // 10
R.prop('b', objectA); // 20

const getA = R.prop('a');

const objectB = { a: 30, c: 10 };
const objectC = { a: 80, w: 90 };
const objects = [objectA, objectB, objectC];

R.map(getA, objects); // [10, 30, 80]

props, path

이 두 함수는 prop 함수와 비슷하지만 키의 배열을 받습니다.

props는 여러 개의 키를 받아 배열의 순서에 맞춰 추출하고, path는 중첩된 객체를 키 순서대로 읽습니다.

import * as R from 'ramda';

const wideObject = { a: 10, b: 20, c: 30, d: 40 };
R.props(['a', 'c'], wideObject); // [10, 30]

const deepObject = { a: { b: { c: { d: [10, 20, 30, 40] } } } };
R.path(['a', 'b', 'c', 'd', 3], deepObject); // 40

이 함수는 일정한 구조를 갖는 데이터를 반복적으로 다룰때 유용합니다.

import * as R from 'ramda';

const dataA = { props: { data: { users: [{ value: 0 }] } } };
const dataB = { props: { data: { users: [{ value: 1 }] } } };
const dataC = { props: { data: { users: [{ value: 2 }] } } };
const dataD = { props: { data: { users: [{ value: 3 }] } } };

const getValue = R.path(['props', 'data', 'users', 0, 'value']);

getValue(dataA); // 0
getValue(dataB); // 1
getValue(dataC); // 2
getValue(dataD); // 3

key 관련

이번에 소개할 함수는 객체의 key에 관련된 함수들입니다.

이 함수들은 key 인자로 사용해서 변경된 새로운 객체를 얻습니다.

pick, omit

pick은 키의 배열을 받아 추출하고, 반대로 omit은 제거합니다.

import * as R from 'ramda';

const object = { a: 0, b: 1, c : 2, d: 3 };
R.pick(['a', 'b'], object); // { a: 0, b: 1 }
R.omit(['a', 'b'], object); // { c: 2, d: 3 }

assoc, dissoc

assoc은 키와 값을 받아 객체에 추가 혹은 갱신하고, dissoc은 키를 받아 해당하는 필드를 객체에서 제거합니다.

dissoc은 omit과 비슷하지만 하나의 필드만을 제거할 수 있습니다.

import * as R from 'ramda';

const object = { a: 10, b: 20 };
R.assoc('c', 30, object); // { a: 10, b: 20, c: 30 };
R.assoc('b', 30, object); // { a: 10, b: 30 };
R.dissoc('b', object);    // { a: 10 };

assocPath, dissocPath

이 두 함수는 중첩된 객체에 대한 assoc과 dissoc입니다.

인자로 키를 받지 않고 키의 배열을 받아 동작합니다.

import * as R from 'ramda';

const foo = { a: 10, b: 20 };
const bar = R.assocPath(['c', 'd'], 30, foo); // { a: 10, b: 20, c: { d: 30 } }
R.dissoc('c', bar);            // { a: 10, b: 20 }
R.dissocPath(['c', 'd'], bar); // { a: 10, b: 20, c: { } }