internal void ResolveValue(JsonPlusValue child) { if (child.Count == 0) { Remove(child); } }
/// <summary> /// Parses the Json+ source code specified into structured objects. /// </summary> /// <param name="source">The source code that conforms to Json+ specification.</param> /// <param name="includeCallback">Callback used to resolve the `include` directive.</param> /// <param name="resolveSubstitutions">Resolve substitution directives.</param> /// <param name="resolveEnv">Try to resolve environment variables. Does nothing if <paramref name="resolveSubstitutions"/> is `false`.</param> /// <returns></returns> private JsonPlusRoot ParseSource(string source, bool resolveSubstitutions, bool resolveEnv, IncludeCallbackAsync includeCallback) { if (string.IsNullOrWhiteSpace(source)) { throw new JsonPlusParserException(string.Format(RS.SourceEmptyError, nameof(source))); } if (includeCallback != null) { _includeCallback = includeCallback; } try { _tokens = new JPlusTokenizer(source).Tokenize(); _root = new JsonPlusValue(null); ParseTokens(); if (resolveSubstitutions) { ResolveAllSubstitution(resolveEnv); } } catch (JsonPlusTokenizerException e) { throw JsonPlusParserException.Create(e, null, string.Format(RS.TokenizeError, e.Message), e); } catch (JsonPlusException e) { throw JsonPlusParserException.Create(_tokens.Current, Path, e.Message, e); } return(new JsonPlusRoot(_root, _substitutions)); }
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); }
internal void ResolveValue(JsonPlusValue value) { if (value.Type != JsonPlusType.Empty) { return; } ((JsonPlusObject)Parent).ResolveValue(this); }
/// <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()); }
/// <see cref="IJsonPlusNode.Clone(IJsonPlusNode)"/> public virtual IJsonPlusNode Clone(IJsonPlusNode newParent) { JsonPlusValue clone = new JsonPlusValue(newParent); foreach (IJsonPlusNode value in this) { clone.Add(value.Clone(clone)); } return(clone); }
/// <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 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 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> /// 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 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 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 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> /// 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); }
internal void EnsureMemberIsObject() { if (Type == JsonPlusType.Object) { return; } JsonPlusValue v = new JsonPlusValue(this); JsonPlusObject o = new JsonPlusObject(v); v.Add(o); _internalValues.Add(v); }
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]); }
internal JsonPlusValue OlderValueThan(IJsonPlusNode marker) { List <JsonPlusObject> objectList = new List <JsonPlusObject>(); int index = 0; while (index < _internalValues.Count) { JsonPlusValue value = _internalValues[index]; if (value.Any(v => ReferenceEquals(v, marker))) { break; } switch (value.Type) { case JsonPlusType.Object: objectList.Add(value.GetObject()); break; case JsonPlusType.Literal: case JsonPlusType.Array: objectList.Clear(); break; } index++; } if (objectList.Count == 0) { return(index == 0 ? null : _internalValues[index - 1]); } JsonPlusValue result = new JsonPlusValue(null); JsonPlusObject o = new JsonPlusObject(result); result.Add(o); foreach (JsonPlusObject obj in objectList) { o.Merge(obj); } return(result); }
internal List <JsonPlusSubstitution> SetValue(JsonPlusValue value) { List <JsonPlusSubstitution> removedSubs = new List <JsonPlusSubstitution>(); if (value.Type == JsonPlusType.Array || value.Type == JsonPlusType.Literal) { List <JsonPlusSubstitution> subs = value.GetAllSubstitution(); if (subs.All(sub => sub.Path != Path)) { foreach (JsonPlusValue item in _internalValues) { removedSubs.AddRange(item.GetAllSubstitution()); } _internalValues.Clear(); } } _internalValues.Add(value); return(removedSubs); }
/// <summary> /// Returns a value indicating whether the value of this instance is equal to the value of the specified <see cref="JsonPlusValue"/> 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> protected bool Equals(JsonPlusValue other) { return(Type == other.Type && GetString() == other.GetString()); }
private bool IsValueCyclic(JsonPlusObjectMember field, JsonPlusSubstitution sub) { Stack <JsonPlusValue> pendingValues = new Stack <JsonPlusValue>(); List <JsonPlusObjectMember> visitedFields = new List <JsonPlusObjectMember> { field }; Stack <JsonPlusSubstitution> pendingSubs = new Stack <JsonPlusSubstitution>(); pendingSubs.Push(sub); while (pendingSubs.Count > 0) { JsonPlusSubstitution currentSub = pendingSubs.Pop(); if (!_root.GetObject().TryGetMember(currentSub.Path, out var currentField)) { continue; } if (visitedFields.Contains(currentField)) { return(true); } visitedFields.Add(currentField); pendingValues.Push(currentField.Value); while (pendingValues.Count > 0) { JsonPlusValue currentValue = pendingValues.Pop(); foreach (IJsonPlusNode value in currentValue) { switch (value) { case JsonPlusLiteralValue _: break; case JsonPlusObject o: foreach (JsonPlusObjectMember f in o.Values) { if (visitedFields.Contains(f)) { return(true); } visitedFields.Add(f); pendingValues.Push(f.Value); } break; case JsonPlusArray a: foreach (JsonPlusValue item in a.GetArray()) { pendingValues.Push(item); } break; case JsonPlusSubstitution s: pendingSubs.Push(s); break; } } } } return(false); }
static JsonPlusValue() { Undefined = new EmptyValue(null); }
/// <summary> /// Returns the underlying <see cref="JsonPlusLiteralType"/> of a literal value. /// </summary> /// <exception cref="JsonPlusException">The <see cref="Type"/> property must be of type <see cref="JsonPlusType.Literal"/>.</exception> /// <returns> /// The underlying <see cref="JsonPlusLiteralType"/> of an instance of this <see cref="JsonPlusValue"/> which is a literal value. /// </returns> public virtual JsonPlusLiteralType GetLiteralType() { if (Type != JsonPlusType.Literal) { throw new JsonPlusException(RS.ExpectLiteralType); } if (Count > 1) { bool containsSubstitution = false; foreach (IJsonPlusNode node in Children) { if (node is JsonPlusSubstitution) { containsSubstitution = true; break; } } if (containsSubstitution) { return(JsonPlusLiteralType.String); } if (IsTimeSpan()) { return(JsonPlusLiteralType.TimeSpan); } if (IsByteSize()) { return(JsonPlusLiteralType.ByteSize); } return(JsonPlusLiteralType.String); } if (Count == 0) { return(JsonPlusLiteralType.Null); } // timespan and bytesize expressions will have at least 2 children, so here onwards node cannot be either. IJsonPlusNode firstChild = Children[0]; if (firstChild is JsonPlusSubstitution) { JsonPlusValue substChild = ((JsonPlusSubstitution)firstChild).ResolvedValue; if (substChild.Type != JsonPlusType.Literal) { throw new JsonPlusException(string.Format(RS.InternalErrorStopCode, "ONLY_SUBST_TYPE_NOT_LITERAL")); } return(substChild.GetLiteralType()); } if (firstChild is NullValue) { return(JsonPlusLiteralType.Null); } else if (firstChild is BooleanValue) { return(JsonPlusLiteralType.Boolean); } else if (firstChild is DecimalValue) { return(JsonPlusLiteralType.Decimal); } else if (firstChild is IntegerValue) { return(JsonPlusLiteralType.Integer); } else if (firstChild is HexadecimalValue) { return(JsonPlusLiteralType.Hexadecimal); } else if (firstChild is OctetValue) { return(JsonPlusLiteralType.Octet); } else if (firstChild is UnquotedStringValue) { return(JsonPlusLiteralType.UnquotedString); } else if (firstChild is QuotedStringValue) { return(JsonPlusLiteralType.QuotedString); } else if (firstChild is TripleQuotedStringValue) { return(JsonPlusLiteralType.TripleQuotedString); } else if (firstChild is WhitespaceValue) { return(JsonPlusLiteralType.Whitespace); } else { throw new JsonPlusException(string.Format(RS.UnknownLiteralToken, firstChild.GetType().ToString())); } }
/// <summary> /// Retrieves the next value token from the tokenizer and appends it /// to the supplied element <paramref name="owner"/>. /// </summary> /// <param name="owner">The element to append the next token.</param> /// <exception cref="Exception">End of file reached while trying to read a value</exception> private JsonPlusValue ParseValue(IJsonPlusNode owner) { JsonPlusValue value = new JsonPlusValue(owner); bool parsing = true; while (parsing) { switch (_tokens.Current.Type) { case TokenType.Include: case TokenType.OptionalInclude: value.Add(ParseInclude(value)); break; case TokenType.LiteralValue: // Consume leading whitespaces. if (_tokens.Current.IsNonSignificant()) { ConsumeWhitespace(); } if (_tokens.Current.Type != TokenType.LiteralValue) { break; } while (_tokens.Current.Type == TokenType.LiteralValue) { value.Add(JsonPlusLiteralValue.Create(value, _tokens.Current)); _tokens.Next(); } break; case TokenType.StartOfObject: value.Add(ParseObject(value)); break; case TokenType.StartOfArray: value.Add(ParseArray(value)); break; case TokenType.OptionalSubstitution: case TokenType.Substitution: JsonPlusPath pointerPath = JsonPlusPath.Parse(_tokens.Current.Value); JsonPlusSubstitution sub = new JsonPlusSubstitution(value, pointerPath, _tokens.Current, _tokens.Current.Type == TokenType.Substitution); _substitutions.Add(sub); value.Add(sub); _tokens.Next(); break; case TokenType.EndOfObject: case TokenType.EndOfArray: parsing = false; break; // comments automatically stop value parsing. case TokenType.Comment: ConsumeWhitelines(); parsing = false; break; case TokenType.EndOfLine: parsing = false; break; case TokenType.EndOfFile: case TokenType.ArraySeparator: parsing = false; break; case TokenType.SelfAssignment: JsonPlusSubstitution subAssign = new JsonPlusSubstitution(value, new JsonPlusPath(Path), _tokens.Current, false); _substitutions.Add(subAssign); value.Add(subAssign); value.Add(ParseSelfAssignArray(value)); parsing = false; break; case TokenType.Assignment: ConsumeWhitelines(); break; default: throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.ErrAtUnexpectedToken, _tokens.Current.Type)); } } // trim trailing whitespace if result is a literal if (value.Type == JsonPlusType.Literal) { if (value[value.Count - 1] is WhitespaceValue) { value.RemoveAt(value.Count - 1); } } return(value); }
/// <summary> /// Returns a node as a generic <see cref="JsonPlusValue"/>. /// </summary> /// <param name="path">A Json+ query path that identifies a node in the current context.</param> /// <returns>The <see cref="JsonPlusValue"/> value of the node specified by <paramref name="path"/>, or `null` if the node does not exist.</returns> public JsonPlusValue GetValue(JsonPlusPath path) { JsonPlusValue value = GetNode(path); return(value); }
private void ResolveAllSubstitution(bool resolveEnv) { foreach (JsonPlusSubstitution sub in _substitutions) { // Retrieve value JsonPlusValue res; try { res = ResolveSubstitution(sub); } catch (JsonPlusException e) { throw JsonPlusParserException.Create(sub, sub.Path, string.Format(RS.SubstitutionError, e.Message), e); } if (res != null) { sub.ResolvedValue = res; continue; } if (resolveEnv) { // Try to pull value from environment string envValue = null; try { envValue = Environment.GetEnvironmentVariable(sub.Path.Value); } catch (Exception) { // ignored } if (envValue != null) { // undefined value resolved to an environment variable res = new JsonPlusValue(sub.Parent); if (envValue.NeedQuotes()) { res.Add(new QuotedStringValue(sub.Parent, envValue)); } else { res.Add(new UnquotedStringValue(sub.Parent, envValue)); } sub.ResolvedValue = res; continue; } } // ${ throws exception if it is not resolved if (sub.Required) { throw JsonPlusParserException.Create(sub, sub.Path, string.Format(RS.UnresolvedSubstitution, sub.Path)); } sub.ResolvedValue = new EmptyValue(sub.Parent); } }
/// <summary> /// Initializes a new instance of the <see cref="JsonPlusRoot"/> class. /// </summary> /// <param name="value">The value to associate with this instance.</param> /// <param name="substitutions">An enumeration of substitutions to associate with this instance.</param> public JsonPlusRoot(JsonPlusValue value, IEnumerable <JsonPlusSubstitution> substitutions) { Value = value; Substitutions = substitutions; }
/// <summary> /// Returns a node as a <see cref="string"/>. /// </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> /// <returns>The <see cref="string"/> value of the node specified by <paramref name="path"/>, or <paramref name="defaultValue"/> if the node does not exist.</returns> public string GetString(JsonPlusPath path, string defaultValue = null) { JsonPlusValue value = GetNode(path); return(ReferenceEquals(value, JsonPlusValue.Undefined) ? defaultValue : value.GetString()); }
/// <summary> /// Initializes a new instance of the <see cref="JsonPlusRoot"/> class. /// </summary> /// <param name="value">The value to associate with this node.</param> public JsonPlusRoot(JsonPlusValue value) : this(value, Enumerable.Empty <JsonPlusSubstitution>()) { }