public static T Path <T>(this JsonObj obj, string target, T replacement)
 {
     try
     {
         return(Path <T>(obj, target));
     }
     catch (KeyNotFoundException)
     {
         return(replacement);
     }
 }
示例#2
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);
        }
示例#3
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);
        }
        /// <summary>
        /// Returns an object of type T found in the specified path.
        /// </summary>
        /// <typeparam name="T">The type of object to return.</typeparam>
        /// <param name="obj">The JsonObj to look through.</param>
        /// <param name="target">The path to follow.</param>
        /// <returns>The object at the end of the path.</returns>
        /// <remarks>
        /// If obj is a Starbound Versioned JSON object, if the first key is not found,
        /// Path will automatically try skipping into the __content object.
        /// </remarks>
        public static T Path <T>(this JsonObj obj, string target)
        {
            if (string.IsNullOrWhiteSpace(target))
            {
                throw new ArgumentException("Path is empty.");
            }
            if (!target.StartsWith("/"))
            {
                throw new ArgumentException("Path does not start with root.");
            }
            if (target.EndsWith("/"))
            {
                throw new ArgumentException("Path does not end with a key or index.");
            }
            var    parts = target.Substring(1).Split('/');
            object root  = obj;
            var    here  = root;
            int    index;

            foreach (var part in parts)
            {
                if (part == "-")
                {
                    throw new JsonException("Can't use - here; we're not patching anything.");
                }
                bool isIndex = int.TryParse(part, out index);
                if (isIndex && here == root)
                {
                    throw new JsonException("Tried to start with an array index. That's extra special.");
                }
                if (isIndex && here is object[])
                {
                    var list = (object[])here;
                    if (index < 0 || index >= list.Length)
                    {
                        throw new JsonException("Index out of range.");
                    }
                    here = list[index];
                }
                else if (isIndex && here is List <object> )
                {
                    var list = (List <object>)here;
                    if (index < 0 || index >= list.Count)
                    {
                        throw new IndexOutOfRangeException();
                    }
                    here = list[index];
                }
                else if (here is JsonObj)
                {
                    var map = (JsonObj)here;
                    if (here == root && map.ContainsKey("__content") && !map.ContainsKey(part))
                    {
                        //Sneakily stealthily skip into this.
                        map = (JsonObj)map["__content"];
                    }
                    if (!map.ContainsKey(part))
                    {
                        throw new KeyNotFoundException();
                    }
                    here = map[part];
                }
                else
                {
                    throw new JsonException("Current node is not an array or object, but path isn't done yet.");
                }
            }

            if (typeof(T).Name == "Int32" && here is Int64)
            {
                here = (int)(Int64)here;
            }
            if (typeof(T).Name == "Boolean" && here is bool)
            {
                here = (bool)here;
            }
            else if (typeof(T).Name == "Int32[]" && here is List <object> )
            {
                here = ((List <object>)here).Select(x => (int)(Int64)x).ToArray();
            }
            else if (typeof(T).Name == "Int64[]" && here is List <object> )
            {
                here = ((List <object>)here).Select(x => (Int64)x).ToArray();
            }
            else if (typeof(T).Name == "Boolean[]" && here is List <object> )
            {
                here = ((List <object>)here).Select(x => (bool)x).ToArray();
            }
            else if (typeof(T).Name == "String[]" && here is List <object> )
            {
                here = ((List <object>)here).Select(x => (string)x).ToArray();
            }
            else if (typeof(T).Name == "JsonObj[]" && here is List <object> )
            {
                here = ((List <object>)here).Cast <JsonObj>().ToArray();
            }
            else if (typeof(T).Name == "Object[]" && here is List <object> )
            {
                here = ((List <object>)here).ToArray();
            }
            else if (typeof(T).Name == "List`1")
            {
                var contained = typeof(T).GetGenericArguments()[0];
                var hereList  = (List <object>)here;
                switch (contained.Name)
                {
                case "Int32":
                    here = hereList.Select(x => (int)(Int64)x).ToList();
                    break;

                case "Boolean":
                    here = hereList.Select(x => (bool)x).ToList();
                    break;

                case "String":
                    here = hereList.Select(x => (string)x).ToList();
                    break;

                case "JsonObj":
                    here = hereList.Select(x => (JsonObj)x).ToList();
                    break;

                default:
                    here = hereList;
                    break;
                }
            }

            if (!(here is T))
            {
                throw new JsonException(string.Format("Value at end of path is not of the requested type -- found {0} but expected {1}.", here.GetType(), typeof(T)));
            }
            return((T)here);
        }
 /// <summary>
 /// Returns the JsonObj found in the specified path.
 /// </summary>
 /// <param name="obj">The JsonObj to look through.</param>
 /// <param name="target">The path to follow.</param>
 /// <returns>The JsonObj at the end of the path.</returns>
 /// <remarks>
 /// If obj is a Starbound Versioned JSON object, if the first key is not found,
 /// Path will automatically try skipping into the __content object.
 /// </remarks>
 public static JsonObj Path(this JsonObj obj, string target)
 {
     return(Path <JsonObj>(obj, target));
 }
