예제 #1
0
        /// <summary>
        /// Returns the <see cref="JsonPlusObjectMember"/> associated with the <see cref="JsonPlusPath"/> specified.
        /// </summary>
        /// <param name="path">The path to the member to return.</param>
        /// <param name="result">If the member is returned successfully, this parameter will contain the result <see cref="JsonPlusObjectMember"/>. This parameter should be passed uninitialized.</param>
        /// <returns>`true` if the <see cref="JsonPlusObject"/> contains the <see cref="JsonPlusObjectMember"/> associated with <paramref name="path"/>. Otherwise, `false`.</returns>
        public bool TryGetMember(JsonPlusPath path, out JsonPlusObjectMember result)
        {
            result = null;
            if (path == null || path.Count == 0)
            {
                return(false);
            }

            int            pathIndex     = 0;
            JsonPlusObject currentObject = this;

            while (true)
            {
                string key = path[pathIndex];

                if (!currentObject.TryGetValue(key, out var field))
                {
                    return(false);
                }

                if (pathIndex >= path.Count - 1)
                {
                    result = field;
                    return(true);
                }

                if (field.Type != JsonPlusType.Object)
                {
                    return(false);
                }

                currentObject = field.GetObject();
                pathIndex     = pathIndex + 1;
            }
        }
예제 #2
0
        private JsonPlusValue ResolveSubstitution(JsonPlusSubstitution sub)
        {
            JsonPlusObjectMember subField = sub.ParentMember;

            // first case, this substitution is a direct self-reference
            if (sub.Path == sub.GetMemberPath())
            {
                IJsonPlusNode parent = sub.Parent;
                while (parent is JsonPlusValue)
                {
                    parent = parent.Parent;
                }

                // Fail case
                if (parent is JsonPlusArray)
                {
                    throw new JsonPlusException(RS.SelfRefSubstitutionInArray);
                }

                // try to resolve substitution by looking backward in the field assignment stack
                return(subField.OlderValueThan(sub));
            }

            // need to recursively get full path
            JsonPlusPath fieldPath = subField.GetMemberPath();

            // second case, the substitution references a field child in the past
            if (sub.Path.IsChildPathOf(fieldPath))
            {
                JsonPlusValue olderValue = subField.OlderValueThan(sub);
                if ((olderValue != null) &&
                    (olderValue.Type == JsonPlusType.Object))
                {
                    int          difLength = sub.Path.Count - fieldPath.Count;
                    JsonPlusPath deltaPath = sub.Path.SubPath(sub.Path.Count - difLength, difLength);

                    JsonPlusObject olderObject = olderValue.GetObject();
                    if (olderObject.TryGetValue(deltaPath, out JsonPlusValue innerValue))
                    {
                        return(innerValue.Type == JsonPlusType.Object ? innerValue : null);
                    }
                }
            }

            // Detect invalid parent-referencing substitution
            if (fieldPath.IsChildPathOf(sub.Path))
            {
                throw new JsonPlusException(RS.SubstitutionRefDirectParentError);
            }

            // Detect invalid cyclic reference loop
            if (IsValueCyclic(subField, sub))
            {
                throw new JsonPlusException(RS.CyclicSubstitutionLoop);
            }

            // third case, regular substitution
            _root.GetObject().TryGetValue(sub.Path, out JsonPlusValue field);
            return(field);
        }
예제 #3
0
 /// <summary>
 /// Initializes a new instance of the <see cref="JsonPlusSubstitution"/> class.
 /// </summary>
 /// <param name="parent">The <see cref="JsonPlusValue"/> parent of this substitution.</param>
 /// <param name="path">The <see cref="JsonPlusPath"/> that this substitution is pointing to.</param>
 /// <param name="required">Indicates whether this is a lazy substitution. Lazy substitutions uses the `${?` notation.</param>
 /// <param name="location">The location of this substitution token in the source code, used for exception generation purposes.</param>
 internal JsonPlusSubstitution(IJsonPlusNode parent, JsonPlusPath path, ISourceLocation location, bool required)
 {
     Parent   = parent ?? throw new ArgumentNullException(nameof(parent), RS.CannotSubstitutionRootNode);
     Column   = location.Column;
     Line     = location.Line;
     Required = required;
     Path     = path;
 }
