/// <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; } }
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); }
/// <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; }
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); }
/// <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)); }
/// <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()); }
/// <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); }
/// <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>(); }
/// <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()); }
/// <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)); }
/// <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()); }
/// <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()); }
/// <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()); }
/// <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()); }
/// <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()); }
/// <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()); }
/// <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); }
/// <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); }
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); }
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]); }
// 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)); }
/// <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); }
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)); }
/// <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; } }
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(); } }
/// <see cref="HasPath(JsonPlusPath)"/> public bool HasPath(string path) { return(HasPath(JsonPlusPath.Parse(path))); }