示例#6
0
 /// <summary>
 /// Returns the JsonObj found in the specified path.
 /// </summary>
 /// <param name="obj">The JsonObj to look through.</param>
 /// <param name="path">The path to follow.</param>
 /// <returns>The JsonObj at the end of the path.</returns>
 /// <remarks>
 /// If obj is a Starbound Versioned JSON object, if the first key is not found,
 /// Path will automatically try skipping into the __content object.
 /// </remarks>
 public static JsonObj Path(this JsonObj obj, string path)
 {
     return(Path <JsonObj>(obj, path));
 }
        /// <summary>
        /// Parses Starbound Versioned JSON from a stream into an object. The input stream can be headered or bare. If the input stream is headered, the resulting object will be wrapped to preserve versioning information.
        /// </summary>
        /// <param name="stream">The input stream to parse from.</param>
        /// <returns>A JsonObj, list, string, double...</returns>
        public static object Deserialize(BinaryReader stream)
        {
            Func <object> something = null;            //placeholder for @array and @object

            Func <double> @double = () =>
            {
                var moto8  = stream.ReadBytes(8);
                var intel8 = new[] { moto8[7], moto8[6], moto8[5], moto8[4], moto8[3], moto8[2], moto8[1], moto8[0] };
                var ret    = 0.0;
                using (var intel = new BinaryReader(new MemoryStream(intel8)))
                    ret = intel.ReadDouble();
                return(ret);
            };

            Func <bool> @bool = () =>
            {
                return(stream.ReadBoolean());
            };

            Func <long> @int = () =>
            {
                return(stream.ReadVLQSigned());
            };

            Func <string> @string = () =>
            {
                var len = (int)stream.ReadVLQUnsigned();
                var str = Encoding.UTF8.GetString(stream.ReadBytes(len));
                return(str);
            };

            Func <object> @array = () =>
            {
                var count = (int)stream.ReadVLQUnsigned();
                var ret   = new object[count];
                for (var i = 0; i < count; i++)
                {
                    ret[i] = @something();
                }

                var allAreNull = ret.All(i => i == null);
                if (!allAreNull)
                {
                    var allAreSameType = true;
                    foreach (var i in ret)
                    {
                        if (i == null)
                        {
                            continue;
                        }
                        if (!(i is string))
                        {
                            allAreSameType = false;
                            break;
                        }
                    }
                    if (allAreSameType)
                    {
                        return(ret.Cast <string>().ToList());
                    }

                    allAreSameType = true;
                    foreach (var i in ret)
                    {
                        if (i == null)
                        {
                            continue;
                        }
                        if (!(i is int))
                        {
                            allAreSameType = false;
                            break;
                        }
                    }
                    if (allAreSameType)
                    {
                        return(ret.Cast <int>().ToList());
                    }

                    allAreSameType = true;
                    foreach (var i in ret)
                    {
                        if (i == null)
                        {
                            continue;
                        }
                        if (!(i is double))
                        {
                            allAreSameType = false;
                            break;
                        }
                    }
                    if (allAreSameType)
                    {
                        return(ret.Cast <double>().ToList());
                    }
                }

                return(ret.ToList());
            };

            Func <JsonObj> @object = () =>
            {
                var count = (int)stream.ReadVLQUnsigned();
                var ret   = new JsonObj();
                for (var i = 0; i < count; i++)
                {
                    ret.Add(@string(), @something());
                }
                return(ret);
            };

            something = () =>
            {
                var type = stream.ReadByte();
                switch (type)
                {
                case 1: return(null);

                case 2: return(@double());

                case 3: return(@bool());

                case 4: return(@int());

                case 5: return(@string());

                case 6: return(@array());

                case 7: return(@object());

                default: throw new JsonException(string.Format("Unknown item type 0x{0:X2} while deserializing. Stream offset: 0x{1:X}", type, stream.BaseStream.Position));
                }
            };

            if ((char)stream.PeekChar() == 'S')
            {
                //SBVJ file?
                var sbvj = new string(stream.ReadChars(6));
                if (sbvj != "SBVJ01")
                {
                    throw new JsonException("File does not start with a valid object identifier, nor is it a Starbound Versioned JSON file.");
                }
                var identifier = @string();
                var hasVersion = @bool();
                var version    = 0;
                if (hasVersion)
                {
                    var moto4  = stream.ReadBytes(4);
                    var intel4 = new[] { moto4[3], moto4[2], moto4[1], moto4[0] };
                    using (var intel = new BinaryReader(new MemoryStream(intel4)))
                    {
                        version = intel.ReadInt32();
                    }
                }
                var wrapper = new JsonObj();
                wrapper["__id"] = identifier;
                if (hasVersion)
                {
                    wrapper["__version"] = version;
                }
                wrapper["__content"] = something();
                return(wrapper);
            }

            return(something());
        }