Ejemplo n.º 1
0
        /// <summary>
        /// Parses an input string into an object. The input can be any well-formed JSON or JSON5.
        /// </summary>
        /// <param name="text">The string to parse.</param>
        /// <returns>A JsonObj, list, string, double...</returns>
        public static object Parse(string text)
        {
            var previousCulture = System.Threading.Thread.CurrentThread.CurrentCulture;

            System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;

            var at      = 0;        //The index of the current character
            var ch      = ' ';      //The current character
            var escapee = new Dictionary <char, string>
            {
                { '\'', "\'" },
                { '\"', "\"" },
                { '\\', "\\" },
                { '/', "/" },
                { '\n', string.Empty },                 //Replace escaped newlines in strings w/ empty string
                { 'b', "\b" },
                { 'f', "\f" },
                { 'n', "\n" },
                { 'r', "\r" },
                { 't', "\t" },
            };
            Func <int> locateError = () =>
            {
                var line = 0;
                for (var i = 0; i < at; i++)
                {
                    if (text[i] == '\r')
                    {
                        line++;
                    }
                }
                return(line);
            };
            Func <char> next = () =>
            {
                if (at >= text.Length)
                {
                    ch = '\0';
                }
                else
                {
                    ch = text[at];
                    at++;
                }
                return(ch);
            };
            Func <char, char> expect = (c) =>
            {
                if (c != ch)
                {
                    throw new JsonException(string.Format("Expected '{1}' instead of '{0}'", ch, c), locateError());
                }
                return(next());
            };

            #region identifier
            Func <string> identifier = () =>
            {
                var key = new StringBuilder();
                key.Append(ch);
                //Identifiers must start with a letter, _ or $.
                if ((ch != '_' && ch != '$') &&
                    (ch < 'a' || ch > 'z') &&
                    (ch < 'A' || ch > 'Z'))
                {
                    throw new JsonException("Bad identifier", locateError());
                }
                //Subsequent characters can contain digits.
                while (next() != '\0' && (
                           ch == '_' || ch == '$' ||
                           (ch >= 'a' && ch <= 'z') ||
                           (ch >= 'A' && ch <= 'Z') ||
                           (ch >= '0' && ch <= '9')))
                {
                    key.Append(ch);
                }
                return(key.ToString());
            };
            #endregion
            #region number
            Func <object> number = () =>
            {
                var sign = '\0';
                var str  = new StringBuilder();
                var bas  = 10;
                if (ch == '-' || ch == '+')
                {
                    sign = ch;
                    str.Append(ch);
                    expect(ch);
                }
                if (ch == 'I')
                {
                    expect('I');
                    expect('n');
                    expect('f');
                    expect('i');
                    expect('n');
                    expect('i');
                    expect('t');
                    expect('y');
                    if (!AllowNaN)
                    {
                        throw new JsonException("Found an unallowed Infinity value.");
                    }
                    return((sign == '-') ? double.NegativeInfinity : double.PositiveInfinity);
                }
                if (ch == '0')
                {
                    str.Append(ch);
                    next();
                    if (ch == 'x' || ch == 'X')
                    {
                        str.Append(ch);
                        next();
                        bas = 16;
                    }
                    else if (ch >= '0' && ch <= '9')
                    {
                        throw new JsonException("Octal literal", locateError());
                    }
                }

                //https://github.com/aseemk/json5/issues/36
                if (bas == 16 && sign != '\0')
                {
                    throw new JsonException("Signed hexadecimal literal", locateError());
                }

                switch (bas)
                {
                case 10:
                    while (ch >= '0' && ch <= '9')
                    {
                        str.Append(ch);
                        next();
                    }
                    if (ch == '.')
                    {
                        if (str.Length == 0)
                        {
                            str.Append('0');
                        }
                        str.Append(ch);
                        while (next() != '\0' && ch >= '0' && ch <= '9')
                        {
                            str.Append(ch);
                        }
                    }
                    if (ch == 'e' || ch == 'E')
                    {
                        str.Append(ch);
                        next();
                        if (ch == '-' || ch == '+')
                        {
                            str.Append(ch);
                            next();
                        }
                        while (ch >= '0' && ch <= '9')
                        {
                            str.Append(ch);
                            next();
                        }
                    }
                    break;

                case 16:
                    while (ch >= '0' && ch <= '9' || ch >= 'A' && ch <= 'F' || ch >= 'a' && ch <= 'f')
                    {
                        str.Append(ch);
                        next();
                    }
                    break;

                default:
                    throw new JsonException("Invalid number base, somehow.");
                }
                if (bas == 16)
                {
                    return(int.Parse(str.ToString().Substring(2), System.Globalization.NumberStyles.HexNumber));
                }
                try
                {
                    return(Double.Parse(str.ToString()));
                }
                catch (OverflowException)
                {
                    return((sign == '-') ? Double.MinValue : Double.MaxValue);
                }
            };
            #endregion
            #region string
            Func <string> @string = () =>
            {
                var  hex = 0;
                var  i   = 0;
                var  str = new StringBuilder();
                char delim;
                var  uffff = 0;
                //When parsing for string values, we must look for ' or " and \ characters.
                if (ch == '"' || ch == '\'')
                {
                    delim = ch;
                    while (next() != '\0')
                    {
                        if (ch == delim)
                        {
                            next();
                            return(str.ToString());
                        }
                        else if (ch == '\\')
                        {
                            next();
                            if (ch == '\r' || ch == '\n')
                            {
                                str.Append('\n');
                                next();
                                continue;
                            }
                            if (ch == 'u')
                            {
                                uffff = 0;
                                for (i = 0; i < 4; i += 1)
                                {
                                    hex   = int.Parse(next().ToString(), System.Globalization.NumberStyles.HexNumber);
                                    uffff = uffff * 16 + hex;
                                }
                                str.Append((char)uffff);
                            }
                            else if (escapee.ContainsKey(ch))
                            {
                                str.Append(escapee[ch]);
                            }
                            else
                            {
                                break;
                            }
                        }
                        else
                        {
                            str.Append(ch);
                        }
                    }
                }
                throw new JsonException("Bad string", locateError());
            };
            #endregion
            #region inlineComment
            Func <string> inlineComment = () =>
            {
                //Skip an inline comment, assuming this is one. The current character should
                //be the second / character in the // pair that begins this inline comment.
                //To finish the inline comment, we look for a newline or the end of the text.
                if (ch != '/')
                {
                    throw new JsonException("Not an inline comment");
                }
                do
                {
                    next();
                    if (ch == '\n')
                    {
                        expect('\n');
                        return(string.Empty);
                    }
                } while (ch != '\0');
                return(string.Empty);
            };
            #endregion
            #region blockComment
            Func <string> blockComment = () =>
            {
                //Skip a block comment, assuming this is one. The current character should be
                //the * character in the /* pair that begins this block comment.
                //To finish the block comment, we look for an ending */ pair of characters,
                //but we also watch for the end of text before the comment is terminated.
                if (ch != '*')
                {
                    throw new JsonException("Not a block comment");
                }
                do
                {
                    next();
                    while (ch == '*')
                    {
                        expect('*');
                        if (ch == '/')
                        {
                            expect('/');
                            return(string.Empty);
                        }
                    }
                } while (ch != '\0');
                throw new JsonException("Unterminated block comment");
            };
            #endregion
            #region comment
            Func <string> comment = () =>
            {
                if (ch != '/')
                {
                    throw new JsonException("Not a comment", locateError());
                }
                expect('/');
                if (ch == '/')
                {
                    inlineComment();
                }
                else if (ch == '*')
                {
                    blockComment();
                }
                else
                {
                    throw new JsonException("Unrecognized comment", locateError());
                }
                return(string.Empty);
            };
            #endregion
            #region white
            Func <string> white = () =>
            {
                //Skip whitespace and comments.
                //Note that we're detecting comments by only a single / character.
                //This works since regular expressions are not valid JSON(5), but this will
                //break if there are other valid values that begin with a / character!
                while (ch != '\0')
                {
                    if (ch == '/')
                    {
                        comment();
                    }
                    else if (ch <= ' ')
                    {
                        next();
                    }
                    else
                    {
                        return(string.Empty);
                    }
                }
                return(string.Empty);
            };
            #endregion
            #region word
            Func <bool?> word = () =>
            {
                //true, false, or null.
                switch (ch)
                {
                case 't':
                    expect('t');
                    expect('r');
                    expect('u');
                    expect('e');
                    return(true);

                case 'f':
                    expect('f');
                    expect('a');
                    expect('l');
                    expect('s');
                    expect('e');
                    return(false);

                case 'n':
                    expect('n');
                    expect('u');
                    expect('l');
                    expect('l');
                    return(null);

                default:
                    throw new JsonException(string.Format("Unexpected '{0}'", ch), locateError());
                }
            };
            #endregion
            Func <object> value = null;            //Place holder for the value function.
            #region array
            Func <List <object> > @array = () =>
            {
                var justHadComma = false;
                var arr          = new List <object>();
                if (ch == '[')
                {
                    expect('[');
                    white();
                    while (ch != '\0')
                    {
                        if (ch == ']')
                        {
                            if (!AllowTrailingComma && justHadComma)
                            {
                                throw new JsonException("Superfluous trailing comma", locateError());
                            }
                            expect(ch);
                            return(arr);                            //.ToArray(); //Potentially empty array
                        }
                        //ES5 allows omitting elements in arrays, e.g. [,] and
                        //[,null]. We don't allow this in JSON5.
                        if (ch == ',')
                        {
                            throw new JsonException("Missing array element", locateError());
                        }
                        else
                        {
                            arr.Add(value());
                        }
                        white();
                        //If there's no comma after this value, this needs to
                        //be the end of the array.
                        if (ch != ',')
                        {
                            expect(']');
                            return(arr);
                        }
                        expect(',');
                        justHadComma = true;
                        white();
                    }
                }
                throw new JsonException("Bad array", locateError());
            };
            #endregion
            #region object
            Func <JsonObj> @object = () =>
            {
                //Parse an object value.
                var key = string.Empty;
                var obj = new JsonObj();
                if (ch == '{')
                {
                    expect('{');
                    white();
                    while (ch != '\0')
                    {
                        if (ch == '}')
                        {
                            expect('}');
                            return(obj);                            //Potentially empty object
                        }
                        //Keys can be unquoted. If they are, they need to be
                        //valid JS identifiers.
                        if (ch == '\"' || ch == '\'')
                        {
                            key = @string();
                        }
                        else
                        {
                            key = identifier();
                        }
                        white();
                        expect(':');
                        if (obj.ContainsKey(key))
                        {
                            throw new JsonException(string.Format("Duplicate key \"{0}\"", key), locateError());
                        }
                        obj[key] = value();
                        white();
                        //If there's no comma after this pair, this needs to be
                        //the end of the object.
                        if (ch != ',')
                        {
                            expect('}');
                            return(obj);
                        }
                        expect(',');
                        white();
                    }
                }
                throw new JsonException("Bad object", locateError());
            };
            #endregion
            #region value
            value = () =>
            {
                //Parse a JSON value. It could be an object, an array, a string, a number,
                //or a word.
                white();
                switch (ch)
                {
                case '{':
                    return(@object());

                case '[':
                    return(@array());

                case '\"':
                case '\'':
                    return(@string());

                case '-':
                case '+':
                case '.':
                    return(number());

                case 'N':
                    expect('N');
                    expect('a');
                    expect('N');
                    if (!AllowNaN)
                    {
                        throw new JsonException("Found an unallowed NaN value.");
                    }
                    return(double.NaN);

                case 'I':
                    expect('I');
                    expect('n');
                    expect('f');
                    expect('i');
                    expect('n');
                    expect('i');
                    expect('t');
                    expect('y');
                    if (!AllowNaN)
                    {
                        throw new JsonException("Found an unallowed Infinity value.");
                    }
                    return(double.PositiveInfinity);

                default:
                    return(ch >= '0' && ch <= '9' ? @number() : word());
                }
            };
            #endregion

            //wow.
            //much cheat.
            //so unexpected.
            //-- KAWA
            white();
            object ret = null;
            if (ch == '\0')
            {
                //Ret is null and stays null.
            }
            else if (ch == '[')
            {
                ret = @array();
            }
            else if (ch == '{')
            {
                ret = @object();
            }
            else
            {
                ret = value();
            }

            System.Threading.Thread.CurrentThread.CurrentCulture = previousCulture;
            return(ret);
        }
