키 이름의 MongoDB 점 (.)


95

mongo는 점 (.) 또는 달러 기호 ($)가있는 키 삽입을 허용하지 않는 것 같지만 mongoimport 도구를 사용하여 점이 포함 된 JSON 파일을 가져 오면 제대로 작동했습니다. 운전자가 해당 요소를 삽입하려는 것에 대해 불평하고 있습니다.

다음은 데이터베이스에서 문서의 모습입니다.

{
    "_id": {
        "$oid": "..."
    },
    "make": "saab",
    "models": {
        "9.7x": [
            2007,
            2008,
            2009,
            2010
        ]
    }
}

내가이 모든 일을 잘못하고 있고 외부 데이터 (예 : 모델)와 함께 해시 맵을 사용해서는 안됩니까? 아니면 어떻게 든 점을 이스케이프 할 수 있습니까? 어쩌면 나는 너무 많은 Javascript와 비슷하다고 생각하고 있습니다.


답변:


87

MongoDB는 점이 있는 키를 지원하지 않으므로 가져 오기 전에 JSON 파일을 제거 / 교체하기 위해 사전 처리해야합니다. 그렇지 않으면 모든 종류의 문제에 대해 스스로 설정해야합니다.

이 문제에 대한 표준 해결 방법은 없으며 최상의 방법은 상황의 세부 사항에 너무 의존하는 것입니다. 하지만 가능한 한 모든 주요 인코더 / 디코더 접근 방식을 피하고 싶습니다. JSON 재구성은 아마도 일회성 비용이 될 수있는 불편 함을 영구적으로 지불 할 것이기 때문입니다.


1
표준적인 방법은 없다고 생각합니다. 최선의 접근 방식은 상황의 특성에 너무 의존적입니다. 하지만 가능한 한 모든 주요 인코더 / 디코더 접근 방식을 피하고 싶습니다. JSON 재구성은 아마도 일회성 비용이 될 수있는 불편 함을 영구적으로 지불 할 것이기 때문입니다.
JohnnyHK

8
이 상황을 다시 만났습니다. 이것은 우리가 제어 할 수 있고 종종 쿼리해야하는 앱 키 이름에서 그렇게 많이 발생하지 않는 것 같습니다.하지만 사용자가 제공 한 데이터를 제어 할 수 없지만 (a) Mongo에 저장하고 싶은 중첩 된 데이터 구조로 , (b) 우리는 이것이 발생할 수있는 특정 필드 (예 : models여기)를 알고 있으며 (c) Mongo에서 키 이름으로 쿼리 할 필요가 없습니다. 그래서 제가 정한 패턴은 JSON.stringify저장시이 필드에, 검색시 'JSON.parse'입니다.
프로토 타입

16
필요한 경우 {check_keys : false} 옵션을 제공하여이 문제를 우회 할 수 있습니다.
Tzury Bar Yochay

5
@TzuryBarYochay OMG 당신은 북서쪽 통로와 동등한 MongoDB를 찾았습니다. 나는 이것이 받아 들여진 대답이어야한다고 생각한다.
프로토 타입 '

2
@emarel db.collection_foo.update ({이 : "그"}, {$ 세트 : {A : "B"}}, {check_keys : 거짓})
Tzury 바 Yochay

23

다른 답변에서 언급했듯이 MongoDB는 필드 이름대한 제한으로 인해 $또는 .문자를 맵 키로 허용하지 않습니다 . 그러나 달러 기호 연산자 에서 언급 했듯이이 제한 사항을 이스케이프 한다고해서 이러한 키가있는 문서 삽입 할 수있는 것은 아닙니다. 문서를 업데이트하거나 쿼리 할 수 ​​없습니다.

단순히 교체의 문제 .와 함께 [dot]U+FF0E(이 페이지에 다른 곳에서 언급 한 바와 같이이) 어떤 사용자가 합법적으로 키 저장하려고 할 때 발생한다 [dot]또는 U+FF0E?

Fantom의 afMorphia 드라이버 가 취하는 접근 방식 은 Java와 유사한 유니 코드 이스케이프 시퀀스를 사용하지만 이스케이프 문자가 먼저 이스케이프되도록하는 것입니다. 본질적으로 다음과 같은 문자열 대체가 이루어집니다 (*).

