[javascript] 점 표기법의 JavaScript 문자열을 객체 참조로 변환

JS 객체가 주어짐

var obj = { a: { b: '1', c: '2' } }

그리고 끈

"a.b"

문자열을 점 표기법으로 변환하여 어떻게 갈 수 있습니까?

var val = obj.a.b

문자열이 그대로 'a'라면 사용할 수 있습니다 obj[a]. 그러나 이것은 더 복잡합니다. 나는 간단한 방법이 있다고 생각하지만 현재 탈출하고 있습니다.



답변

최근 메모 : 이 답변이 많은 찬사를 받았다는 것에 대해 아첨하지만, 나는 약간 끔찍합니다. “xabc”와 같은 점 표기법 문자열을 참조로 변환해야하는 경우, 아마도 이상한 직렬화 해제를 수행하지 않는 한 매우 잘못된 일이 있다는 신호일 수 있습니다.

다시 말해,이 답변을 찾는 초보자는 “왜 내가 이것을 하는가?”라는 질문을 스스로에게해야합니다.

유스 케이스가 작고 성능 문제가 발생하지 않을 경우 일반적 으로이 작업을 수행하는 것이 좋습니다. 추상화를 통해 나중에 더 복잡하게 만들 필요가 없습니다. 실제로 이것이 코드 복잡성을 줄이고 일을 단순하게 유지한다면 OP가 요구하는 작업을 수행 해야 할 것입니다. 그러나 그렇지 않은 경우 다음 중 하나라도 해당되는지 고려하십시오.

사례 1 : 데이터 작업의 기본 방법 (예 : 객체를 전달하고 역 참조하는 앱의 기본 형식) “문자열에서 함수 또는 변수 이름을 어떻게 찾을 수 있습니까?”

  • 이것은 나쁜 프로그래밍 관행입니다 (특히 불필요한 메타 프로그래밍이 필요하며 기능에 영향을 미치지 않는 코딩 스타일을 위반하며 성능에 영향을 미칩니다). 이 경우에 자신을 찾는 초보자는 대신 배열 표현 (예 : [ ‘x’, ‘a’, ‘b’, ‘c’]) 또는 가능한 경우보다 직접 / 간단 / 직선적 인 작업을 고려해야합니다. 처음부터 참조 자체 추적 (클라이언트 측 또는 서버 측 인 경우 가장 이상적임) 등 (기존의 고유 ID는 추가하기에 부적합하지만 사양에 다른 방식으로 요구되는 경우 사용할 수 있음) 존재하지 않습니다.)

사례 2 : 직렬화 된 데이터 작업 또는 사용자에게 표시 될 데이터 Date 개체 대신 날짜를 문자열 “1999-12-30″으로 사용하는 것과 같습니다 (주의하지 않으면 시간대 버그가 발생하거나 직렬화 복잡성이 추가 될 수 있음). 또는 당신은 무엇을하고 있는지 알고 있습니다.

  • 아마도 괜찮을 것입니다. 점 문자열 “.”이 없는지주의하십시오. 위생 처리 된 입력 조각에.

이 답변을 항상 사용하고 문자열과 배열간에 앞뒤로 변환하는 것을 발견하면 나쁜 경우 일 수 있으며 대안을 고려해야합니다.

다음은 다른 솔루션보다 10 배 더 짧은 우아한 하나의 라이너입니다.

function index(obj,i) {return obj[i]}
'a.b.etc'.split('.').reduce(index, obj)

[편집] 또는 ECMAScript 6에서 :

'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)

(다른 사람들이 제안하는 것처럼 eval이 항상 나쁘다고 생각하지는 않지만 (보통은 아니지만)이 사람들은이 방법이 eval을 사용하지 않는다는 것을 기쁘게 생각합니다. 위와 obj.a.b.etc주어진 obj문자열을 찾을 수 "a.b.etc"있습니다.)

reduceECMA-262 표준 (5 판)에 있음에도 불구하고 여전히 사용을 두려워하는 사람들을 위해 다음과 같이 2 줄 재귀 구현이 있습니다.

function multiIndex(obj,is) {  // obj,['1','2','3'] -> ((obj['1'])['2'])['3']
    return is.length ? multiIndex(obj[is[0]],is.slice(1)) : obj
}
function pathIndex(obj,is) {   // obj,'1.2.3' -> multiIndex(obj,['1','2','3'])
    return multiIndex(obj,is.split('.'))
}
pathIndex('a.b.etc')

