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


214

JS 객체가 주어짐

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

그리고 끈

"a.b"

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

var val = obj.a.b

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


25
@Andrey eval는 악하다. 사용하지 마십시오
kevinji 2016 년

5
참고 : 방금 수행 한 흥미로운 속도 테스트는 다음과 같습니다. jsperf.com/dereference-object-property-path-from-string
James Wilkins

perf를 진지하게 고려하고 동일한 경로를 많이 재사용하는 경우 (예 : 배열 필터 함수 내부) 아래 답변에 설명 된대로 함수 생성자를 사용하십시오. 동일한 경로가 수천 번 사용되면 Function 메소드는 모든 역 참조에서 경로를 회피 또는 분할하고 줄이는 것보다 10 배 이상 빠를 수 있습니다.
Kevin Crumley


답변:


437

최근 메모 : 이 답변이 많은 찬사를 받았다는 것에 대해 아첨하지만, 나는 약간 끔찍합니다. "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를 사용하여이를보다 효율적으로 만들 수 있습니다.)


4
reduce현재 사용중인 모든 브라우저에서 지원되는 것은 아닙니다.
Ricardo Tomasi

14
@Ricardo : Array.reduceECMA-262 표준의 일부입니다. 오래된 브라우저를 실제로 지원하려면 Array.prototype.reduce어딘가에 주어진 샘플 구현을 정의 할 수 있습니다 (예 : developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… ).
ninjagecko

2
예. 그러나 두 줄을 기능에 넣는 것은 쉽습니다. var setget = function( obj, path ){ function index( robj,i ) {return robj[i]}; return path.split('.').reduce( index, obj ); }
nevf

3
나는 닌자 게코 덕분 에이 우아한 예를 좋아합니다. 배열 스타일 표기법과 빈 문자열을 처리하도록 확장했습니다. 여기의 예제를 참조하십시오 : jsfiddle.net/sc0ttyd/q7zyd
Sc0ttyD

2
@ninjagecko 어떻게 이것을 세터로 바꾸겠습니까? 경로로 값을 반환 할뿐만 아니라 새로운 값이 함수에 전송되면 설정합니까?
Swader

64

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

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

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

4
참고 : _.get(object, path)경로를 찾지 못하면 깨지지 않습니다. 'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)그렇습니다. 모든 경우가 아니라 내 특정 사례에 대해 정확히 내가 필요한 것. 감사!
Mr. B.

1
@ Mr.B. Lodash의 최신 버전에는에 대한 세 번째 선택적인 인수가 defaultValue있습니다. _.get()메소드가 리턴하는 경우 기본값 _.get()결의로는 undefined당신이 원하는 당신이 설정 한 값에 대한 시계 무엇에 그렇게 설정합니다.
steampowered

7
궁금한 사람은 누구나 지원합니다 _.set(object, path, value).
Jeffrey Roosendaal

19

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

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

19

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;

10