\  -->  \\
$  -->  \u0024
.  -->  \u002e

나중에 MongoDB 에서 맵 키를 읽을 때 역방향 교체가 수행됩니다 .

또는 Fantom 코드에서 :

Str encodeKey(Str key) {
    return key.replace("\\", "\\\\").replace("\$", "\\u0024").replace(".", "\\u002e")
}

Str decodeKey(Str key) {
    return key.replace("\\u002e", ".").replace("\\u0024", "\$").replace("\\\\", "\\")
}

사용자가 이러한 변환을 인식해야하는 유일한 시간은 해당 키에 대한 쿼리를 구성 할 때입니다.

dotted.property.names구성 목적으로 데이터베이스에 저장하는 것이 일반적이라는 점을 감안할 때 이러한 접근 방식이 모든 맵 키를 단순히 금지하는 것보다 낫다고 생각합니다.

(*) afMorphia는 실제로 Java의 유니 코드 이스케이프 구문에 언급 된대로 전체 / 적절한 유니 코드 이스케이프 규칙을 수행 하지만 설명 된 대체 시퀀스도 잘 작동합니다.


//g첫 번째 항목 만이 아니라 모든 항목을 대체 하는 데 사용해야 합니다. 또한 Martin Konecny의 답변에서와 같이 전폭 등가물을 사용하는 것이 좋은 생각 인 것 같습니다. 마지막으로 백 슬래시 하나면 인코딩에 충분합니다. key.replace(/\./g, '\uff0e').replace(/\$/g, '\uff04').replace(/\\/g, '\uff3c')
cw '

1
@cw '-코드는 Java와 유사한 구문이므로 replace 는 실제로 모든 발생을 대체하며 백 슬래시를 이스케이프하려면 이중 백 슬래시가 필요합니다. 다시 말하지만, 모든 사례가 보장되도록 몇 가지 형태의 이스케이프를 도입해야합니다 . 누군가가 실제로 U+FF04.
Steve Eynon 2016-08-04

2
결과적으로 Mongodb는 최신 버전에서 점과 달러를 지원합니다. 참조 : - stackoverflow.com/a/57106679/3515086
Abhidemon

18

몽고의 워드 프로세서 와 같은 잘못된 문자를 교체 할 것을 제안 $하고 .자신의 유니 코드 등가물.

이러한 상황에서 키는 예약 된 $ 및을 대체해야합니다. 문자. 모든 문자로 충분하지만 U + FF04 (예 : "$") 및 U + FF0E (예 : ".")와 같은 유니 코드 전체 너비를 사용하는 것이 좋습니다.


74
이는 앞으로 엄청난 디버깅 문제를 해결하는 방법처럼 들립니다.
아무도

2
@AndrewMedico는 @tamlyn - 나는 문서를 같은 평균 뭔가 생각db.test.insert({"field\uff0ename": "test"})
P. 마이어 NORE

4
-1 A. 그건 끔찍한 생각입니다. 누군가가 실제로 이러한 유니 코드 문자를 키로 사용하려고하면 어떨까요? 그러면 시스템에 대해 아는 사람이 수행하는 조용한 오류가 발생합니다. 그런 모호한 이스케이프 방법을 사용하지 마십시오. B. 몽고 문서는 더 이상 그렇게 말하지 않습니다. 아마도 누군가가 그 끔찍한 아이디어를 깨달았 기 때문일 것입니다
BT

7
@SergioTulentsev 추천을 제거했습니다 :) github.com/mongodb/docs/commit/…
BT

2
@BT : 모자 팁, 선생님 :)
Sergio Tulentsev

15

MongoDB의 최신 안정 버전 (v3.6.1)은 현재 키 또는 필드 이름에 점 (.)을 지원합니다.

이제 필드 이름에 점 (.) 및 달러 ($) 문자가 포함될 수 있습니다.


