Computer >> 컴퓨터 >  >> 프로그래밍 >> JavaScript

점 표기법으로 평탄화된 JavaScript 객체를 중첩 객체와 배열 구조로 되돌리는 방법

API 응답이나 설정 파일을 다루다 보면 car.make, car.revisions.0.miles처럼 점(.) 표기법으로 평탄화된 키를 가진 객체를 만나게 됩니다. 이 글에서는 이렇게 평탄화된 객체를 원래의 중첩 객체와 배열 구조로 되돌리는(unflatten) 방법을 알아보겠습니다.

문제 상황

다음과 같이 모든 키가 점 표기법으로 나열된 객체가 있다고 가정해 보겠습니다.

const obj = {
    "firstName": "John",
    "lastName": "Green",
    "car.make": "Honda",
    "car.model": "Civic",
    "car.revisions.0.miles": 10150,
    "car.revisions.0.code": "REV01",
    "car.revisions.0.changes": "",
    "car.revisions.1.miles": 20021,
    "car.revisions.1.code": "REV02",
    "car.revisions.1.changes.0.type": "asthetic",
    "car.revisions.1.changes.0.desc": "Left tire cap",
    "car.revisions.1.changes.1.type": "mechanic",
    "car.revisions.1.changes.1.desc": "Engine pressure regulator",
    "visits.0.date": "2015-01-01",
    "visits.0.dealer": "DEAL-001",
    "visits.1.date": "2015-03-01",
    "visits.1.dealer": "DEAL-002"
};

이렇게 평탄화된 구조는 사람이 읽기 어렵고 데이터를 조작하기도 번거롭습니다. 따라서 이러한 객체를 입력받아 원래대로 중첩된 객체와 배열 구조로 되돌려 주는 JavaScript 함수를 작성해야 합니다.

즉, 위 객체를 변환한 최종 결과는 다음과 같은 형태가 되어야 합니다.

const output = {
    firstName: 'John',
    lastName: 'Green',
    car: {
        make: 'Honda',
        model: 'Civic',
        revisions: [
            { miles: 10150, code: 'REV01', changes: '' },
            {
                miles: 20021,
                code: 'REV02',
                changes: [
                    { type: 'asthetic', desc: 'Left tire cap' },
                    { type: 'mechanic', desc: 'Engine pressure regulator' }
                ]
            }
        ]
    },
    visits: [
        { date: '2015-01-01', dealer: 'DEAL-001' },
        { date: '2015-03-01', dealer: 'DEAL-002' }
    ]
};

구현 방법

핵심 아이디어는 각 키를 점(.)을 기준으로 분리한 뒤, 분리된 경로를 따라가면서 결과 객체에 중첩 구조를 하나씩 만들어 주는 것입니다. 이때 다음 세그먼트가 숫자라면 그 자리는 배열로, 그렇지 않다면 객체로 생성하면 됩니다.

const obj = {
    "firstName": "John",
    "lastName": "Green",
    "car.make": "Honda",
    "car.model": "Civic",
    "car.revisions.0.miles": 10150,
    "car.revisions.0.code": "REV01",
    "car.revisions.0.changes": "",
    "car.revisions.1.miles": 20021,
    "car.revisions.1.code": "REV02",
    "car.revisions.1.changes.0.type": "asthetic",
    "car.revisions.1.changes.0.desc": "Left tire cap",
    "car.revisions.1.changes.1.type": "mechanic",
    "car.revisions.1.changes.1.desc": "Engine pressure regulator",
    "visits.0.date": "2015-01-01",
    "visits.0.dealer": "DEAL-001",
    "visits.1.date": "2015-03-01",
    "visits.1.dealer": "DEAL-002"
};

const unflatten = (obj = {}) => {
    const result = {};
    let temp, substrings, property, i;
    for (property in obj) {
        // 키를 점(.) 기준으로 분리
        substrings = property.split('.');
        temp = result;
        // 마지막 세그먼트 직전까지 경로를 따라가며 중첩 구조 생성
        for (i = 0; i < substrings.length - 1; i++) {
            if (!(substrings[i] in temp)) {
                if (isFinite(substrings[i + 1])) {
                    // 다음 세그먼트가 숫자면 배열 생성
                    temp[substrings[i]] = [];
                } else {
                    // 그렇지 않으면 객체 생성
                    temp[substrings[i]] = {};
                }
            }
            temp = temp[substrings[i]];
        }
        // 마지막 세그먼트에 실제 값 할당
        temp[substrings[substrings.length - 1]] = obj[property];
    }
    return result;
};

console.log(JSON.stringify(unflatten(obj), undefined, 4));

동작 원리

  • 키 분리: split('.')을 사용해 "car.revisions.0.miles"와 같은 키를 ["car", "revisions", "0", "miles"] 배열로 분리합니다.
  • 경로 탐색 및 생성: 마지막 요소를 제외한 각 세그먼트를 순회하며 해당 경로가 아직 없으면 새로 만듭니다. 이때 바로 다음 세그먼트가 유한한 숫자(isFinite)라면 배열을, 아니면 객체를 생성합니다.
  • 값 할당: 마지막 세그먼트 위치에 원본 객체의 값을 대입합니다.

출력 결과

위 코드를 실행하면 콘솔에 다음과 같은 결과가 출력됩니다.

{
    "firstName": "John",
    "lastName": "Green",
    "car": {
        "make": "Honda",
        "model": "Civic",
        "revisions": [
            {
                "miles": 10150,
                "code": "REV01",
                "changes": ""
            },
            {
                "miles": 20021,
                "code": "REV02",
                "changes": [
                    {
                        "type": "asthetic",
                        "desc": "Left tire cap"
                    },
                    {
                        "type": "mechanic",
                        "desc": "Engine pressure regulator"
                    }
                ]
            }
        ]
    },
    "visits": [
        {
            "date": "2015-01-01",
            "dealer": "DEAL-001"
        },
        {
            "date": "2015-03-01",
            "dealer": "DEAL-002"
        }
    ]
}

이처럼 간단한 반복문과 isFinite() 검사만으로도 점 표기법으로 평탄화된 객체를 손쉽게 원래의 중첩 구조로 복원할 수 있습니다. 폼 데이터 처리, 쿼리 스트링 파싱, API 응답 변환 등 다양한 상황에서 유용하게 활용할 수 있습니다.