예제 #4
0
        private IJsonPlusNode ParseSelfAssignArray(IJsonPlusNode owner)
        {
            // sanity check
            if (_tokens.Current.Type != TokenType.SelfAssignment)
            {
                throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenInArray, TokenType.SelfAssignment, _tokens.Current.Type));
            }

            // consume += operator token
            ConsumeWhitelines();

            JsonPlusArray currentArray = new JsonPlusArray(owner);

            switch (_tokens.Current.Type)
            {
            case TokenType.Include:
            case TokenType.OptionalInclude:
                // #todo
                currentArray.Add(ParseInclude(currentArray));
                break;

            case TokenType.StartOfArray:
                return(ParseArray(owner));

            case TokenType.StartOfObject:
                // #todo
                currentArray.Add(ParseObject(currentArray));
                break;

            case TokenType.LiteralValue:
                if (_tokens.Current.IsNonSignificant())
                {
                    ConsumeWhitelines();
                }
                if (_tokens.Current.Type != TokenType.LiteralValue)
                {
                    break;
                }

                currentArray.Add(ParseValue(currentArray));
                return(currentArray);

            case TokenType.OptionalSubstitution:
            case TokenType.Substitution:
                JsonPlusPath         pointerPath = JsonPlusPath.Parse(_tokens.Current.Value);
                JsonPlusSubstitution sub         = new JsonPlusSubstitution(owner, pointerPath, _tokens.Current,
                                                                            _tokens.Current.Type == TokenType.Substitution);
                _substitutions.Add(sub);
                _tokens.Next();
                return(sub);

            default:
                throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenInArray, TokenType.EndOfArray, _tokens.Current.Type));
            }

            return(currentArray);
        }
예제 #5
0
        /// <summary>
        /// Returns a node in the Json+ tree using Json+ path query expression.
        /// </summary>
        /// <param name="path">The Json+ path query expression.</param>
        /// <returns>The value of a node selected by <paramref name="path"/>.</returns>
        protected virtual JsonPlusValue GetNode(JsonPlusPath path)
        {
            if (Value.Type != JsonPlusType.Object)
            {
                throw new JsonPlusException(RS.RootNotAnObject);
            }

            return(Value.GetObject().GetValue(path));
        }
예제 #6
0
        /// <summary>
        /// Returns a node as a <see cref="Nullable{Int64}"/> object by parsing the value as a number with data size unit.
        /// </summary>
        /// <param name="path">A Json+ query path that identifies a node in the current context.</param>
        /// <returns>The <see cref="Nullable{Int64}"/> value of the node specified by <paramref name="path"/>.</returns>
        /// <see cref="JsonPlusValue.GetByteSize()"/>
        public long?GetByteSize(JsonPlusPath path)
        {
            JsonPlusValue value = GetNode(path);

            if (ReferenceEquals(value, JsonPlusValue.Undefined))
            {
                return(null);
            }
            return(value.GetByteSize());
        }
예제 #7
0
        /// <summary>
        /// Returns the value associated with the supplied key. If the supplied key is not found, one is created with a blank value.
        /// </summary>
        /// <param name="path">The path associated with the value.</param>
        /// <returns>The value associated with <paramref name="path"/>.</returns>
        private JsonPlusObjectMember GetOrCreateKey(JsonPlusPath path)
        {
            if (TryGetValue(path.Key, out JsonPlusObjectMember child))
            {
                return(child);
            }

            child = new JsonPlusObjectMember(path, this);
            Add(path.Key, child);
            return(child);
        }
