#region License // Copyright (c) 2007 James Newton-King // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. #endregion using System; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; using System.IO; using Newtonsoft.Json.Utilities; using System.Globalization; #if !PocketPC && !SILVERLIGHT using Newtonsoft.Json.Linq.ComponentModel; #endif namespace Newtonsoft.Json.Linq { /// /// Represents a JSON object. /// #if !PocketPC && !SILVERLIGHT [TypeDescriptionProvider(typeof(JTypeDescriptionProvider))] #endif public class JObject : JContainer, IDictionary, INotifyPropertyChanged #if !PocketPC && !SILVERLIGHT && !NET20 , INotifyPropertyChanging #endif { /// /// Occurs when a property value changes. /// public event PropertyChangedEventHandler PropertyChanged; #if !PocketPC && !SILVERLIGHT && !NET20 /// /// Occurs when a property value is changing. /// public event PropertyChangingEventHandler PropertyChanging; #endif /// /// Initializes a new instance of the class. /// public JObject() { } /// /// Initializes a new instance of the class from another object. /// /// A object to copy from. public JObject(JObject other) : base(other) { } /// /// Initializes a new instance of the class with the specified content. /// /// The contents of the object. public JObject(params object[] content) : this((object)content) { } /// /// Initializes a new instance of the class with the specified content. /// /// The contents of the object. public JObject(object content) { Add(content); } internal override bool DeepEquals(JToken node) { JObject t = node as JObject; return (t != null && ContentsEqual(t)); } internal override void ValidateToken(JToken o, JToken existing) { ValidationUtils.ArgumentNotNull(o, "o"); if (o.Type != JTokenType.Property) throw new ArgumentException("Can not add {0} to {1}.".FormatWith(CultureInfo.InvariantCulture, o.GetType(), GetType())); // looping over all properties every time isn't good // need to think about performance here JProperty property = (JProperty)o; foreach (JProperty childProperty in Children()) { if (childProperty != existing && string.Equals(childProperty.Name, property.Name, StringComparison.Ordinal)) throw new ArgumentException("Can not add property {0} to {1}. Property with the same name already exists on object.".FormatWith(CultureInfo.InvariantCulture, property.Name, GetType())); } } internal void InternalPropertyChanged(JProperty childProperty) { OnPropertyChanged(childProperty.Name); #if !SILVERLIGHT OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, IndexOfItem(childProperty))); #else OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, childProperty, childProperty, IndexOfItem(childProperty))); #endif } internal void InternalPropertyChanging(JProperty childProperty) { #if !PocketPC && !SILVERLIGHT && !NET20 OnPropertyChanging(childProperty.Name); #endif } internal override JToken CloneToken() { return new JObject(this); } /// /// Gets the node type for this . /// /// The type. public override JTokenType Type { get { return JTokenType.Object; } } /// /// Gets an of this object's properties. /// /// An of this object's properties. public IEnumerable Properties() { return Children().Cast(); } /// /// Gets a the specified name. /// /// The property name. /// A with the specified name or null. public JProperty Property(string name) { return Properties() .Where(p => string.Equals(p.Name, name, StringComparison.Ordinal)) .SingleOrDefault(); } /// /// Gets an of this object's property values. /// /// An of this object's property values. public JEnumerable PropertyValues() { return new JEnumerable(Properties().Select(p => p.Value)); } /// /// Gets the with the specified key. /// /// The with the specified key. public override JToken this[object key] { get { ValidationUtils.ArgumentNotNull(key, "o"); string propertyName = key as string; if (propertyName == null) throw new ArgumentException("Accessed JObject values with invalid key value: {0}. Object property name expected.".FormatWith(CultureInfo.InvariantCulture, MiscellaneousUtils.ToString(key))); return this[propertyName]; } set { ValidationUtils.ArgumentNotNull(key, "o"); string propertyName = key as string; if (propertyName == null) throw new ArgumentException("Set JObject values with invalid key value: {0}. Object property name expected.".FormatWith(CultureInfo.InvariantCulture, MiscellaneousUtils.ToString(key))); this[propertyName] = value; } } /// /// Gets or sets the with the specified property name. /// /// public JToken this[string propertyName] { get { ValidationUtils.ArgumentNotNull(propertyName, "propertyName"); JProperty property = Property(propertyName); return (property != null) ? property.Value : null; } set { JProperty property = Property(propertyName); if (property != null) { property.Value = value; } else { #if !PocketPC && !SILVERLIGHT && !NET20 OnPropertyChanging(propertyName); #endif Add(new JProperty(propertyName, value)); OnPropertyChanged(propertyName); } } } /// /// Loads an from a . /// /// A that will be read for the content of the . /// A that contains the JSON that was read from the specified . public static JObject Load(JsonReader reader) { ValidationUtils.ArgumentNotNull(reader, "reader"); if (reader.TokenType == JsonToken.None) { if (!reader.Read()) throw new Exception("Error reading JObject from JsonReader."); } if (reader.TokenType != JsonToken.StartObject) throw new Exception( "Error reading JObject from JsonReader. Current JsonReader item is not an object: {0}".FormatWith( CultureInfo.InvariantCulture, reader.TokenType)); JObject o = new JObject(); o.SetLineInfo(reader as IJsonLineInfo); if (!reader.Read()) throw new Exception("Error reading JObject from JsonReader."); o.ReadContentFrom(reader); return o; } /// /// Load a from a string that contains JSON. /// /// A that contains JSON. /// A populated from the string that contains JSON. public static JObject Parse(string json) { JsonReader jsonReader = new JsonTextReader(new StringReader(json)); return Load(jsonReader); } /// /// Creates a from an object. /// /// The object that will be used to create . /// A with the values of the specified object public static new JObject FromObject(object o) { return FromObject(o, new JsonSerializer()); } /// /// Creates a from an object. /// /// The object that will be used to create . /// The that will be used to read the object. /// A with the values of the specified object public static new JObject FromObject(object o, JsonSerializer jsonSerializer) { JToken token = FromObjectInternal(o, jsonSerializer); if (token != null && token.Type != JTokenType.Object) throw new ArgumentException("Object serialized to {0}. JObject instance expected.".FormatWith(CultureInfo.InvariantCulture, token.Type)); return (JObject)token; } /// /// Writes this token to a . /// /// A into which this method will write. /// A collection of which will be used when writing the token. public override void WriteTo(JsonWriter writer, params JsonConverter[] converters) { writer.WriteStartObject(); foreach (JProperty property in Properties()) { property.WriteTo(writer, converters); } writer.WriteEndObject(); } #region IDictionary Members /// /// Adds the specified property name. /// /// Name of the property. /// The value. public void Add(string propertyName, JToken value) { Add(new JProperty(propertyName, value)); } bool IDictionary.ContainsKey(string key) { return (Property(key) != null); } ICollection IDictionary.Keys { get { throw new NotImplementedException(); } } /// /// Removes the property with the specified name. /// /// Name of the property. /// true if item was successfully removed; otherwise, false. public bool Remove(string propertyName) { JProperty property = Property(propertyName); if (property == null) return false; property.Remove(); return true; } /// /// Tries the get value. /// /// Name of the property. /// The value. /// true if a value was successfully retrieved; otherwise, false. public bool TryGetValue(string propertyName, out JToken value) { JProperty property = Property(propertyName); if (property == null) { value = null; return false; } value = property.Value; return true; } ICollection IDictionary.Values { get { throw new NotImplementedException(); } } #endregion #region ICollection> Members void ICollection>.Add(KeyValuePair item) { Add(new JProperty(item.Key, item.Value)); } void ICollection>.Clear() { RemoveAll(); } bool ICollection>.Contains(KeyValuePair item) { JProperty property = Property(item.Key); if (property == null) return false; return (property.Value == item.Value); } void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { if (array == null) throw new ArgumentNullException("array"); if (arrayIndex < 0) throw new ArgumentOutOfRangeException("arrayIndex", "arrayIndex is less than 0."); if (arrayIndex >= array.Length) throw new ArgumentException("arrayIndex is equal to or greater than the length of array."); if (Count > array.Length - arrayIndex) throw new ArgumentException("The number of elements in the source JObject is greater than the available space from arrayIndex to the end of the destination array."); int index = 0; foreach (JProperty property in Properties()) { array[arrayIndex + index] = new KeyValuePair(property.Name, property.Value); index++; } } /// /// Gets the number of elements contained in the . /// /// /// The number of elements contained in the . public int Count { get { return Children().Count(); } } bool ICollection>.IsReadOnly { get { return false; } } bool ICollection>.Remove(KeyValuePair item) { if (!((ICollection>)this).Contains(item)) return false; ((IDictionary)this).Remove(item.Key); return true; } #endregion internal override int GetDeepHashCode() { return ContentsHashCode(); } /// /// Returns an enumerator that iterates through the collection. /// /// /// A that can be used to iterate through the collection. /// public IEnumerator> GetEnumerator() { foreach (JProperty property in Properties()) { yield return new KeyValuePair(property.Name, property.Value); } } /// /// Raises the event with the provided arguments. /// /// Name of the property. protected virtual void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } #if !PocketPC && !SILVERLIGHT && !NET20 /// /// Raises the event with the provided arguments. /// /// Name of the property. protected virtual void OnPropertyChanging(string propertyName) { if (PropertyChanging != null) PropertyChanging(this, new PropertyChangingEventArgs(propertyName)); } #endif } }