동일한 경로를 여러 번 역 참조 할 경우 각 점 표기법 경로에 대한 함수를 작성하면 실제로 최고의 성능을 발휘합니다 (위의 설명에서 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");

1
오랫동안 동일한 경로를 역 참조해야하는 경우 위의 jsperf.com 링크는 나중에 함수를 저장하고 조회하는 방법의 예를 보여줍니다. Function 생성자를 호출하는 작업은 상당히 느리므로 높은 성능의 코드는 가능한 경우 반복하지 않도록 결과를 기억해야합니다.
Kevin Crumley


7

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

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'));


6

Lodash 를 이미 사용 중인 경우 property또는 get기능을 사용할 수 있습니다 .

var obj = { a: { b: '1', c: '2' } };
_.property('a.b')(obj); // => 1
_.get(obj, 'a.b'); // => 1

밑줄도 property기능이 있지만 점 표기법을 지원하지 않습니다.


1
Underscore는 점 표기법을 지원하지 않는 것 같습니다. 답변이 업데이트되었습니다.
Tamlyn

5

다른 제안은 약간 비밀 스럽기 때문에 내가 기여할 것이라고 생각했습니다.

Object.prop = function(obj, prop, val){
    var props = prop.split('.')
      , final = props.pop(), p 
    while(p = props.shift()){
        if (typeof obj[p] === 'undefined')
            return undefined;
        obj = obj[p]
    }
    return val ? (obj[final] = val) : obj[final]
}

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

// get
console.log(Object.prop(obj, 'a.c')) // -> 2
// set
Object.prop(obj, 'a.c', function(){})
console.log(obj) // -> { a: { b: '1', c: [Function] } }

5

npm에 사용 가능한 라이브러리를 사용하면이 프로세스가 간단 해집니다. https://www.npmjs.com/package/dot-object

 var dot = require('dot-object');

var obj = {
 some: {
   nested: {
     value: 'Hi there!'
   }
 }
};

var val = dot.pick('some.nested.value', obj);
console.log(val);

// Result: Hi there!

1
관심을 가져 주셔서 감사합니다. 매우 유용합니다.
nevf

그리고 이것이 모든 프로젝트가 10000 개의 의존성을 주입하는 이유입니다 :-).
마빈

4
var a = { b: { c: 9 } };

function value(layer, path, value) {
    var i = 0,
        path = path.split('.');

    for (; i < path.length; i++)
        if (value != null && i + 1 === path.length)
            layer[path[i]] = value;
        layer = layer[path[i]];

    return layer;
};

value(a, 'b.c'); // 9

value(a, 'b.c', 4);

value(a, 'b.c'); // 4

이것은 훨씬 간단한 eval방법 과 비교할 때 많은 코드 이지만 Simon Willison이 말했듯이 eval을 사용해서는 안됩니다 .

또한 JSFiddle 입니다.


대단한 일이며 CD 산체보다 짧습니다. 감사.
nevf

값 (A, 'BBBBB')는 정의되지 않은 반환하지 않습니다
frumbert

4

함수가 점 및 / 또는 배열 스타일 참조를 처리하고 빈 문자열로 인해 부모 객체가 반환되도록 ninjagecko의 우아한 답변을 확장했습니다.

여기 있습니다 :

string_to_ref = function (object, reference) {
    function arr_deref(o, ref, i) { return !ref ? o : (o[ref.slice(0, i ? -1 : ref.length)]) }
    function dot_deref(o, ref) { return ref.split('[').reduce(arr_deref, o); }
    return !reference ? object : reference.split('.').reduce(dot_deref, object);
};

여기에서 작동하는 jsFiddle 예제를 참조하십시오 : http://jsfiddle.net/sc0ttyd/q7zyd/