예제 #8
0
        /// <summary>
        /// Initializes a new instance of the <see cref="JsonPlusObjectMember"/> class.
        /// </summary>
        /// <param name="path">The path to this member in the data tree.</param>
        /// <param name="parent">The parent container of this object instance.</param>
        public JsonPlusObjectMember(JsonPlusPath path, JsonPlusObject parent)
        {
            if (path == null)
            {
                throw new ArgumentNullException(nameof(path));
            }

            Path            = new JsonPlusPath(path);
            Parent          = parent;
            _internalValues = new List <JsonPlusValue>();
        }
예제 #9
0
        /// <summary>
        /// Returns a node as an enumerable collection <see cref="string"/>.
        /// </summary>
        /// <param name="path">A Json+ query path that identifies a node in the current context.</param>
        /// <exception cref="JsonPlusParserException">The node cannot be casted into an array.</exception>
        /// <returns>The <see cref="IList{String}"/> value of the node specified by <paramref name="path"/>.</returns>
        public IList <string> GetStringList(JsonPlusPath path)
        {
            JsonPlusValue value = GetNode(path);

            if (ReferenceEquals(value, JsonPlusValue.Undefined))
            {
                throw new JsonPlusParserException(string.Format(RS.ErrCastNodeByPathToArray, path));
            }

            return(value.GetStringList());
        }
예제 #10
0
        /// <summary>
        /// Returns a node as a <see cref="TimeSpan"/>.
        /// </summary>
        /// <param name="path">A Json+ query path that identifies a node in the current context.</param>
        /// <param name="defaultValue">The default value to return if the node specified by <paramref name="path"/> does not exist. Defaults to `null`.</param>
        /// <param name="allowInfinite">Set to `true` to allow the keyword `infinite`. Otherwise, `false`. Defaults to `true`.</param>
        /// <returns>The <see cref="TimeSpan"/> value of the node specified by <paramref name="path"/>, or <paramref name="defaultValue"/> if the node does not exist.</returns>
        public TimeSpan GetTimeSpan(JsonPlusPath path, TimeSpan?defaultValue = null, bool allowInfinite = true)
        {
            JsonPlusValue value = GetNode(path);

            if (value == null)
            {
                return(defaultValue.GetValueOrDefault());
            }

            return(value.GetTimeSpan(allowInfinite));
        }
예제 #11
0
        /// <summary>
        /// Returns a node as an <see cref="long"/>.
        /// </summary>
        /// <param name="path">A Json+ query path that identifies a node in the current context.</param>
        /// <param name="defaultValue">The default value to return if the node specified by <paramref name="path"/> does not exist. Defaults to zero.</param>
        /// <returns>The <see cref="Int64"/> value of the node specified by <paramref name="path"/>, or <paramref name="defaultValue"/> if the node does not exist.</returns>
        public long GetInt64(JsonPlusPath path, long defaultValue = 0)
        {
            JsonPlusValue value = GetNode(path);

            if (ReferenceEquals(value, JsonPlusValue.Undefined))
            {
                return(defaultValue);
            }

            return(value.GetInt64());
        }
예제 #12
0
        /// <summary>
        /// Returns a node as a <see cref="double"/>.
        /// </summary>
        /// <param name="path">A Json+ query path that identifies a node in the current context.</param>
        /// <param name="defaultValue">The default value to return if the node specified by <paramref name="path"/> does not exist. Defaults to zero.</param>
        /// <returns>The <see cref="double"/> value of the node specified by <paramref name="path"/>, or <paramref name="defaultValue"/> if the node does not exist.</returns>
        public double GetDouble(JsonPlusPath path, double defaultValue = 0)
        {
            JsonPlusValue value = GetNode(path);

            if (ReferenceEquals(value, JsonPlusValue.Undefined))
            {
                return(defaultValue);
            }

            return(value.GetDouble());
        }
