Pregunta

I try to use firebase db, I found very important restrictions, which are not described in firebase help or FAQ.

First problem is that symbol: dot '.' prohibited in keys,

i.e. firebase reject (with unknown reason) next:

        nameRef.child('Henry.Morgan@caribbean.sea').set('Pirat');

Second problem with forward slashes in your keys '/', when you try to add key like this

        {'02/10/2013': true}

In firebase you can see:

         '02': {
             '10': {
                 '2013': true
             }
         }       

Have you got any ideas how to solve it (automatically)? May be set some flag that it is string key with all symbols? Of course, I can parse/restore data every time before write and after read, but...

By the way '.' '/' - all restricted symbols for firebase ?

¿Fue útil?

Solución

The reason that adding a child 02/10/2013 creates a structure in Firebase is because the forward slashes are resulting in the creation of a new level.

So the line I assume you are using something similar to: firebaseRef.child('02/10/2013').set(true) is equivalent to firebaseRef.child('02').child('10').child('2013').set(true).

To avoid the problems of not being able to use the following characters in reference key names (source),

  • . (period)
  • $ (dollar sign)
  • [ (left square bracket)
  • ] (right square bracket)
  • # (hash or pound sign)
  • / (forward slash)

we can use one of JavaScript's built in encoding functions since as far as I can tell, Firebase does not provide a built in method to do so. Here's a run-through to see which is the most effective for our purposes:

var forbiddenChars = '.$[]#/'; //contains the forbidden characters
escape(forbiddenChars); //results in ".%24%5B%5D%23/"
encodeURI(forbiddenChars); //results in ".%24%5B%5D%23%2F"
encodeURIComponent(forbiddenChars); //results in ".%24%5B%5D%23%2F"

Evidently, the most effective solution is encodeURIComponent. However, it doesn't solve all our problems. The . character still poses a problem as shown by the above test and trying to encodeURIComponent your test e-mail address. My suggestion would be to chain a replace function after the encodeURIComponent to deal with the periods.

Here's what the solution would look like for your two example cases:

encodeURIComponent('Henry.Morgan@caribbean.sea').replace(/\./g, '%2E') //results in "Henry%2EMorgan%40caribbean%2Esea"
encodeURIComponent('02/10/2013'); //results in "02%2F10%2F2013"

Since both the final results are safe for insertion into a Firebase as a key name, the only other concern is decoding after reading from a Firebase which can be solved with replace('%2E', '.') and a simple decodeURIComponent(...).

Otros consejos

I faced the same problem myself, and I have created firebase-encode for this purpose.

