/* SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. */ /* * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. * * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch B.V. licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ //This is nancyfx's dynamicdictionary //it is slightly modified to add the ability to chain dynamic property access of arbitrary depth //without binding on null ref errors in between. using System; using System.Collections; using System.Collections.Generic; using System.Dynamic; using System.Linq; using System.Text.RegularExpressions; using OpenSearch.Net.Utf8Json; // ReSharper disable ArrangeMethodOrOperatorBody // ReSharper disable RemoveRedundantBraces // ReSharper disable ArrangeAccessorOwnerBody namespace OpenSearch.Net { /// /// A dictionary that supports dynamic access. /// [JsonFormatter(typeof(DynamicDictionaryFormatter))] public class DynamicDictionary : DynamicObject, IEquatable, IEnumerable, IDictionary { private readonly IDictionary _backingDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// Gets the number of elements contained in the . /// /// The number of elements contained in the . public int Count { get { return _backingDictionary.Count; } } /// /// Creates a new instance of Dictionary{String,Object} using the keys and underlying object values of this DynamicDictionary instance's key values. /// /// public Dictionary ToDictionary() => _backingDictionary.ToDictionary(kv => kv.Key, kv => kv.Value.Value); /// /// Returns an empty dynamic dictionary. /// /// A instance. public static DynamicDictionary Empty => new DynamicDictionary(); /// /// Gets a value indicating whether the is read-only. /// /// Always returns . public bool IsReadOnly { get { return false; } } private static Regex SplitRegex = new Regex(@"(? /// Traverses data using path notation. /// e.g some.deep.nested.json.path /// /// A special lookup is available for ANY key _arbitrary_key_ e.g some.deep._arbitrary_key_.json.path which will traverse into the first key /// If _arbitrary_key_ is the last value it will return the key name /// /// /// path into the stored object, keys are separated with a dot and the last key is returned as T /// /// T or default public T Get(string path) { if (path == null) return default; var split = SplitRegex.Split(path); var queue = new Queue(split); if (queue.Count == 0) return default; var d = new DynamicValue(_backingDictionary); while (queue.Count > 0) { var key = queue.Dequeue().Replace(@"\.", "."); if (key == "_arbitrary_key_") { if (queue.Count > 0) d = d[0]; else { var v = d?.ToDictionary()?.Keys?.FirstOrDefault(); d = v != null ? new DynamicValue(v) : DynamicValue.NullValue; } } else if (int.TryParse(key, out var i)) d = d[i]; else d = d[key]; } return d.TryParse(); } /// /// Gets or sets the with the specified name. /// /// A instance containing a value. public DynamicValue this[string name] { get { name = GetNeutralKey(name); if (!_backingDictionary.TryGetValue(name, out var member)) { member = new DynamicValue(null); } return member; } set { name = GetNeutralKey(name); _backingDictionary[name] = value is DynamicValue ? value : new DynamicValue(value); } } /// /// Gets an containing the keys of the . /// /// An containing the keys of the . public ICollection Keys => _backingDictionary.Keys; /// /// Gets an containing the values in the . /// /// An containing the values in the . public ICollection Values { get { return _backingDictionary.Values; } } /// /// Adds an item to the . /// /// The object to add to the . public void Add(KeyValuePair item) { this[item.Key] = item.Value; } /// /// Removes all items from the . /// public void Clear() => _backingDictionary.Clear(); /// /// Determines whether the contains a specific value. /// /// /// if is found in the ; otherwise, . /// /// The object to locate in the . public bool Contains(KeyValuePair item) { return _backingDictionary.Contains(item); } /// /// Copies the elements of the to an , starting at a particular /// index. /// /// /// The one-dimensional that is the destination of the elements copied from the /// . The must have zero-based indexing. /// /// The zero-based index in at which copying begins. public void CopyTo(KeyValuePair[] array, int arrayIndex) { _backingDictionary.CopyTo(array, arrayIndex); } /// /// Removes the first occurrence of a specific object from the . /// /// /// if was successfully removed from the ; otherwise, /// . /// /// The object to remove from the . public bool Remove(KeyValuePair item) { return _backingDictionary.Remove(item); } /// /// Adds an element with the provided key and value to the . /// /// The object to use as the key of the element to add. /// The object to use as the value of the element to add. public void Add(string key, DynamicValue value) { this[key] = value; } /// /// Determines whether the contains an element with the specified key. /// /// /// if the contains an element with the key; otherwise, . /// /// The key to locate in the . public bool ContainsKey(string key) { return _backingDictionary.ContainsKey(key); } /// /// Removes the element with the specified key from the . /// /// if the element is successfully removed; otherwise, . /// The key of the element to remove. public bool Remove(string key) { key = GetNeutralKey(key); return _backingDictionary.Remove(key); } /// /// Gets the value associated with the specified key. /// /// /// if the contains an element with the specified key; otherwise, /// . /// /// The key whose value to get. /// /// When this method returns, the value associated with the specified key, if the key is found; otherwise, the default /// value for the type of the parameter. This parameter is passed uninitialized. /// public bool TryGetValue(string key, out DynamicValue value) { if (_backingDictionary.TryGetValue(key, out value)) return true; return false; } /// /// Returns the enumeration of all dynamic member names. /// /// A that contains dynamic member names. IEnumerator IEnumerable.GetEnumerator() { return _backingDictionary.Keys.GetEnumerator(); } /// /// Returns an enumerator that iterates through the collection. /// /// A that can be used to iterate through the collection. IEnumerator> IEnumerable>.GetEnumerator() { return _backingDictionary.GetEnumerator(); } /// /// Backwards compatible access to all the KeyValue pairs, in the next release you will be able to foreach directly /// public IEnumerable> GetKeyValues() { return _backingDictionary; } /// /// Returns the enumeration of all dynamic member names. /// /// A that contains dynamic member names. public IEnumerator GetEnumerator() { return _backingDictionary.Keys.GetEnumerator(); } /// /// Indicates whether the current is equal to another object of the same type. /// /// /// if the current instance is equal to the parameter; otherwise, /// . /// /// An instance to compare with this instance. public bool Equals(DynamicDictionary other) { if (ReferenceEquals(null, other)) { return false; } return ReferenceEquals(this, other) || Equals(other._backingDictionary, _backingDictionary); } /// /// Creates a dynamic dictionary from an instance. /// /// An instance, that the dynamic dictionary should be created from. /// An instance. public static DynamicDictionary Create(IDictionary values) { var instance = new DynamicDictionary(); foreach (var key in values.Keys) { var v = values[key]; instance[key] = v is DynamicValue av ? av : new DynamicValue(v); } return instance; } /// /// Provides the implementation for operations that set member values. Classes derived from the /// class can override this method to specify dynamic behavior for operations such as setting a value for a property. /// /// /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language /// determines the behavior. (In most cases, a language-specific run-time exception is thrown.) /// /// /// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of /// the member to which the value is being assigned. For example, for the statement sampleObject.SampleProperty = "Test", where sampleObject is /// an instance of the class derived from the class, binder.Name returns "SampleProperty". The /// binder.IgnoreCase property specifies whether the member name is case-sensitive. /// /// /// The value to set to the member. For example, for sampleObject.SampleProperty = "Test", where sampleObject is an /// instance of the class derived from the class, the is "Test". /// public override bool TrySetMember(SetMemberBinder binder, object value) { this[binder.Name] = new DynamicValue(value); return true; } /// /// Provides the implementation for operations that get member values. Classes derived from the /// class can override this method to specify dynamic behavior for operations such as getting a value for a property. /// /// /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language /// determines the behavior. (In most cases, a run-time exception is thrown.) /// /// /// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of /// the member on which the dynamic operation is performed. For example, for the Console.WriteLine(sampleObject.SampleProperty) statement, /// where sampleObject is an instance of the class derived from the class, binder.Name returns /// "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive. /// /// /// The result of the get operation. For example, if the method is called for a property, you can assign the property /// value to . /// public override bool TryGetMember(GetMemberBinder binder, out object result) { if (!_backingDictionary.TryGetValue(binder.Name, out var v)) { result = new DynamicValue(null); } else result = v; return true; } /// /// Returns the enumeration of all dynamic member names. /// /// A that contains dynamic member names. public override IEnumerable GetDynamicMemberNames() { return _backingDictionary.Keys; } /// /// Determines whether the specified is equal to this instance. /// /// The to compare with this instance. /// /// if the specified is equal to this instance; otherwise, /// . /// public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } return obj.GetType() == typeof(DynamicDictionary) && Equals((DynamicDictionary)obj); } /// /// Returns a hash code for this . /// /// A hash code for this , suitable for use in hashing algorithms and data structures like a hash table. public override int GetHashCode() => _backingDictionary?.GetHashCode() ?? 0; private static KeyValuePair GetDynamicKeyValuePair(KeyValuePair item) { var dynamicValueKeyValuePair = new KeyValuePair(item.Key, item.Value); return dynamicValueKeyValuePair; } private static string GetNeutralKey(string key) { return key; } } }