예제 #13
0
        /// <summary>
        /// Returns a node as a <see cref="float"/>.
        /// </summary>
        /// <param name="path">A Json+ query path that identifies a node in the current context.</param>
        /// <param name="defaultValue">The default value to return if the node specified by <paramref name="path"/> does not exist. Defaults to zero.</param>
        /// <returns>The <see cref="float"/> value of the node specified by <paramref name="path"/>, or <paramref name="defaultValue"/> if the node does not exist.</returns>
        public float GetSingle(JsonPlusPath path, float defaultValue = 0)
        {
            JsonPlusValue value = GetNode(path);

            if (ReferenceEquals(value, JsonPlusValue.Undefined))
            {
                return(defaultValue);
            }

            return(value.GetSingle());
        }
예제 #14
0
        /// <summary>
        /// Return a node as a <see cref="bool"/>.
        /// </summary>
        /// <param name="path">A Json+ query path that identifies a node in the current context.</param>
        /// <param name="defaultValue">The default value to return if the node specified by <paramref name="path"/> does not exist.</param>
        /// <returns>The <see cref="bool"/> value of the node specified by <paramref name="path"/>, or <paramref name="defaultValue"/> if the node does not exist.</returns>
        public bool GetBoolean(JsonPlusPath path, bool defaultValue = false)
        {
            JsonPlusValue value = GetNode(path);

            if (ReferenceEquals(value, JsonPlusValue.Undefined))
            {
                return(defaultValue);
            }

            return(value.GetBoolean());
        }
예제 #15
0
        /// <summary>
        /// Returns a node as an <see cref="int"/>.
        /// </summary>
        /// <param name="path">A Json+ query path that identifies a node in the current context.</param>
        /// <param name="defaultValue">The default value to return if the node specified by <paramref name="path"/> does not exist. Defaults to zero.</param>
        /// <returns>The <see cref="Int32"/> value of the node specified by <paramref name="path"/>, or <paramref name="defaultValue"/> if the node does not exist.</returns>
        public int GetInt32(JsonPlusPath path, int defaultValue = 0)
        {
            JsonPlusValue value = GetNode(path);

            if (ReferenceEquals(value, JsonPlusValue.Undefined))
            {
                return(defaultValue);
            }

            return(value.GetInt32());
        }
예제 #16
0
        /// <summary>
        /// Returns a node as a <see cref="decimal"/>.
        /// </summary>
        /// <param name="path">A Json+ query path that identifies a node in the current context.</param>
        /// <param name="defaultValue">The default value to return if the node specified by <paramref name="path"/> does not exist. Defaults to zero.</param>
        /// <returns>The <see cref="decimal"/> value of the node specified by <paramref name="path"/>, or <paramref name="defaultValue"/> if the node does not exist.</returns>
        public decimal GetDecimal(JsonPlusPath path, decimal defaultValue = 0)
        {
            JsonPlusValue value = GetNode(path);

            if (ReferenceEquals(value, JsonPlusValue.Undefined))
            {
                return(defaultValue);
            }

            return(value.GetDecimal());
        }
예제 #17
0
        /// <summary>
        /// Returns the backing <see cref="JsonPlusValue"/> value of the <see cref="JsonPlusObjectMember"/> associated with
        /// the <see cref="JsonPlusPath"/> specified. The path is relative to the this instance.
        /// </summary>
        /// <param name="path">The relative <see cref="JsonPlusPath"/> path associated with the <see cref="JsonPlusObjectMember"/> of the <see cref="JsonPlusValue"/>.</param>
        /// <param name="result">If the field is returned successfully, this parameter will contain the backing <see cref="JsonPlusValue"/> of the <see cref="JsonPlusObjectMember"/> associated
        /// with <paramref name="path"/> (if the path is resolvable). This parameter should be passed uninitialized.</param>
        /// <returns>`true` if the <see cref="JsonPlusObject"/> contains the <see cref="JsonPlusObjectMember"/> resolvable by the <paramref name="path"/> specified. Otherwise, `false`.</returns>
        public bool TryGetValue(JsonPlusPath path, out JsonPlusValue result)
        {
            result = null;

            if (!TryGetMember(path, out JsonPlusObjectMember field))
            {
                return(false);
            }

            result = field.Value;
            return(true);
        }