Unlike the chosen answer, firebase-encode encodes only unsafe characters (./[]#$) and % (necessary due to how encoding/decoding works). It leaves other special characters that are safe to be used as firebase key while encodeURIComponent will encode them.

Here's the source code for details:

// http://stackoverflow.com/a/6969486/692528
const escapeRegExp = (str) => str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');


const chars = '.$[]#/%'.split('');
const charCodes = chars.map((c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);

const charToCode = {};
const codeToChar = {};
chars.forEach((c, i) => {
  charToCode[c] = charCodes[i];
  codeToChar[charCodes[i]] = c;
});

const charsRegex = new RegExp(`[${escapeRegExp(chars.join(''))}]`, 'g');
const charCodesRegex = new RegExp(charCodes.join('|'), 'g');

const encode = (str) => str.replace(charsRegex, (match) => charToCode[match]);
const decode = (str) => str.replace(charCodesRegex, (match) => codeToChar[match]);

I wrote this for Java (since I came here expecting a java implementation):

public static String encodeForFirebaseKey(String s) {
    return s
            .replace("_", "__")
            .replace(".", "_P")
            .replace("$", "_D")
            .replace("#", "_H")
            .replace("[", "_O")
            .replace("]", "_C")
            .replace("/", "_S")
            ;
}

public static String decodeFromFirebaseKey(String s) {
    int i = 0;
    int ni;
    String res = "";
    while ((ni = s.indexOf("_", i)) != -1) {
        res += s.substring(i, ni);
        if (ni + 1 < s.length()) {
            char nc = s.charAt(ni + 1);
            if (nc == '_') {
                res += '_';
            } else if (nc == 'P') {
                res += '.';
            } else if (nc == 'D') {
                res += '$';
            } else if (nc == 'H') {
                res += '#';
            } else if (nc == 'O') {
                res += '[';
            } else if (nc == 'C') {
                res += ']';
            } else if (nc == 'S') {
                res += '/';
            } else {
                // this case is due to bad encoding
            }
            i = ni + 2;
        } else {
            // this case is due to bad encoding
            break;
        }
    }
    res += s.substring(i);
    return res;
}

Character limitations are documented at https://www.firebase.com/docs/creating-references.html - you cannot use '.', '/', '[', ']', '#', and '$' in key names. There is no automatic way of escaping these characters, I'd recommend avoiding their use altogether or creating your own escaping/unescaping mechanism.

If you're using Swift 3, this works for me (try it in a playground):

var str = "this.is/a#crazy[string]right$here.$[]#/"

if let strEncoded = str.addingPercentEncoding(withAllowedCharacters: .alphanumerics) {
    print(strEncoded)

    if let strDecoded = strEncoded.removingPercentEncoding {
        print(strDecoded)
    }
}

I got annoyed with this problem so I took the answer from @sushain97 (thanks!) and built a deep encoder/decoder.

https://www.npmjs.com/package/firebase-key-encode

Basic usage:

var firebaseKeyEncode = require('firebase-key-encode');
firebaseKeyEncode.encode('my.bad.key');
// Output: my%2Ebad%2Ekey

Deep Usage:

var firebaseKeyEncode = require('firebase-key-encode');

var badTree = {
    "pets": [
        {
            "jimmy.choo": 15}
        ],
    "other.key": 5
}

firebaseKeyEncode.deepEncode(badTree);

// Output: {
//    "pets": [
//        {
//            "jimmy%2Echoo": 15}
//        ],
//    "other%2Ekey": 5
// }

Personally, I found a simple and easy hack for this same problem I encountered

I took the dateTime string and convert it using replace('/','|')

the result will be something like this 2017|07|24 02:39:37 instead of 2017/07/24 02:39:37.

Even though it is not what OP asks,
but in my experience rather than using such dubious keys it is better to let .push() create an id,
and other things - e-mail, date etc. save as content of the dedicated fields.

$id: {
   email: "Henry.Morgan@caribbean.sea"
}

P.S. Don't try to save volume by inserting what should be content into the key.
Premature optimization is the root of all evil (c).

Efficient C# implementation (for Unity and .net). Based on the answer from @josue.0.

    public static string EncodeFirebaseKey(string s) {
            StringBuilder sb = new StringBuilder();
            foreach (char c in s) {
                switch (c) {
                    case '_':
                        sb.Append("__");
                        break;
                    case '$':
                        sb.Append("_D");
                        break;
                    case '.':
                        sb.Append("_P");
                        break;
                    case '#':
                        sb.Append("_H");
                        break;
                    case '[':
                        sb.Append("_O");
                        break;
                    case ']':
                        sb.Append("_C");
                        break;
                    case '/':
                        sb.Append("_S");
                        break;
                    default:
                        sb.Append(c);
                        break;
                }
            }
            return sb.ToString();
        }

        public static string DecodeFirebaseKey(string s) {
            StringBuilder sb = new StringBuilder();
            bool underscore = false;
            for (int i = 0; i < s.Length; i++) {
                if (underscore) {
                    switch (s[i]) {
                        case '_':
                            sb.Append('_');
                            break;
                        case 'D':
                            sb.Append('$');
                            break;
                        case 'P':
                            sb.Append('.');
                            break;
                        case 'H':
                            sb.Append('#');
                            break;
                        case 'O':
                            sb.Append('[');
                            break;
                        case 'C':
                            sb.Append(']');
                            break;
                        case 'S':
                            sb.Append('/');
                            break;
                        default:
                            Debug.LogWarning("Bad firebase key for decoding");
                            break;
                    }
                    underscore = false;
                } else {
                    switch (s[i]) {
                        case '_':
                            underscore = true;
                            break;
                        default:
                            sb.Append(s[i]);
                            break;
                    }
                }
            }
            return sb.ToString();
        }

Python implementation

_escape = {'&': '&&',
       '$': '&36',
       '#': '&35',
       '[': '&91',
       ']': '&93',
       '/': '&47',
       '.': '&46'}

_unescape = {e: u for u, e in _escape.items()}


def escape_firebase_key(text):
    return text.translate(str.maketrans(_escape))


def unescape_firebase_key(text):
    chunks = []
    i = 0
    while True:
        a = text[i:].find('&')
        if a == -1:
            return ''.join(chunks + [text[i:]])
        else:
            if text[i+a:i+a+2] == '&&':
                chunks.append('&')
                i += a+2
            else:
                s = text[i+a:i+a+3]
                if s in _unescape:
                    chunks.append(text[i:i+a])
                    chunks.append(_unescape[s])
                    i += a+3
                else:
                    raise RuntimeError('Cannot unescape')

And a few test cases:

test_pairs = [('&hello.', '&&hello&46'),
              ('&&&', '&&&&&&'),
              ('some@email.com', 'some@email&46com'),
              ('#$[]/.', '&35&36&91&93&47&46')]
               
for u, e in test_pairs:
    assert escape_firebase_key(u) == e, f"escaped '{u}' is '{e}', but was '{escape_firebase_key(u)}'"""
    assert unescape_firebase_key(e) == u, f"unescaped '{e}' is '{u}', but was '{unescape_firebase_key(e)}'"
    
try:
    unescape_firebase_key('&error')
    assert False, 'Must have raised an exception here'
except RuntimeError as ex:
    assert str(ex) == 'Cannot unescape'

const encodeKey = s => s.replace(/[\.\$\[\]#\/%]/g, c => '%' + c.charCodeAt(0).toString(16).toUpperCase())
const decodeKey = s => s.replace(/%(2E|24|5B|5D|23|2F|25)/g, decodeURIComponent)

console.log(encodeKey('.$[]#/%23')) // %2E%24%5B%5D%23%2F%2523
console.log(decodeKey(encodeKey('.$[]#/%23'))) // .$[]#/%23

import re
import urllib.parse

encode_key = lambda s: re.sub('[\.\$\[\]#\/%]', lambda c: f'%{ord(c.group()):X}', s)
decode_key = lambda s: re.sub('%(2E|24|5B|5D|23|2F|25)', lambda c: urllib.parse.unquote(c.group()), s)
print(encode_key('.$[]#/%23')) # %2E%24%5B%5D%23%2F%2523
print(decode_key(encode_key('.$[]#/%23'))) # .$[]#/%23
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top