1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
3 using System.Collections;
4 using System.Collections.Generic;
5 using System.Diagnostics.CodeAnalysis;
6 using System.Runtime.Serialization;
8 using WrappedPair = System.Json.NGenWrapper<System.Collections.Generic.KeyValuePair<string, System.Json.JsonValue>>;
13 /// A JsonObject is an unordered collection of zero or more key/value pairs.
15 /// <remarks>A JsonObject is an unordered collection of zero or more key/value pairs,
16 /// where each key is a String and each value is a <see cref="System.Json.JsonValue"/>, which can be a
17 /// <see cref="System.Json.JsonPrimitive"/>, a <see cref="System.Json.JsonArray"/>, or a JsonObject.</remarks>
18 [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix",
19 Justification = "Object in the context of JSON already conveys the meaning of dictionary")]
21 public sealed class JsonObject : JsonValue, IDictionary<string, JsonValue>
24 private Dictionary<string, JsonValue> values = new Dictionary<string, JsonValue>(StringComparer.Ordinal);
26 private List<WrappedPair> indexedPairs;
27 private int instanceSaveCount;
28 private object saveLock = new object();
31 /// Creates an instance of the <see cref="System.Json.JsonObject"/> class initialized with an
32 /// <see cref="System.Collections.Generic.IEnumerable{T}"/> collection of key/value pairs.
34 /// <param name="items">The <see cref="System.Collections.Generic.IEnumerable{T}"/> collection of
35 /// <see cref="System.Collections.Generic.KeyValuePair{K, V}"/> used to initialize the
36 /// key/value pairs</param>
37 /// <exception cref="System.ArgumentNullException">If items is null.</exception>
38 /// <exception cref="System.ArgumentException">If any of the values in the collection
39 /// is a <see cref="System.Json.JsonValue"/> with <see cref="System.Json.JsonValue.JsonType"/> property of
40 /// value <see cref="F:System.Json.JsonType.Default"/>.</exception>
41 [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures",
42 Justification = "There's no complexity using this design because nested generic type is atomic type not another collection")]
43 public JsonObject(IEnumerable<KeyValuePair<string, JsonValue>> items)
49 /// Creates an instance of the <see cref="System.Json.JsonObject"/> class initialized with a collection of key/value pairs.
51 /// <param name="items">The <see cref="System.Collections.Generic.KeyValuePair{K, V}"/> objects used to initialize the key/value pairs.</param>
52 /// <exception cref="System.ArgumentException">If any of the values in the collection
53 /// is a <see cref="System.Json.JsonValue"/> with <see cref="System.Json.JsonValue.JsonType"/> property of
54 /// value <see cref="F:System.Json.JsonType.Default"/>.</exception>
55 public JsonObject(params KeyValuePair<string, JsonValue>[] items)
64 /// Gets the JSON type of this <see cref="System.Json.JsonObject"/>. The return value
65 /// is always <see cref="F:System.Json.JsonType.Object"/>.
67 public override JsonType JsonType
69 get { return JsonType.Object; }
73 /// Gets a collection that contains the keys in this <see cref="System.Json.JsonObject"/>.
75 public ICollection<string> Keys
77 get { return values.Keys; }
81 /// Gets a collection that contains the values in this <see cref="System.Json.JsonObject"/>.
83 public ICollection<JsonValue> Values
85 get { return values.Values; }
89 /// Returns the number of key/value pairs in this <see cref="System.Json.JsonObject"/>.
91 public override int Count
93 get { return values.Count; }
97 /// Gets a value indicating whether this JSON CLR object is read-only.
99 bool ICollection<KeyValuePair<string, JsonValue>>.IsReadOnly
101 get { return ((ICollection<KeyValuePair<string, JsonValue>>)values).IsReadOnly; }
105 /// Gets or sets the value associated with the specified key.
107 /// <param name="key">The key of the value to get or set.</param>
108 /// <returns>The <see cref="System.Json.JsonValue"/> associated to the specified key.</returns>
109 /// <exception cref="System.ArgumentNullException">If key is null.</exception>
110 /// <exception cref="System.ArgumentException">The property is set and the value is a
111 /// <see cref="System.Json.JsonValue"/> with <see cref="System.Json.JsonValue.JsonType"/>
112 /// property of value <see cref="F:System.Json.JsonType.Default"/>.</exception>
113 public override JsonValue this[string key]
119 throw new ArgumentNullException("key");
127 if (value != null && value.JsonType == JsonType.Default)
129 throw new ArgumentNullException("value", Properties.Resources.UseOfDefaultNotAllowed);
134 throw new ArgumentNullException("key");
137 bool replacement = values.ContainsKey(key);
138 JsonValue oldValue = null;
141 oldValue = values[key];
142 RaiseItemChanging(value, JsonValueChange.Replace, key);
146 RaiseItemChanging(value, JsonValueChange.Add, key);
152 RaiseItemChanged(oldValue, JsonValueChange.Replace, key);
156 RaiseItemChanged(value, JsonValueChange.Add, key);
162 /// Safe string indexer for the <see cref="System.Json.JsonValue"/> type.
164 /// <param name="key">The key of the element to get.</param>
165 /// <returns>If this instance contains the given key and the value corresponding to
166 /// the key is not null, then it will return that value. Otherwise it will return a
167 /// <see cref="System.Json.JsonValue"/> instance with <see cref="System.Json.JsonValue.JsonType"/>
168 /// equals to <see cref="F:System.Json.JsonType.Default"/>.</returns>
169 public override JsonValue ValueOrDefault(string key)
171 if (key != null && ContainsKey(key) && this[key] != null)
176 return base.ValueOrDefault(key);
180 /// Adds a specified collection of key/value pairs to this instance.
182 /// <param name="items">The collection of key/value pairs to add.</param>
183 /// <exception cref="System.ArgumentNullException">If items is null.</exception>
184 /// <exception cref="System.ArgumentException">If the value of any of the items in the collection
185 /// is a <see cref="System.Json.JsonValue"/> with <see cref="System.Json.JsonValue.JsonType"/> property of
186 /// value <see cref="F:System.Json.JsonType.Default"/>.</exception>
187 [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures",
188 Justification = "There's no complexity using this design because nested generic type is atomic type not another collection")]
189 public void AddRange(IEnumerable<KeyValuePair<string, JsonValue>> items)
193 throw new ArgumentNullException("items");
196 if (ChangingListenersCount > 0)
198 foreach (KeyValuePair<string, JsonValue> item in items)
200 RaiseItemChanging(item.Value, JsonValueChange.Add, item.Key);
204 foreach (KeyValuePair<string, JsonValue> item in items)
206 if (item.Value != null && item.Value.JsonType == JsonType.Default)
208 throw new ArgumentNullException("items", Properties.Resources.UseOfDefaultNotAllowed);
211 values.Add(item.Key, item.Value);
212 RaiseItemChanged(item.Value, JsonValueChange.Add, item.Key);
217 /// Adds the elements from an array of type <see cref="System.Json.JsonValue"/> to this instance.
219 /// <param name="items">The array of key/value paris to be added to this instance.</param>
220 /// <exception cref="System.ArgumentException">If the value of any of the items in the array
221 /// is a <see cref="System.Json.JsonValue"/> with <see cref="System.Json.JsonValue.JsonType"/> property of
222 /// value <see cref="F:System.Json.JsonType.Default"/>.</exception>
223 public void AddRange(params KeyValuePair<string, JsonValue>[] items)
225 AddRange(items as IEnumerable<KeyValuePair<string, JsonValue>>);
231 /// <returns></returns>
232 IEnumerator IEnumerable.GetEnumerator()
234 return ((IEnumerable)values).GetEnumerator();
238 /// Adds a key/value pair to this <see cref="System.Json.JsonObject"/> instance.
240 /// <param name="key">The key for the element added.</param>
241 /// <param name="value">The <see cref="System.Json.JsonValue"/> for the element added.</param>
242 /// <exception cref="System.ArgumentException">If the value is a <see cref="System.Json.JsonValue"/>
243 /// with <see cref="System.Json.JsonValue.JsonType"/> property of
244 /// value <see cref="F:System.Json.JsonType.Default"/>.</exception>
245 public void Add(string key, JsonValue value)
247 if (value != null && value.JsonType == JsonType.Default)
249 throw new ArgumentNullException("value", Properties.Resources.UseOfDefaultNotAllowed);
252 RaiseItemChanging(value, JsonValueChange.Add, key);
253 values.Add(key, value);
254 RaiseItemChanged(value, JsonValueChange.Add, key);
258 /// Adds a key/value pair to this <see cref="System.Json.JsonObject"/> instance.
260 /// <param name="item">The key/value pair to be added.</param>
261 /// <exception cref="System.ArgumentException">If the value of the pair is a
262 /// <see cref="System.Json.JsonValue"/> with <see cref="System.Json.JsonValue.JsonType"/>
263 /// property of value <see cref="F:System.Json.JsonType.Default"/>.</exception>
264 public void Add(KeyValuePair<string, JsonValue> item)
266 Add(item.Key, item.Value);
270 /// Checks whether a key/value pair with a specified key exists in this <see cref="System.Json.JsonObject"/> instance.
272 /// <param name="key">The key to check for.</param>
273 /// <returns>true if this instance contains the key; otherwise, false.</returns>
274 public override bool ContainsKey(string key)
276 return values.ContainsKey(key);
280 /// Removes the key/value pair with a specified key from this <see cref="System.Json.JsonObject"/> instance.
282 /// <param name="key">The key of the item to remove.</param>
283 /// <returns>true if the element is successfully found and removed; otherwise, false.
284 /// This method returns false if key is not found in this <see cref="System.Json.JsonObject"/> instance.</returns>
285 public bool Remove(string key)
287 JsonValue original = null;
288 bool containsKey = false;
289 if (ChangingListenersCount > 0 || ChangedListenersCount > 0)
291 containsKey = TryGetValue(key, out original);
294 if (containsKey && ChangingListenersCount > 0)
296 RaiseItemChanging(original, JsonValueChange.Remove, key);
299 bool result = values.Remove(key);
301 if (containsKey && ChangedListenersCount > 0)
303 RaiseItemChanged(original, JsonValueChange.Remove, key);
310 /// Attempts to get the value that corresponds to the specified key.
312 /// <param name="key">The key of the value to retrieve.</param>
313 /// <param name="value">The primitive or structured <see cref="System.Json.JsonValue"/> object that has the key
314 /// specified. If this object does not contain a key/value pair with the given key,
315 /// this parameter is set to null.</param>
316 /// <returns>true if the instance of the <see cref="System.Json.JsonObject"/> contains an element with the
317 /// specified key; otherwise, false.</returns>
318 public bool TryGetValue(string key, out JsonValue value)
320 return values.TryGetValue(key, out value);
324 /// Removes all key/value pairs from this <see cref="System.Json.JsonObject"/> instance.
328 RaiseItemChanging(null, JsonValueChange.Clear, null);
330 RaiseItemChanged(null, JsonValueChange.Clear, null);
333 bool ICollection<KeyValuePair<string, JsonValue>>.Contains(KeyValuePair<string, JsonValue> item)
335 return ((ICollection<KeyValuePair<string, JsonValue>>)values).Contains(item);
339 /// Copies the contents of this <see cref="System.Json.JsonObject"/> instance into a specified
340 /// key/value destination array beginning at a specified index.
342 /// <param name="array">The destination array of type <see cref="System.Collections.Generic.KeyValuePair{K, V}"/>
343 /// to which the elements of this <see cref="System.Json.JsonObject"/> are copied.</param>
344 /// <param name="arrayIndex">The zero-based index at which to begin the insertion of the
345 /// contents from this <see cref="System.Json.JsonObject"/> instance.</param>
346 public void CopyTo(KeyValuePair<string, JsonValue>[] array, int arrayIndex)
348 ((ICollection<KeyValuePair<string, JsonValue>>)values).CopyTo(array, arrayIndex);
351 bool ICollection<KeyValuePair<string, JsonValue>>.Remove(KeyValuePair<string, JsonValue> item)
353 if (ChangingListenersCount > 0)
355 if (ContainsKey(item.Key) && EqualityComparer<JsonValue>.Default.Equals(item.Value, values[item.Key]))
357 RaiseItemChanging(item.Value, JsonValueChange.Remove, item.Key);
361 bool result = ((ICollection<KeyValuePair<string, JsonValue>>)values).Remove(item);
364 RaiseItemChanged(item.Value, JsonValueChange.Remove, item.Key);
371 /// Returns an enumerator over the key/value pairs contained in this <see cref="System.Json.JsonObject"/> instance.
373 /// <returns>An <see cref="System.Collections.Generic.IEnumerator{T}"/> which iterates
374 /// through the members of this instance.</returns>
375 protected override IEnumerator<KeyValuePair<string, JsonValue>> GetKeyValuePairEnumerator()
377 return values.GetEnumerator();
381 /// Callback method called when a Save operation is starting for this instance.
383 protected override void OnSaveStarted()
388 if (indexedPairs == null)
390 indexedPairs = new List<WrappedPair>();
392 foreach (KeyValuePair<string, JsonValue> item in values)
394 indexedPairs.Add(new WrappedPair(item));
401 /// Callback method called when a Save operation is finished for this instance.
403 protected override void OnSaveEnded()
408 if (instanceSaveCount == 0)
416 /// Callback method called to let an instance write the proper JXML attribute when saving this
419 /// <param name="jsonWriter">The JXML writer used to write JSON.</param>
420 internal override void WriteAttributeString(XmlDictionaryWriter jsonWriter)
422 jsonWriter.WriteAttributeString(JXmlToJsonValueConverter.TypeAttributeName, JXmlToJsonValueConverter.ObjectAttributeValue);
426 /// Callback method called during Save operations to let the instance write the start element
427 /// and return the next element in the collection.
429 /// <param name="jsonWriter">The JXML writer used to write JSON.</param>
430 /// <param name="currentIndex">The index within this collection.</param>
431 /// <returns>The next item in the collection, or null of there are no more items.</returns>
432 internal override JsonValue WriteStartElementAndGetNext(XmlDictionaryWriter jsonWriter, int currentIndex)
434 KeyValuePair<string, JsonValue> currentPair = indexedPairs[currentIndex];
435 string currentKey = currentPair.Key;
437 if (currentKey.Length == 0)
439 // special case in JXML world
440 jsonWriter.WriteStartElement(JXmlToJsonValueConverter.ItemElementName, JXmlToJsonValueConverter.ItemElementName);
441 jsonWriter.WriteAttributeString(JXmlToJsonValueConverter.ItemElementName, String.Empty);
445 jsonWriter.WriteStartElement(currentKey);
448 return currentPair.Value;
451 [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", Justification = "Context is required by CLR for this to work.")]
453 private void OnDeserialized(StreamingContext context)
455 saveLock = new object();
458 private void RaiseItemChanging(JsonValue child, JsonValueChange change, string key)
460 if (ChangingListenersCount > 0)
462 RaiseChangingEvent(this, new JsonValueChangeEventArgs(child, change, key));
466 private void RaiseItemChanged(JsonValue child, JsonValueChange change, string key)
468 if (ChangedListenersCount > 0)
470 RaiseChangedEvent(this, new JsonValueChangeEventArgs(child, change, key));