Ejemplo n.º 2
0
        public static bool IsValid(JsonObj data, JsonObj schema, JsonObj node = null)
        {
            if (node == null)
            {
                SchemaError = SchemaErrors.None;
                if (!schema["type"].Equals("object"))
                {
                    throw new JsonException("Can only validate objects.");
                }
                return(IsValid(data, schema, schema));
            }
            var properties = node["properties"] as JsonObj;

            if (node.ContainsKey("required"))
            {
                foreach (var requirement in (node["required"] as List <object>).Select(i => i.ToString()))
                {
                    if (!data.ContainsKey(requirement))
                    {
                        SchemaError = SchemaErrors.MissingRequirement;
                        return(false);
                    }
                }
            }
            foreach (var property in properties)
            {
                var key   = property.Key;
                var value = property.Value as JsonObj;
                var type  = value["type"] as string;
                if (data.ContainsKey(key))
                {
                    var item = data[key];
                    if (!typesMatch(type, item))
                    {
                        SchemaError = SchemaErrors.TypeMismatch;
                        return(false);
                    }
                    if (type == "object")
                    {
                        if (!IsValid(item as JsonObj, schema, value))
                        {
                            return(false);
                        }
                    }
                    if (type == "array")
                    {
                        var array = (IEnumerable <object>)item;
                        if (value.ContainsKey("items"))
                        {
                            var items    = value["items"] as JsonObj;
                            var itemType = items["type"] as string;
                            foreach (var thing in array)
                            {
                                if (!typesMatch(itemType, thing))
                                {
                                    SchemaError = SchemaErrors.TypeMismatch;
                                    return(false);
                                }
                            }
                        }
                        if (value.ContainsKey("maxItems") && array.Count() >= Convert.ToInt64(value["maxItems"]))
                        {
                            return(false);
                        }
                        if (value.ContainsKey("minItems") && array.Count() < Convert.ToInt64(value["minItems"]))
                        {
                            return(false);
                        }
                        if (value.ContainsKey("uniqueItems") && Convert.ToBoolean(value["uniqueItems"]))
                        {
                            var distinct = array.Distinct();
                            if (array.Count() != distinct.Count())
                            {
                                return(false);
                            }
                        }
                    }
                    if (type == "string")
                    {
                        if (value.ContainsKey("pattern") && !Regex.IsMatch(item as string, value["pattern"] as string, RegexOptions.IgnoreCase))
                        {
                            SchemaError = SchemaErrors.PatternMismatch;
                            return(false);
                        }
                        if (value.ContainsKey("maxLength") && (item as string).Length > Convert.ToInt64(value["maxLength"]))
                        {
                            return(false);
                        }
                        if (value.ContainsKey("minLength") && (item as string).Length < Convert.ToInt64(value["minLength"]))
                        {
                            return(false);
                        }
                    }
                    if (type == "integer" || type == "number")
                    {
                        var lastError = SchemaError;
                        SchemaError = SchemaErrors.NumberMismatch;
                        var val = Convert.ToDouble(item);
                        if (value.ContainsKey("multipleOf") && val % Convert.ToDouble(value["multipleOf"]) != 0)
                        {
                            return(false);
                        }
                        if (value.ContainsKey("maximum"))
                        {
                            var max = Convert.ToDouble(value["maximum"]);
                            if (value.ContainsKey("exclusiveMaximum") && Convert.ToBoolean(value["exclusiveMaximum"]) && val >= max)
                            {
                                return(false);
                            }
                            else if (val > max)
                            {
                                return(false);
                            }
                        }
                        if (value.ContainsKey("minimum"))
                        {
                            var min = Convert.ToDouble(value["minimum"]);
                            if (value.ContainsKey("exclusiveMinimum") && Convert.ToBoolean(value["exclusiveMinimum"]) && val <= min)
                            {
                                return(false);
                            }
                            else if (val < min)
                            {
                                return(false);
                            }
                        }
                        SchemaError = lastError;
                    }
                }
            }
            return(true);
        }