JS 컴파일러가 수행하는 최적화에 따라 중첩 된 함수가 일반적인 메소드 (클로저, 오브젝트 또는 글로벌 네임 스페이스에 배치)를 통해 모든 호출에서 재정의되지 않도록 할 수 있습니다.

편집 :

주석에서 흥미로운 질문에 대답하려면 :

이것을 어떻게 세터로 바꾸겠습니까? 경로로 값을 반환 할뿐만 아니라 새로운 값이 함수에 전송되면 설정합니까? – Swader 6 월 28 일 21:42

((!) 참고 : 그가 호출 규칙을 위반하는 것처럼 슬프게도, 세터와 객체를 반환 할 수없는, 주석 대신 같은 부작용이있는 일반 세터 스타일의 기능을 참조 할 것 index(obj,"a.b.etc", value)하고 obj.a.b.etc = value.)

reduce스타일은 정말 적합하지 않다, 그러나 우리는 재귀 구현을 수정할 수 있습니다 :

function index(obj,is, value) {
    if (typeof is == 'string')
        return index(obj,is.split('.'), value);
    else if (is.length==1 && value!==undefined)
        return obj[is[0]] = value;
    else if (is.length==0)
        return obj;
    else
        return index(obj[is[0]],is.slice(1), value);
}

데모:

> obj = {a:{b:{etc:5}}}

> index(obj,'a.b.etc')
5
> index(obj,['a','b','etc'])   #works with both strings and lists
5

> index(obj,'a.b.etc', 123)    #setter-mode - third argument (possibly poor form)
123

> index(obj,'a.b.etc')
123

… 개인적으로는 별도의 기능을 만드는 것이 좋습니다 setIndex(...). 나는 질문의 원래 포즈 .split가 문자열이 아닌 인덱스 배열 (에서 얻을 수 있음 ) 을 사용할 수 있어야한다는 쪽지를 끝내고 싶습니다 . 편의 기능에는 아무런 문제가 없습니다.


논평자가 물었다 :

배열은 어떻습니까? “ab [4] .cd [1] [2] [3]”와 같은 것? – 알렉스

자바 스크립트는 매우 이상한 언어입니다. 일반적으로 객체의 경우에만 그렇게 예를 들어, 자신의 재산의 키와 같은 문자열을 가질 수 x일반 객체 같았다 x={}다음 x[1]이 될 것입니다 x["1"]당신이 바로 … 그래 읽기 …

Javascript Arrays (자체 Object의 인스턴스)는와 같은 것을 할 수 있지만 특히 정수 키를 권장합니다 x=[]; x["puppy"]=5;.

그러나 일반적으로 (그리고 예외가 있습니다) x["somestring"]===x.somestring(허용되면; 할 수 없습니다 x.123).

(사용중인 JS 컴파일러가 사양을 위반하지 않는다는 것을 증명할 수있는 경우 더 세밀한 표현으로 컴파일 할 수 있음을 명심하십시오.)

따라서 귀하의 질문에 대한 답변은 해당 객체가 정수만을 허용한다고 가정하는지 (문제 도메인의 제한으로 인해) 여부에 달려 있습니다. 가정하지 말자. 이어서 올바른 표현은 기지국 식별자 더하기 일부를 연결 한 .identifierS 플러스 일부 ["stringindex"]

a["b"][4]["c"]["d"][1][2][3]우리는 아마 지원해야하지만 이것 과 같습니다 a.b["c\"validjsstringliteral"][3]. 유효한 문자열 리터럴을 구문 분석하는 방법을 보려면 문자열 리터럴 에서 ecmascript 문법 섹션 을 확인해야합니다 . 기술적으로 당신 a은 유효한 자바 스크립트 식별자 인 ( 첫 번째 답변과 달리) 확인하고 싶습니다 .

귀하의 질문에 대한 대답은 간단하지만, 당신의 문자열 쉼표 나 괄호를 포함하지 않는 경우 , 그냥 세트의 문자 길이 1+ 시퀀스와 일치하는 것 ,또는 []:

> "abc[4].c.def[1][2][\"gh\"]".match(/[^\]\[.]+/g)
// ^^^ ^  ^ ^^^ ^  ^   ^^^^^
["abc", "4", "c", "def", "1", "2", ""gh""]

문자열에 이스케이프 문자 또는 "문자가 포함되어 있지 않고 IdentifierNames가 StringLiterals의 하위 언어이기 때문에 점을 []로 변환 할 수 있습니다.