10
서버가 지금 지원하더라도 드라이버는 여전히 키에서 $ 및 점을 확인하고 허용하지 않습니다. 따라서 Mongo는 이론적으로 점과 달러 문자 만 지원합니다. 실제로 이것은 아직 사용할 수 없습니다 :(
JMax

오래되었거나 호환되지 않는 클라이언트를 사용하고있을 수 있습니다. 나는 땀을 흘리지 않고 내 프로덕션 서버에서 이것을 사용하고 있습니다. NodeJS 및 Java 클라이언트를 확인했습니다.
h4ck3d

Java에서는 분명히 작동하지 않습니다! 다음 명령을 시도하십시오. mongoClient.getDatabase("mydb").getCollection("test").insertOne(new Document("value", new Document("key.with.dots", "value").append("$dollar", "value")));mongodb-driver.3.6.3 및 MongoDB 3.6.3을 사용하면 실패합니다.
jmax에

1
실제로 나는 설정 mongodb-4.1.1pymongo-3.7.1. 내가 함께 키를 포함하는 문서에 추가 할 수 .에서 robomongo과를하지만 pymongo그것은 인상이 문턱, InvalidDocument: key '1.1' must not contain '.'... 그것은 지금에 의해 고정되어 있었으면 좋겠다
학습은 엉망이다

mongodb 서버 4.0.9 및 Java 드라이버 3.10.2로 시도했지만 키 이름에 점을 허용하지 않습니다. robomongo를 사용하여 시도하면 작동한다는 것이 이상합니다 ...
xyzt

12

방금 구현 한 솔루션 중 정말 만족 스럽습니다. 키 이름과 값을 두 개의 개별 필드로 분할하는 것입니다. 이렇게하면 문자를 똑같이 유지할 수 있으며 구문 분석 악몽에 대해 걱정할 필요가 없습니다. 문서는 다음과 같습니다.

{
    ...
    keyName: "domain.com",
    keyValue: "unregistered",
    ...
}

keyName keyValuefind 필드 에서 a 를 수행하여 쉽게 쿼리 할 수 ​​있습니다 .

그래서 대신 :

 db.collection.find({"domain.com":"unregistered"})

실제로 예상대로 작동하지 않을 경우 다음을 실행합니다.

db.collection.find({keyName:"domain.com", keyValue:"unregistered"})

예상 된 문서를 반환합니다.


어떻게 했어요? 같은 사건으로 저를 도와 주시겠습니까?
프로파일 러

쿼리 예를 추가했습니다. 도움이 되나요?
Steve

10

값 대신 키에 해시를 사용한 다음 해당 값을 JSON 값에 저장할 수 있습니다.

var crypto = require("crypto");   

function md5(value) {
    return crypto.createHash('md5').update( String(value) ).digest('hex');
}

var data = {
    "_id": {
        "$oid": "..."
    },
    "make": "saab",
    "models": {}
}

var version = "9.7x";

data.models[ md5(version) ] = {
    "version": version,
    "years" : [
        2007,
        2008,
        2009,
        2010
    ]
}

그런 다음 나중에 해시를 사용하여 모델에 액세스합니다.

var version = "9.7x";
collection.find( { _id : ...}, function(e, data ) {
    var models = data.models[ md5(version) ];
}

1
나는 이것이 일방향 해싱을 사용하는 깨끗한 솔루션을 좋아하며 내부에서 작동하는 방식과 정말 유사합니다.
Michael Yagudaev 2014

3
해시를 키로 사용할 때의 문제는 고유성이 보장되지 않고 자주 충돌을 생성한다는 것 입니다. 게다가 맵에 액세스 할 때마다 암호화 해시를 계산하는 것은 나에게 가장 최적의 솔루션이 아닌 것 같습니다.
Steve Eynon 2015

2
마침표를 특수 문자 또는 시퀀스로 바꾸는 것보다 이것이 더 나은 이유는 무엇입니까?
B Seven

문자열을 base64로 변환하는 것이 훨씬 좋습니다.
Zen

8

지금 지원됩니다

MongoDb 3.6 이상 에서는 필드 이름에서 달러 를 모두 지원 합니다. JIRA 아래 참조 : https://jira.mongodb.org/browse/JAVA-2810

Mongodb를 3.6 이상으로 업그레이드하는 것이 가장 좋은 방법입니다.


이것이 가장 좋은 대답입니다. : +1
hello_abhishek jul.

3
3.6은 저장할 수 있지만 아직 지원 되지 않으며 드라이버 오류가 발생하고 쿼리 / 업데이트가 중단 될 수 있습니다. 제한 사항 : "MongoDB 쿼리 언어는 필드 이름에 이러한 문자가 포함 된 문서에 대해 항상 의미있게 쿼리를 표현할 수는 없습니다 (SERVER- 쿼리 언어에 지원이 추가 될 때까지 필드 이름에 $ 및.의 사용은 권장 되지 않으며 공식 MongoDB 드라이버에서 지원하지 않습니다 . "
JeremyDouglass


4

열쇠를 탈출해야합니다. 대부분의 사람들은 문자열을 올바르게 이스케이프하는 방법을 모르는 것 같으므로 다음 단계를 따르십시오.

  1. 이스케이프 문자를 선택하십시오 (거의 사용하지 않는 문자를 선택하는 것이 가장 좋습니다). 예 : '~'
  2. 이스케이프하려면 먼저 이스케이프 문자의 모든 인스턴스를 이스케이프 문자가 추가 된 시퀀스 (예 : '~'-> '~ t')로 바꾼 다음 이스케이프해야하는 문자 나 시퀀스를 이스케이프 문자가 추가 된 시퀀스로 바꿉니다. . 예 : '.' -> '~ p'
  3. 이스케이프를 해제하려면 먼저 두 번째 이스케이프 시퀀스 (예 : '~ p'-> '.')의 모든 인스턴스에서 이스케이프 시퀀스를 제거한 다음 이스케이프 문자 시퀀스를 단일 이스케이프 문자 (예 : '~ s'-> '~)로 변환합니다. ')

또한 mongo는 키가 '$'로 시작하는 것을 허용하지 않으므로 유사한 작업을 수행해야합니다.

다음은이를 수행하는 몇 가지 코드입니다.

// returns an escaped mongo key
exports.escape = function(key) {
  return key.replace(/~/g, '~s')
            .replace(/\./g, '~p')
            .replace(/^\$/g, '~d')
}

// returns an unescaped mongo key
exports.unescape = function(escapedKey) {
  return escapedKey.replace(/^~d/g, '$')
                   .replace(/~p/g, '.')
                   .replace(/~s/g, '~')
}

이 이스케이프는 '. ~ p.'와 같은 문자열이 있으면 여전히 깨질 수 있습니다. 여기서 이스케이프 된 문자열은 '~ p ~~ p ~ p'입니다. 이스케이프를 해제하면 실제 문자열과 다른 '. ~ ..'가 표시됩니다.
jvc

1
@jvc 당신이 맞아요! 설명과 예제 이스케이프 기능을 수정했습니다. 아직 고장난 경우 알려주세요!
BT

3

늦은 답변이지만 Spring과 Mongo를 사용하는 경우 Spring은 MappingMongoConverter. JohnnyHK의 솔루션이지만 Spring에서 처리합니다.

@Autowired
private MappingMongoConverter converter;

@PostConstruct
public void configureMongo() {
 converter.setMapKeyDotReplacement("xxx");
}

저장된 Json이 다음과 같은 경우 :

{ "axxxb" : "value" }

Spring (MongoClient)을 통해 다음과 같이 읽 힙니다.

{ "a.b" : "value" }

찾을 수없는 'org.springframework.data.mongodb.core.convert.MappingMongoConverter'유형의 빈이 필요합니다.
Sathya Narayan C

1

각 객체 키에 대해 JavaScript에서 다음 이스케이프를 사용합니다.

key.replace(/\\/g, '\\\\').replace(/^\$/, '\\$').replace(/\./g, '\\_')

내가 좋아하는 점은 $처음 에만 교체 하고 콘솔에서 사용하기 까다로울 수있는 유니 코드 문자를 사용하지 않는다는 것입니다. _나에게 유니 코드 문자보다 훨씬 더 읽기 쉽다. 또한 한 세트의 특수 문자 ( $, .)를 다른 세트 (유니 코드)로 바꾸지 않습니다. 그러나 전통적인 \.


3
그리고 누군가 키에 _를 사용하면 버그가 발생합니다.
BT

1

완벽하지는 않지만 대부분의 상황에서 작동합니다. 금지 된 문자를 다른 것으로 대체합니다. 키에 있기 때문에 이러한 새 문자는 상당히 드뭅니다.

/** This will replace \ with ⍀, ^$ with '₴' and dots with ⋅  to make the object compatible for mongoDB insert. 
Caveats:
    1. If you have any of ⍀, ₴ or ⋅ in your original documents, they will be converted to \$.upon decoding. 
    2. Recursive structures are always an issue. A cheap way to prevent a stack overflow is by limiting the number of levels. The default max level is 10.
 */
encodeMongoObj = function(o, level = 10) {
    var build = {}, key, newKey, value
    //if (typeof level === "undefined") level = 20     // default level if not provided
    for (key in o) {
        value = o[key]
        if (typeof value === "object") value = (level > 0) ? encodeMongoObj(value, level - 1) : null     // If this is an object, recurse if we can

        newKey = key.replace(/\\/g, '⍀').replace(/^\$/, '₴').replace(/\./g, '⋅')    // replace special chars prohibited in mongo keys
        build[newKey] = value
    }
    return build
}

/** This will decode an object encoded with the above function. We assume the structure is not recursive since it should come from Mongodb */
decodeMongoObj = function(o) {
    var build = {}, key, newKey, value
    for (key in o) {
        value = o[key]
        if (typeof value === "object") value = decodeMongoObj(value)     // If this is an object, recurse
        newKey = key.replace(/⍀/g, '\\').replace(/^₴/, '$').replace(/⋅/g, '.')    // replace special chars prohibited in mongo keys
        build[newKey] = value
    }
    return build
}

다음은 테스트입니다.

var nastyObj = {
    "sub.obj" : {"$dollar\\backslash": "$\\.end$"}
}
nastyObj["$you.must.be.kidding"] = nastyObj     // make it recursive

var encoded = encodeMongoObj(nastyObj, 1)
console.log(encoded)
console.log( decodeMongoObj( encoded) )

및 결과-값은 수정되지 않습니다.

{
  sub⋅obj: {
    ₴dollar⍀backslash: "$\\.end$"
  },
  ₴you⋅must⋅be⋅kidding: {
    sub⋅obj: null,
    ₴you⋅must⋅be⋅kidding: null
  }
}
[12:02:47.691] {
  "sub.obj": {
    $dollar\\backslash: "$\\.end$"
  },
  "$you.must.be.kidding": {
    "sub.obj": {},
    "$you.must.be.kidding": {}
  }
}

1

디버그 목적보다는 응용 프로그램에서 사용하지 않는 것이 좋지 않은 쿼리 방법이 있습니다 (내장 된 개체에서만 작동).

db.getCollection('mycollection').aggregate([
    {$match: {mymapfield: {$type: "object" }}}, //filter objects with right field type
    {$project: {mymapfield: { $objectToArray: "$mymapfield" }}}, //"unwind" map to array of {k: key, v: value} objects
    {$match: {mymapfield: {k: "my.key.with.dot", v: "myvalue"}}} //query
])

1

다른 사용자가 언급했듯이 인코딩 / 디코딩은 향후 문제가 될 수 있으므로 점이있는 모든 키를 대체하는 것이 더 쉬울 수 있습니다. 다음은 키를 '.'로 대체하기 위해 만든 재귀 함수입니다. 발생 :

def mongo_jsonify(dictionary):
    new_dict = {}
    if type(dictionary) is dict:
        for k, v in dictionary.items():
            new_k = k.replace('.', '-')
            if type(v) is dict:
                new_dict[new_k] = mongo_jsonify(v)
            elif type(v) is list:
                new_dict[new_k] = [mongo_jsonify(i) for i in v]
            else:
                new_dict[new_k] = dictionary[k]
        return new_dict
    else:
        return dictionary

if __name__ == '__main__':
    with open('path_to_json', "r") as input_file:
        d = json.load(input_file)
    d = mongo_jsonify(d)
    pprint(d)

mongo가 키에서 허용하지 않는 또 다른 문자이기 때문에이 코드를 수정하여 '$'를 대체 할 수도 있습니다.


0

PHP의 경우 HTML 값을 마침표로 대체합니다. 그게 ".".

다음과 같이 MongoDB에 저장됩니다.

  "validations" : {
     "4e25adbb1b0a55400e030000" : {
     "associate" : "true" 
    },
     "4e25adb11b0a55400e010000" : {
       "associate" : "true" 
     } 
   } 

그리고 PHP 코드 ...

  $entry = array('associate' => $associate);         
  $data = array( '$set' => array( 'validations.' . str_replace(".", `"."`, $validation) => $entry ));     
  $newstatus = $collection->update($key, $data, $options);      

0

Lodash 쌍 을 사용하면 변경할 수 있습니다.

{ 'connect.sid': 's:hyeIzKRdD9aucCc5NceYw5zhHN5vpFOp.0OUaA6' }

으로

[ [ 'connect.sid',
's:hyeIzKRdD9aucCc5NceYw5zhHN5vpFOp.0OUaA6' ] ]

사용

var newObj = _.pairs(oldObj);

0

그대로 보관하고 나중에 예쁘게 변환 할 수 있습니다.

이 예제를 Livescript에 썼습니다. livescript.net 웹 사이트를 사용하여 평가할 수 있습니다.

test =
  field:
    field1: 1
    field2: 2
    field3: 5
    nested:
      more: 1
      moresdafasdf: 23423
  field3: 3



get-plain = (json, parent)->
  | typeof! json is \Object => json |> obj-to-pairs |> map -> get-plain it.1, [parent,it.0].filter(-> it?).join(\.)
  | _ => key: parent, value: json

test |> get-plain |> flatten |> map (-> [it.key, it.value]) |> pairs-to-obj

그것은 생산할 것입니다

{"field.field1":1,
 "field.field2":2,
 "field.field3":5,
 "field.nested.more":1,
 "field.nested.moresdafasdf":23423,
 "field3":3}


0

내 팁을주세요 : JSON.stringify를 사용하여 Object / Array에 점이있는 키 이름을 저장 한 다음 데이터베이스에서 데이터를 가져올 때 처리 할 JSON.parse로 문자열을 Object로 구문 분석 할 수 있습니다.

또 다른 해결 방법 : 다음과 같이 스키마를 재구성합니다.

key : {
"keyName": "a.b"
"value": [Array]
}

0

최신 MongoDB는 점이있는 키를 지원하지만 Java MongoDB 드라이버는 지원하지 않습니다. 그래서 Java에서 작동하도록 java-mongo-driver의 github repo 에서 코드를 가져와 그에 따라 isValid Key 함수를 변경하고 새로운 jar를 만들어 지금 사용했습니다.


0

점 ( .) 또는 달러 ( $)를 실제 문서에서 사용되지 않는 다른 문자로 바꿉니다 . 그리고 문서를 검색 할 때 점 ( .) 또는 달러 ( $)를 복원하십시오 . 이 전략은 사용자가 읽는 데이터에 영향을주지 않습니다.

모든 문자 에서 문자를 선택할 수 있습니다 .


0

이상한 점은 mongojs를 사용하여 _id를 직접 설정하면 점이있는 문서를 만들 수 있지만 _id가 생성 될 때 문서를 만들 수 없다는 것입니다.

작동합니까 :

db.testcollection.save({"_id": "testdocument", "dot.ted.": "value"}, (err, res) => {
    console.log(err, res);
});

작동하지 않음 :

db.testcollection.save({"dot.ted": "value"}, (err, res) => {
    console.log(err, res);
});

처음에는 도트 키로 문서를 업데이트하는 것도 효과가 있다고 생각했지만 도트를 하위 키로 식별했습니다!

mongojs가 점 (하위 키)을 처리하는 방법을 살펴보면 키에 점이 포함되어 있지 않은지 확인하겠습니다.


0

@JohnnyHK 가 언급 한 것처럼 구두점 또는 '.'를 제거하십시오. 데이터가 더 큰 데이터 세트에 축적되기 시작할 때 훨씬 더 큰 문제가 발생하기 때문입니다. 이는 특히 오류가 발생하는 키에 액세스하고 비교해야하는 $ merge와 같은 집계 연산자를 호출 할 때 문제를 일으킬 수 있습니다. 나는 그것을 어려운 방법으로 배웠습니다. 시작하는 사람들을 위해 반복하지 마십시오.


-2

/home/user/anaconda3/lib/python3.6/site-packages/pymongo/collection.py

오류 메시지에서 찾았습니다. 사용하는 경우 anaconda(해당 파일 찾기) 위에 언급 된 파일에서 값을에서 check_keys = True로 변경하십시오 False. 작동합니다!

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.