예제 #18
0
        /// <summary>
        /// Determine whether a node exists at the specified path.
        /// </summary>
        /// <param name="path">A Json+ query path that specifies a node.</param>
        /// <returns>`true` if a node exists at the <paramref name="path"/> specified. Otherwise, `false`.</returns>
        public bool HasPath(JsonPlusPath path)
        {
            JsonPlusValue node;

            try
            {
                node = GetNode(path);
            }
            catch
            {
                return(false);
            }
            return(node != null);
        }
예제 #19
0
        internal bool IsChildPathOf(JsonPlusPath parentPath)
        {
            if (Count < parentPath.Count)
            {
                return(false);
            }

            for (int i = 0; i < parentPath.Count; ++i)
            {
                if (this[i] != parentPath[i])
                {
                    return(false);
                }
            }
            return(true);
        }
예제 #20
0
        private JsonPlusObjectMember ParseObjectMember(JsonPlusObject owner)
        {
            // sanity check
            if (_tokens.Current.Type != TokenType.LiteralValue)
            {
                throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenInMember, TokenType.LiteralValue, _tokens.Current.Type));
            }

            JsonPlusPath pathDelta = ParseKey();

            if (_tokens.Current.IsNonSignificant())
            {
                ConsumeWhitelines();
            }

            // sanity check
            if (_tokens.Current.Type != TokenType.Assignment &&
                _tokens.Current.Type != TokenType.StartOfObject &&
                _tokens.Current.Type != TokenType.SelfAssignment)
            {
                throw JsonPlusParserException.Create(_tokens.Current, Path,
                                                     string.Format(RS.UnexpectedTokenWith3AltInMember,
                                                                   TokenType.Assignment, TokenType.StartOfObject, TokenType.SelfAssignment,
                                                                   _tokens.Current.Type));
            }

            // sanity check
            if (pathDelta == null || pathDelta.Count == 0)
            {
                throw JsonPlusParserException.Create(_tokens.Current, Path, RS.ObjectMemberPathUnspecified);
            }

            List <JsonPlusObjectMember> childInPath = owner.TraversePath(pathDelta);

            Path.AddRange(pathDelta);
            JsonPlusObjectMember currentField = childInPath[childInPath.Count - 1];

            JsonPlusValue parsedValue = ParseValue(currentField);

            foreach (JsonPlusSubstitution removedSub in currentField.SetValue(parsedValue))
            {
                _substitutions.Remove(removedSub);
            }

            Path.RemoveRange(Path.Count - pathDelta.Count, pathDelta.Count);
            return(childInPath[0]);
        }
예제 #21
0
        // parse path value
        private JsonPlusPath ParseKey()
        {
            while (_tokens.Current.LiteralType == LiteralTokenType.Whitespace)
            {
                _tokens.Next();
            }

            // sanity check
            if (_tokens.Current.Type != TokenType.LiteralValue)
            {
                throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedKeyType, TokenType.LiteralValue, _tokens.Current.Type));
            }

            if (_tokens.Current.IsNonSignificant())
            {
                ConsumeWhitelines();
            }
            if (_tokens.Current.Type != TokenType.LiteralValue)
            {
                return(null);
            }

            TokenizeResult keyTokens = new TokenizeResult();

            while (_tokens.Current.Type == TokenType.LiteralValue)
            {
                keyTokens.Add(_tokens.Current);
                _tokens.Next();
            }

            keyTokens.Reverse();
            while (keyTokens.Count > 0 && keyTokens[0].LiteralType == LiteralTokenType.Whitespace)
            {
                keyTokens.RemoveAt(0);
            }
            keyTokens.Reverse();

            keyTokens.Add(new Token(string.Empty, TokenType.EndOfFile, null));

            return(JsonPlusPath.FromTokens(keyTokens));
        }