> var R=[], demoString="abc[4].c.def[1][2][\"gh\"]";
> for(var match,matcher=/^([^\.\[]+)|\.([^\.\[]+)|\["([^"]+)"\]|\[(\d+)\]/g; 
      match=matcher.exec(demoString); ) {
  R.push(Array.from(match).slice(1).filter(x=>x!==undefined)[0]);
  // extremely bad code because js regexes are weird, don't use this
}
> R

["abc", "4", "c", "def", "1", "2", "gh"]

물론 항상 조심하고 데이터를 신뢰하지 마십시오. 일부 사용 사례에서 작동 할 수있는 몇 가지 나쁜 방법은 다음과 같습니다.

// hackish/wrongish; preprocess your string into "a.b.4.c.d.1.2.3", e.g.: 
> yourstring.replace(/]/g,"").replace(/\[/g,".").split(".")
"a.b.4.c.d.1.2.3"  //use code from before

스페셜 2018 편집 :

하자의 전체-원을 가서 우리가 가지고 올 수있는 가장 비효율적 인, 끔찍-overmetaprogrammed 솔루션 … 구문의 이익을 할 순도 hamfistery을. ES6 Proxy 객체를 사용하여! … 또한 잘 작성된 라이브러리를 손상시킬 수있는 일부 속성 (정상적이고 훌륭하지만)을 정의 해 봅시다. 성능, 온전함 (자신 또는 다른 사람), 직업 등에 관심이있는 경우이를 사용하는 것에주의해야합니다.

// [1,2,3][-1]==3 (or just use .slice(-1)[0])
if (![1][-1])
    Object.defineProperty(Array.prototype, -1, {get() {return this[this.length-1]}}); //credit to caub

// WARNING: THIS XTREME™ RADICAL METHOD IS VERY INEFFICIENT,
// ESPECIALLY IF INDEXING INTO MULTIPLE OBJECTS,
// because you are constantly creating wrapper objects on-the-fly and,
// even worse, going through Proxy i.e. runtime ~reflection, which prevents
// compiler optimization

// Proxy handler to override obj[*]/obj.* and obj[*]=...
var hyperIndexProxyHandler = {
    get: function(obj,key, proxy) {
        return key.split('.').reduce((o,i)=>o[i], obj);
    },
    set: function(obj,key,value, proxy) {
        var keys = key.split('.');
        var beforeLast = keys.slice(0,-1).reduce((o,i)=>o[i], obj);
        beforeLast[keys[-1]] = value;
    },
    has: function(obj,key) {
        //etc
    }
};
function hyperIndexOf(target) {
    return new Proxy(target, hyperIndexProxyHandler);
}

데모:

var obj = {a:{b:{c:1, d:2}}};
console.log("obj is:", JSON.stringify(obj));

var objHyper = hyperIndexOf(obj);
console.log("(proxy override get) objHyper['a.b.c'] is:", objHyper['a.b.c']);
objHyper['a.b.c'] = 3;
console.log("(proxy override set) objHyper['a.b.c']=3, now obj is:", JSON.stringify(obj));

console.log("(behind the scenes) objHyper is:", objHyper);

if (!({}).H)
    Object.defineProperties(Object.prototype, {
        H: {
            get: function() {
                return hyperIndexOf(this); // TODO:cache as a non-enumerable property for efficiency?
            }
        }
    });

console.log("(shortcut) obj.H['a.b.c']=4");
obj.H['a.b.c'] = 4;
console.log("(shortcut) obj.H['a.b.c'] is obj['a']['b']['c'] is", obj.H['a.b.c']);

산출:

obj는 { “a”: { “b”: { “c”: 1, “d”: 2}}}입니다.

(프록시 재정의 get) objHyper [ ‘abc’]는 다음과 같습니다. 1

(프록시 재정의 세트) objHyper [ ‘abc’] = 3, 이제 obj는 { “a”: { “b”: { “c”: 3, “d”: 2}}}입니다.

(장면 뒤) objHyper는 다음과 같습니다. 프록시 {a : {…}}

(바로 가기) obj.H [ ‘abc’] = 4

(바로 가기) obj.H [ ‘abc’]는 obj [ ‘a’] [ ‘b’] [ ‘c’]는 다음과 같습니다. 4

비효율적 인 아이디어 : 입력 인수를 기반으로 디스패치를 ​​위의 내용을 수정할 수 있습니다. .match(/[^\]\[.]+/g)지원 하는 메소드를 사용 obj['keys'].like[3]['this']하거나 if instanceof Array인 경우 와 같이 Array를 입력으로 승인하십시오 keys = ['a','b','c']; obj.H[keys].


정의되지 않은 인덱스를 ‘더 부드러운’NaN 스타일 방식으로 처리하려는 경우 (예 : index({a:{b:{c:...}}}, 'a.x.c')잡히지 않은 TypeError가 아닌 undefined를 반환) … :

1) 이는 1 차원 인덱스 상황에서 “오류를 던지기보다는 정의되지 않은 상태로 반환해야합니다”라는 관점에서 의미가 있습니다. { ‘eg’] == undefined이므로 ” N 차원 상황에서 오류입니다.

