/// <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); }
private static void Flatten(IJsonPlusNode node) { if (!(node is JsonPlusValue v)) { return; } switch (v.Type) { case JsonPlusType.Object: JsonPlusObject o = v.GetObject(); v.Clear(); v.Add(o); foreach (JsonPlusObjectMember item in o.Values) { Flatten(item); } break; case JsonPlusType.Array: List <IJsonPlusNode> a = v.GetArray(); v.Clear(); JsonPlusArray newArray = new JsonPlusArray(v); foreach (IJsonPlusNode item in a) { Flatten(item); newArray.Add(item); } v.Add(newArray); break; case JsonPlusType.Literal: if (v.Count == 1) { return; } string value = v.GetString(); v.Clear(); if (value == null) { v.Add(new NullValue(v)); } else if (value.NeedTripleQuotes()) { v.Add(new TripleQuotedStringValue(v, value)); } else if (value.NeedQuotes()) { v.Add(new QuotedStringValue(v, value)); } else { v.Add(new UnquotedStringValue(v, value)); } break; } }
/// <see cref="IJsonPlusNode.Clone(IJsonPlusNode)"/> public IJsonPlusNode Clone(IJsonPlusNode newParent) { JsonPlusObject clone = new JsonPlusObject(newParent); foreach (KeyValuePair <string, JsonPlusObjectMember> kvp in this) { clone[kvp.Key] = (JsonPlusObjectMember)kvp.Value.Clone(clone); } return(clone); }
/// <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 the merged <see cref="JsonPlusObject"/> that backs the <see cref="JsonPlusObjectMember"/> associated with the key specified. /// </summary> /// <param name="key">The key associated with 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="key"/>, and the <see cref="JsonPlusObjectMember.Type"/> property /// is <see cref="JsonPlusType.Object"/>. Otherwise, `false`.</returns> public bool TryGetObject(string key, out JsonPlusObject result) { result = null; if (!TryGetMember(key, out JsonPlusObjectMember field)) { return(false); } result = field.GetObject(); 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); }
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); }
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]); }
/// <summary> /// Merge the members of a <see cref="JsonPlusObject"/> into this instance. /// </summary> /// <param name="other">The <see cref="JsonPlusObject"/> to merge with this instance.</param> public virtual void Merge(JsonPlusObject other) { string[] keys = other.Keys.ToArray(); foreach (string key in keys) { if (ContainsKey(key)) { JsonPlusObjectMember thisItem = this[key]; JsonPlusObjectMember otherItem = other[key]; if (thisItem.Type == JsonPlusType.Object && otherItem.Type == JsonPlusType.Object) { thisItem.GetObject().Merge(otherItem.GetObject()); continue; } } this[key] = other[key]; } }
/// <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="JsonPlusObject.Merge(JsonPlusObject)"/> public override void Merge(JsonPlusObject other) { ((JsonPlusObjectMember)Parent).Value.Add(other.Clone(((JsonPlusObjectMember)Parent).Value)); base.Merge(other); }
// The owner in this context can be either an object or an array. private JsonPlusObject ParseObject(IJsonPlusNode owner) { JsonPlusObject jpObject = new JsonPlusObject(owner); if (_tokens.Current.Type != TokenType.StartOfObject && _tokens.Current.Type != TokenType.LiteralValue && _tokens.Current.Type != TokenType.Include) { throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenWith2AltInObject, TokenType.StartOfObject, TokenType.LiteralValue, _tokens.Current.Type)); } bool headless = true; if (_tokens.Current.Type == TokenType.StartOfObject) { headless = false; ConsumeWhitelines(); } IJsonPlusNode lastValue = null; bool parsing = true; while (parsing) { switch (_tokens.Current.Type) { case TokenType.Include: case TokenType.OptionalInclude: if (lastValue != null) { throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenWith2AltInObject, TokenType.ArraySeparator, TokenType.EndOfLine, _tokens.Current.Type)); } lastValue = ParseInclude(jpObject); break; case TokenType.LiteralValue: if (_tokens.Current.IsNonSignificant()) { ConsumeWhitespace(); } if (_tokens.Current.Type != TokenType.LiteralValue) { break; } if (lastValue != null) { throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenWith2AltInObject, TokenType.ArraySeparator, TokenType.EndOfLine, _tokens.Current.Type)); } lastValue = ParseObjectMember(jpObject); break; // TODO: can an object be declared floating without being assigned to a field? //case TokenType.StartOfObject: case TokenType.Comment: case TokenType.EndOfLine: switch (lastValue) { case null: ConsumeWhitelines(); break; case JsonPlusObjectMember _: break; default: ((JsonPlusValue)jpObject.Parent).Add(lastValue.GetObject()); break; } lastValue = null; ConsumeWhitelines(); break; case TokenType.ArraySeparator: switch (lastValue) { case null: throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenWith2AltInObject, TokenType.Assignment, TokenType.StartOfObject, _tokens.Current.Type)); case JsonPlusObjectMember _: break; default: ((JsonPlusValue)jpObject.Parent).Add(lastValue.GetObject()); break; } lastValue = null; ConsumeWhitelines(); break; case TokenType.EndOfObject: case TokenType.EndOfFile: if (headless && _tokens.Current.Type != TokenType.EndOfFile) { throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenInObject, TokenType.EndOfFile, _tokens.Current.Type)); } if (!headless && _tokens.Current.Type != TokenType.EndOfObject) { throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenInObject, TokenType.EndOfFile, _tokens.Current.Type)); } switch (lastValue) { case null: break; case JsonPlusObjectMember _: break; default: ((JsonPlusValue)jpObject.Parent).Add(lastValue.GetObject()); break; } lastValue = null; parsing = false; break; default: throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.ErrAtUnexpectedTokenInObject, _tokens.Current.Type)); } } if (_tokens.Current.Type == TokenType.EndOfObject) { _tokens.Next(); } return(jpObject); }