예제 #22
0
        /// <summary>
        /// Returns a value indicating whether the value of this instance is equal to the value of the specified <see cref="JsonPlusPath"/> instance.
        /// </summary>
        /// <param name="other">The object to compare to this instance.</param>
        /// <returns>`true` if the <paramref name="other"/> parameter equals the value of this instance. Otherwise, `false`.</returns>
        public bool Equals(JsonPlusPath other)
        {
            if (other is null)
            {
                return(false);
            }

            if (Count != other.Count)
            {
                return(false);
            }

            for (int i = 0; i < Count; ++i)
            {
                if (this[i] != other[i])
                {
                    return(false);
                }
            }
            return(true);
        }
예제 #23
0
        private JsonPlusPath GetMemberPath(JsonPlusObjectMember member, JsonPlusPath subPath)
        {
            if (member.ParentMember == null && subPath == null)
            {
                return(new JsonPlusPath(new string[] { member.Key }));
            }

            if (subPath == null)
            {
                return(GetMemberPath(member.ParentMember, new JsonPlusPath(new string[] { member.Key })));
            }

            subPath.Add(member.Key);

            if (member.ParentMember == null)
            {
                return(subPath);
            }

            return(GetMemberPath(member.ParentMember, subPath));
        }
예제 #24
0
        /// <summary>
        /// Returns the <see cref="JsonPlusObjectMember"/> associated with the <see cref="JsonPlusPath"/> specified.
        /// </summary>
        /// <param name="path">The path to the field to return.</param>
        /// <exception cref="ArgumentNullException">The <paramref name="path"/> specified is `null`.</exception>
        /// <exception cref="ArgumentException">The <paramref name="path"/> specified is empty.</exception>
        /// <exception cref="KeyNotFoundException">The key does not exist in the <see cref="JsonPlusObject"/> instance.</exception>
        /// <exception cref="JsonPlusException">The <paramref name="path"/> specified is invalid.</exception>
        /// <returns>The <see cref="JsonPlusObjectMember"/> associated with <paramref name="path"/>.</returns>
        public JsonPlusObjectMember GetMember(JsonPlusPath path)
        {
            if (path == null)
            {
                throw new ArgumentNullException(nameof(path));
            }

            if (path.Count == 0)
            {
                throw new ArgumentException(RS.PathIsEmpty, nameof(path));
            }

            int            pathIndex     = 0;
            JsonPlusObject currentObject = this;

            while (true)
            {
                string key = path[pathIndex];

                if (!currentObject.TryGetValue(key, out JsonPlusObjectMember field))
                {
                    throw new KeyNotFoundException(string.Format(RS.ObjectMemberNotFoundByPath,
                                                                 key, new JsonPlusPath(path.GetRange(0, pathIndex + 1)).Value));
                }

                if (pathIndex >= path.Count - 1)
                {
                    return(field);
                }

                if (field.Type != JsonPlusType.Object)
                {
                    throw new JsonPlusException(string.Format(RS.ObjectMemberInPathNotObject,
                                                              new JsonPlusPath(path.GetRange(0, pathIndex + 1)).Value));
                }

                currentObject = field.GetObject();
                pathIndex     = pathIndex + 1;
            }
        }
예제 #25
0
        internal List <JsonPlusObjectMember> TraversePath(JsonPlusPath path)
        {
            List <JsonPlusObjectMember> result = new List <JsonPlusObjectMember>();
            int            pathLength          = 1;
            JsonPlusObject currentObject       = this;

            while (true)
            {
                JsonPlusObjectMember child = currentObject.GetOrCreateKey(new JsonPlusPath(path.GetRange(0, pathLength)));
                result.Add(child);

                pathLength++;
                if (pathLength > path.Count)
                {
                    return(result);
                }

                child.EnsureMemberIsObject();

                // cannot use child.GetObject() because it would return a merged object, which
                // breaks autoref with the parent object
                currentObject = child.Value.GetObject();
            }
        }
예제 #26
0
 /// <see cref="HasPath(JsonPlusPath)"/>
 public bool HasPath(string path)
 {
     return(HasPath(JsonPlusPath.Parse(path)));
 }