2) 이것은 우리가하고있는 관점에서 의미 가 없으므로x['a']['x']['c'] 위의 예제에서 TypeError로 실패합니다.

즉, 축소 기능을 다음 중 하나로 대체 하여이 작업을 수행 할 수 있습니다.

(o,i)=>o===undefined?undefined:o[i]또는
(o,i)=>(o||{})[i].

(다음에 색인 할 하위 결과가 정의되지 않을 때마다 for 루프를 사용하고 중단 / 복귀를 사용하거나 이러한 실패가 충분히 드문 것으로 예상되는 경우 try-catch를 사용하여이를보다 효율적으로 만들 수 있습니다.)


답변

lodash 를 사용할 수 있다면 정확히 기능하는 함수가 있습니다.

_.get (객체, 경로, [defaultValue])

var val = _.get(obj, "a.b");


답변

재귀와 관련된 좀 더 복잡한 예.

function recompose(obj,string){
    var parts = string.split('.');
    var newObj = obj[parts[0]];
    if(parts[1]){
        parts.splice(0,1);
        var newString = parts.join('.');
        return recompose(newObj,newString);
    }
    return newObj;
}


var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};

alert(recompose(obj,'a.d.a.b')); //blah


답변

lodash.get 을 사용할 수도 있습니다 .

이 패키지 (npm i –save lodash.get)를 설치 한 후 다음과 같이 사용하면됩니다.

const get = require('lodash.get');

const myObj = { user: { firstName: 'Stacky', lastName: 'Overflowy' }, id: 123 };

console.log(get(myObj, 'user.firstName')); // prints Stacky
console.log(get(myObj, 'id')); //prints  123

//You can also update values
get(myObj, 'user').firstName = John;


답변

동일한 경로를 여러 번 역 참조 할 경우 각 점 표기법 경로에 대한 함수를 작성하면 실제로 최고의 성능을 발휘합니다 (위의 설명에서 James Wilkins와 연결된 성능 테스트를 확장).

var path = 'a.b.x';
var getter = new Function("obj", "return obj." + path + ";");
getter(obj);

Function 생성자를 사용하면 보안 및 최악의 성능 측면에서 eval ()과 동일한 단점이 있지만 IMO는 극도의 역동 성과 고성능의 조합이 필요한 경우에 잘 사용되지 않는 도구입니다. 이 방법을 사용하여 배열 필터 함수를 작성하고 AngularJS 다이제스트 루프 내에서 호출합니다. 내 프로파일은 3-4 레벨 깊이의 동적으로 정의 된 경로를 사용하여 약 2000 개의 복잡한 객체를 역 참조하고 필터링하는 데 1ms 미만의 array.filter () 단계를 일관되게 보여줍니다.

물론 setter 함수를 생성하기 위해 비슷한 방법론을 사용할 수 있습니다.

var setter = new Function("obj", "newval", "obj." + path + " = newval;");
setter(obj, "some new val");


답변

원래 게시물 이후 몇 년. 이제 ‘object-path’라는 훌륭한 라이브러리가 있습니다.
https://github.com/mariocasciaro/object-path

NPM 및 BOWER https://www.npmjs.com/package/object-path 에서 사용 가능

다음과 같이 쉽습니다.

objectPath.get(obj, "a.c.1");  //returns "f"
objectPath.set(obj, "a.j.0.f", "m");

깊이 중첩 된 속성 및 배열에 적용됩니다.


답변

경로를 분할하고 반복하고 가지고있는 객체를 줄이는 것이 좋습니다. 이 제안은 누락 된 속성에 대한 기본값으로 작동 합니다.

const getValue = (object, keys) => keys.split('.').reduce((o, k) => (o || {})[k], object);

console.log(getValue({ a: { b: '1', c: '2' } }, 'a.b'));
console.log(getValue({ a: { b: '1', c: '2' } }, 'foo.bar.baz'));