정말 좋은 해결책입니다. 문제는 하나 뿐이며 []표기법은 항상 배열에 대한 것으로 가정 합니다. 객체 키뿐만 아니라 예를가 그런 식으로 표현이있을 수 obj['some-problem/name'].list[1]이 나는 업데이트로했다 해결하기 위해 arr_deref이 같은 기능 javascript function arr_deref(o, ref, i) { return !ref ? o : (o[(ref.slice(0, i ? -1 : ref.length)).replace(/^['"]|['"]$/g, '')]); }
세르칸 Yersen

1
요즘에는 이렇게하지 않을 것입니다. 나는 Lodash 사용하십시오 : lodash.com/docs#get을
Sc0ttyD

2
var find = function(root, path) {
  var segments = path.split('.'),
      cursor = root,
      target;

  for (var i = 0; i < segments.length; ++i) {
   target = cursor[segments[i]];
   if (typeof target == "undefined") return void 0;
   cursor = target;
  }

  return cursor;
};

var obj = { a: { b: '1', c: '2' } }
find(obj, "a.b"); // 1

var set = function (root, path, value) {
   var segments = path.split('.'),
       cursor = root,
       target;

   for (var i = 0; i < segments.length - 1; ++i) {
      cursor = cursor[segments[i]] || { };
   }

   cursor[segments[segments.length - 1]] = value;
};

set(obj, "a.k", function () { console.log("hello world"); });

find(obj, "a.k")(); // hello world

모든 빠른 답변에 감사드립니다. eval () 솔루션을 좋아하지 않습니다. 이것과 비슷한 게시물이 가장 좋습니다. 그러나 여전히 문제가 있습니다. obj.ab = new value 값을 설정하려고합니다. 정확한 b 값은 함수이므로 obj.ab (new_value)를 사용해야합니다. 함수가 호출되었지만 값이 설정되지 않았습니다. 나는 그것이 범위 문제라고 생각하지만 여전히 파고 있습니다. 나는 이것이 원래 질문의 범위를 벗어난 것을 알고 있습니다. 내 코드는 Knockout.js를 사용하고 있으며 b는 ko.observable입니다.
nevf

@ nevf : 원하는 기능을 수행하는 두 번째 기능을 추가했습니다. 원하는 동작에 따라 원하는대로 사용자 정의 할 수 있습니다 (예 : 개체가없는 경우 개체를 만들어야합니까? 등).
Cristian Sanchez

@nevf 그러나 내 기능은 하나의 기능으로 수행됩니다. ; D
McKayla 2016 년

사용할 수있는 업데이트에 감사드립니다. @tylermwashburn-짧은 구현에 감사드립니다. 모두와 함께하십시오.
nevf

2

한 줄의 코드로 점 표기법으로 객체 멤버의 값을 얻을 수 있습니다.

new Function('_', 'return _.' + path)(obj);

당신의 경우 :

var obj = { a: { b: '1', c: '2' } }
var val = new Function('_', 'return _.a.b')(obj);

간단하게하기 위해 다음과 같은 함수를 작성할 수 있습니다.

function objGet(obj, path){
    return new Function('_', 'return _.' + path)(obj);
}

설명:

Function 생성자는 새 Function 객체를 만듭니다. JavaScript에서 모든 함수는 실제로 함수 객체입니다. 함수 생성자를 사용하여 명시 적으로 함수를 작성하는 구문은 다음과 같습니다.

new Function ([arg1[, arg2[, ...argN]],] functionBody)

여기서 arguments(arg1 to argN)유효한 javaScript 식별자에 해당 functionBody하고 함수 정의를 구성하는 javaScript 문이 포함 된 문자열이어야합니다 .

이 경우 문자열 함수 본문을 활용하여 점 표기법으로 객체 멤버를 검색합니다.

도움이 되길 바랍니다.


이것이 무엇을하고 있는지 정확하게 설명 할 수 있습니까? 그리고 어떻게 'ab'등을 함수 매개 변수로 전달할 것입니까?
nevf

1
나는 이것을 +1로 주었지만 JSLint는 "함수 생성자는 eval의 한 형태"라고 경고한다 .
gabe

2

반응 네이티브 에서도 작동하는 GET / SET 답변 ( Object.prototype현재 할당 할 수 없음 ) :

Object.defineProperty(Object.prototype, 'getNestedProp', {
    value: function(desc) {
        var obj = this;
        var arr = desc.split(".");
        while(arr.length && (obj = obj[arr.shift()]));
        return obj;
    },
    enumerable: false
});

Object.defineProperty(Object.prototype, 'setNestedProp', {
    value: function(desc, value) {
        var obj = this;
        var arr = desc.split(".");
        var last = arr.pop();
        while(arr.length && (obj = obj[arr.shift()]));
        obj[last] = value;
    },
    enumerable: false
});

용법:

var a = { values: [{ value: null }] };
var b = { one: { two: 'foo' } };

a.setNestedProp('values.0.value', b.getNestedProp('one.two'));
console.log(a.values[0].value); // foo

1

Ricardo Tomasi의 답변 에서 다음을 복사하고 아직 존재하지 않는 하위 객체를 만들도록 수정했습니다. 약간 덜 효율적 if이지만 (더 많은 것들과 빈 객체 만들기) 꽤 좋을 것입니다.

또한 Object.prop(obj, 'a.b', false)이전에는 할 수 없었던 곳에서 할 수 있습니다. 불행히도 여전히 우리에게 할당 할 수는 없습니다 undefined... 아직 그 방법에 대해 잘 모르겠습니다.

/**
 * Object.prop()
 *
 * Allows dot-notation access to object properties for both getting and setting.
 *
 * @param {Object} obj    The object we're getting from or setting
 * @param {string} prop   The dot-notated string defining the property location
 * @param {mixed}  val    For setting only; the value to set
 */
 Object.prop = function(obj, prop, val){
   var props = prop.split('.'),
       final = props.pop(),
       p;

   for (var i = 0; i < props.length; i++) {
     p = props[i];
     if (typeof obj[p] === 'undefined') {
       // If we're setting
       if (typeof val !== 'undefined') {
         // If we're not at the end of the props, keep adding new empty objects
         if (i != props.length)
           obj[p] = {};
       }
       else
         return undefined;
     }
     obj = obj[p]
   }
   return typeof val !== "undefined" ? (obj[final] = val) : obj[final]
 }

1

점 표기법 키가 포함 된 객체를 해당 키 의 배열 버전 으로 변환 하려면이 키를 사용할 수 있습니다.


이것은 다음과 같은 것을 변환합니다

{
  name: 'Andy',
  brothers.0: 'Bob'
  brothers.1: 'Steve'
  brothers.2: 'Jack'
  sisters.0: 'Sally'
}

{
  name: 'Andy',
  brothers: ['Bob', 'Steve', 'Jack']
  sisters: ['Sally']
}

convertDotNotationToArray(objectWithDotNotation) {

    Object.entries(objectWithDotNotation).forEach(([key, val]) => {

      // Is the key of dot notation 
      if (key.includes('.')) {
        const [name, index] = key.split('.');

        // If you have not created an array version, create one 
        if (!objectWithDotNotation[name]) {
          objectWithDotNotation[name] = new Array();
        }

        // Save the value in the newly created array at the specific index 
        objectWithDotNotation[name][index] = val;
        // Delete the current dot notation key val
        delete objectWithDotNotation[key];
      }
    });

}

JSON과 같이 "brothers.0.one"을 사용하여 값을 처리하지 않았습니다.
Karthick 2016 년

배열 컬렉션을 사용하여 더 복잡한 JSON을 처리 할 수있는 제안이 있습니까?
Karthick 2016 년

0

사용하지 않고 내 코드는 다음과 같습니다 eval. 이해하기도 쉽습니다.

function value(obj, props) {
  if (!props) return obj;
  var propsArr = props.split('.');
  var prop = propsArr.splice(0, 1);
  return value(obj[prop], propsArr.join('.'));
}

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

console.log(value(obj, 'a.d.a.b')); //returns blah

0

예, 4 년 전에 요청을 받았으며 기본 프로토 타입을 확장하는 것은 일반적으로 좋은 생각은 아니지만 모든 확장을 한 곳에 보관하면 유용 할 수 있습니다.
자, 여기 내 길이 있습니다.

   Object.defineProperty(Object.prototype, "getNestedProperty", {
    value     : function (propertyName) {
        var result = this;
        var arr = propertyName.split(".");

        while (arr.length && result) {
            result = result[arr.shift()];
        }

        return result;
    },
    enumerable: false
});

이제 함수 또는 복사 / 붙여 넣기 기능이있는 모듈을 가져 오지 않고도 어디서나 중첩 된 속성을 얻을 수 있습니다.

UPD. 예 :

{a:{b:11}}.getNestedProperty('a.b'); //returns 11

UPD 2. 다음 확장 프로그램이 내 프로젝트에서 몽구스를 깨뜨 렸습니다. 또한 jquery가 손상 될 수 있다는 것을 읽었습니다. 그러므로 절대로 다음 방법으로하지 마십시오

 Object.prototype.getNestedProperty = function (propertyName) {
    var result = this;
    var arr = propertyName.split(".");

    while (arr.length && result) {
        result = result[arr.shift()];
    }

    return result;
};

0

여기 내 구현입니다

구현 1

Object.prototype.access = function() {
    var ele = this[arguments[0]];
    if(arguments.length === 1) return ele;
    return ele.access.apply(ele, [].slice.call(arguments, 1));
}

구현 2 (슬라이스 대신 배열 축소 사용)

Object.prototype.access = function() {
    var self = this;
    return [].reduce.call(arguments,function(prev,cur) {
        return prev[cur];
    }, self);
}

예 :

var myobj = {'a':{'b':{'c':{'d':'abcd','e':[11,22,33]}}}};

myobj.access('a','b','c'); // returns: {'d':'abcd', e:[0,1,2,3]}
myobj.a.b.access('c','d'); // returns: 'abcd'
myobj.access('a','b','c','e',0); // returns: 11

또한 배열 내부의 객체를 처리 할 수 ​​있습니다.

var myobj2 = {'a': {'b':[{'c':'ab0c'},{'d':'ab1d'}]}}
myobj2.access('a','b','1','d'); // returns: 'ab1d'

0

이것은 ninjagecko가 제안한 확장 솔루션입니다.

나를 위해 간단한 문자열 표기법으로는 충분하지 않으므로 아래 버전은 다음과 같은 것을 지원합니다.

index (obj, 'data.accounts [0] .address [0] .postcode');

/**
 * Get object by index
 * @supported
 * - arrays supported
 * - array indexes supported
 * @not-supported
 * - multiple arrays
 * @issues:
 *  index(myAccount, 'accounts[0].address[0].id') - works fine
 *  index(myAccount, 'accounts[].address[0].id') - doesnt work
 * @Example:
 * index(obj, 'data.accounts[].id') => returns array of id's
 * index(obj, 'data.accounts[0].id') => returns id of 0 element from array
 * index(obj, 'data.accounts[0].addresses.list[0].id') => error
 * @param obj
 * @param path
 * @returns {any}
 */
var index = function(obj, path, isArray?, arrIndex?){

    // is an array
    if(typeof isArray === 'undefined') isArray = false;
    // array index,
    // if null, will take all indexes
    if(typeof arrIndex === 'undefined') arrIndex = null;

    var _arrIndex = null;

    var reduceArrayTag = function(i, subArrIndex){
        return i.replace(/(\[)([\d]{0,})(\])/, (i) => {
            var tmp = i.match(/(\[)([\d]{0,})(\])/);
            isArray = true;
            if(subArrIndex){
                _arrIndex =  (tmp[2] !== '') ? tmp[2] : null;
            }else{
                arrIndex =  (tmp[2] !== '') ? tmp[2] : null;
            }
            return '';
        });
    }

    function byIndex(obj, i) {
        // if is an array
        if(isArray){
            isArray = false;
            i = reduceArrayTag(i, true);
            // if array index is null,
            // return an array of with values from every index
            if(!arrIndex){
                var arrValues = [];
                _.forEach(obj, (el) => {
                    arrValues.push(index(el, i, isArray, arrIndex));
                })
                return arrValues;
            }
            // if array index is specified
            var value = obj[arrIndex][i];
            if(isArray){
                arrIndex = _arrIndex;
            }else{
                arrIndex = null;
            }
            return value;
        }else{
            // remove [] from notation,
            // if [] has been removed, check the index of array
            i = reduceArrayTag(i, false);
            return obj[i]
        }
    }

    // reduce with byIndex method
    return path.split('.').reduce(byIndex, obj)
}

0

죽은 말을 때릴 위험이 있습니다 ... 이것은 중첩 된 객체를 횡단하여 기본 객체 또는 동일한 구조를 가진 유사한 객체와 관련된 위치를 참조하는 데 가장 유용합니다. 이를 위해 중첩 된 객체 순회 기능에 유용합니다. 경로를 유지하기 위해 배열을 사용했습니다. 문자열 경로 나 배열을 사용하도록 이것을 수정하는 것은 쉽지 않습니다. 또한 다른 구현과 달리 "정의되지 않은"값을 할당 할 수 있습니다.

/*
 * Traverse each key in a nested object and call fn(curObject, key, value, baseObject, path)
 * on each. The path is an array of the keys required to get to curObject from
 * baseObject using objectPath(). If the call to fn() returns falsey, objects below
 * curObject are not traversed. Should be called as objectTaverse(baseObject, fn).
 * The third and fourth arguments are only used by recursion.
 */
function objectTraverse (o, fn, base, path) {
    path = path || [];
    base = base || o;
    Object.keys(o).forEach(function (key) {
        if (fn(o, key, o[key], base, path) && jQuery.isPlainObject(o[key])) {
            path.push(key);
            objectTraverse(o[key], fn, base, path);
            path.pop();
        }
    });
}

/*
 * Get/set a nested key in an object. Path is an array of the keys to reference each level
 * of nesting. If value is provided, the nested key is set.
 * The value of the nested key is returned.
 */
function objectPath (o, path, value) {
    var last = path.pop();

    while (path.length && o) {
        o = o[path.shift()];
    }
    if (arguments.length < 3) {
        return (o? o[last] : o);
    }
    return (o[last] = value);
}


0

내 프로젝트 에서이 코드를 사용했습니다.

const getValue = (obj, arrPath) => (
  arrPath.reduce((x, y) => {
    if (y in x) return x[y]
    return {}
  }, obj)
)

용법:

const obj = { id: { user: { local: 104 } } }
const path = [ 'id', 'user', 'local' ]
getValue(obj, path) // return 104

0

몇 년 후, 범위와 배열을 처리하는 것을 발견했습니다. 예 :a['b']["c"].d.etc

function getScopedObj(scope, str) {
  let obj=scope, arr;

  try {
    arr = str.split(/[\[\]\.]/) // split by [,],.
      .filter(el => el)             // filter out empty one
      .map(el => el.replace(/^['"]+|['"]+$/g, '')); // remove string quotation
    arr.forEach(el => obj = obj[el])
  } catch(e) {
    obj = undefined;
  }

  return obj;
}

window.a = {b: {c: {d: {etc: 'success'}}}}

getScopedObj(window, `a.b.c.d.etc`)             // success
getScopedObj(window, `a['b']["c"].d.etc`)       // success
getScopedObj(window, `a['INVALID']["c"].d.etc`) // undefined

-1

귀하의 질문이 무엇인지 명확하지 않습니다. 물체가 주어지면 obj.a.b그대로 "2"를 줄 것입니다. 대괄호를 사용하도록 문자열을 조작하려면 다음을 수행하십시오.

var s = 'a.b';
s = 'obj["' + s.replace(/\./g, '"]["') + '"]';
alert(s); // displays obj["a"]["b"]

1
이것은 효과 a.b.c가 없으며 실제로 원하는 것을 달성하지 못합니다. 그들은 eval경로가 아닌 가치를 원합니다 .
McKayla 2016 년

나는 그것을 고치기 위해 그것을 고쳤 a.b.c지만, 당신은 옳다. 그는에서 속성 값을 얻거나 설정하기를 원했다 obj.a.b. 그가 "줄을
바꾸고

잘 했어. :) 약간 모호했다. 그래도 당신은 좋은 변환 작업을 수행했습니다.
McKayla

-1

여기에 내 10 센트가 있습니다. 벨로 프 기능은 제공된 경로를 기반으로 가져 오거나 설정됩니다. Object.hasOwnProperty실수로 잘못된 값을 신경 쓰면이를 대체하십시오 .

나는 그것을 a.b.cab2.c로 테스트하고 {a:{b:[0,1,{c:7}]}}설정과 가져 오기 모두에 대한 작품 :).

치어스

function helper(obj, path, setValue){
  const l = String(path).split('.');
  return l.reduce((o,i, idx)=>{
   if( l.length-idx===1)  { o[i] = setValue || o[i];return setValue ? obj : o[i];}
  o[i] = o[i] || {};
   return o[i];
  }, x)
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.