You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

822 lines
30 KiB

  1. #region License
  2. // Copyright (c) 2007 James Newton-King
  3. //
  4. // Permission is hereby granted, free of charge, to any person
  5. // obtaining a copy of this software and associated documentation
  6. // files (the "Software"), to deal in the Software without
  7. // restriction, including without limitation the rights to use,
  8. // copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. // copies of the Software, and to permit persons to whom the
  10. // Software is furnished to do so, subject to the following
  11. // conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be
  14. // included in all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  17. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  18. // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  19. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  20. // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  21. // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  22. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  23. // OTHER DEALINGS IN THE SOFTWARE.
  24. #endregion
  25. using System;
  26. using System.Collections.Generic;
  27. #if !NET20
  28. using System.Collections.ObjectModel;
  29. using System.Collections.Specialized;
  30. #endif
  31. using System.ComponentModel;
  32. #if !NET20
  33. using System.Dynamic;
  34. using System.Linq.Expressions;
  35. #endif
  36. using System.IO;
  37. using Newtonsoft.Json.Utilities;
  38. using System.Globalization;
  39. #if NET20
  40. using Newtonsoft.Json.Utilities.LinqBridge;
  41. #else
  42. using System.Linq;
  43. #endif
  44. namespace Newtonsoft.Json.Linq
  45. {
  46. /// <summary>
  47. /// Represents a JSON object.
  48. /// </summary>
  49. /// <example>
  50. /// <code lang="cs" source="..\Src\Newtonsoft.Json.Tests\Documentation\LinqToJsonTests.cs" region="LinqToJsonCreateParse" title="Parsing a JSON Object from Text" />
  51. /// </example>
  52. internal partial class JObject : JContainer, IDictionary<string, JToken>, INotifyPropertyChanged
  53. , ICustomTypeDescriptor
  54. #if !NET20
  55. , INotifyPropertyChanging
  56. #endif
  57. {
  58. private readonly JPropertyKeyedCollection _properties = new JPropertyKeyedCollection();
  59. /// <summary>
  60. /// Gets the container's children tokens.
  61. /// </summary>
  62. /// <value>The container's children tokens.</value>
  63. protected override IList<JToken> ChildrenTokens => _properties;
  64. /// <summary>
  65. /// Occurs when a property value changes.
  66. /// </summary>
  67. public event PropertyChangedEventHandler PropertyChanged;
  68. #if !NET20
  69. /// <summary>
  70. /// Occurs when a property value is changing.
  71. /// </summary>
  72. public event PropertyChangingEventHandler PropertyChanging;
  73. #endif
  74. /// <summary>
  75. /// Initializes a new instance of the <see cref="JObject"/> class.
  76. /// </summary>
  77. public JObject()
  78. {
  79. }
  80. /// <summary>
  81. /// Initializes a new instance of the <see cref="JObject"/> class from another <see cref="JObject"/> object.
  82. /// </summary>
  83. /// <param name="other">A <see cref="JObject"/> object to copy from.</param>
  84. public JObject(JObject other)
  85. : base(other)
  86. {
  87. }
  88. /// <summary>
  89. /// Initializes a new instance of the <see cref="JObject"/> class with the specified content.
  90. /// </summary>
  91. /// <param name="content">The contents of the object.</param>
  92. public JObject(params object[] content)
  93. : this((object)content)
  94. {
  95. }
  96. /// <summary>
  97. /// Initializes a new instance of the <see cref="JObject"/> class with the specified content.
  98. /// </summary>
  99. /// <param name="content">The contents of the object.</param>
  100. public JObject(object content)
  101. {
  102. Add(content);
  103. }
  104. internal override bool DeepEquals(JToken node)
  105. {
  106. if (!(node is JObject t))
  107. {
  108. return false;
  109. }
  110. return _properties.Compare(t._properties);
  111. }
  112. internal override int IndexOfItem(JToken item)
  113. {
  114. return _properties.IndexOfReference(item);
  115. }
  116. internal override void InsertItem(int index, JToken item, bool skipParentCheck)
  117. {
  118. // don't add comments to JObject, no name to reference comment by
  119. if (item != null && item.Type == JTokenType.Comment)
  120. {
  121. return;
  122. }
  123. base.InsertItem(index, item, skipParentCheck);
  124. }
  125. internal override void ValidateToken(JToken o, JToken existing)
  126. {
  127. ValidationUtils.ArgumentNotNull(o, nameof(o));
  128. if (o.Type != JTokenType.Property)
  129. {
  130. throw new ArgumentException("Can not add {0} to {1}.".FormatWith(CultureInfo.InvariantCulture, o.GetType(), GetType()));
  131. }
  132. JProperty newProperty = (JProperty)o;
  133. if (existing != null)
  134. {
  135. JProperty existingProperty = (JProperty)existing;
  136. if (newProperty.Name == existingProperty.Name)
  137. {
  138. return;
  139. }
  140. }
  141. if (_properties.TryGetValue(newProperty.Name, out existing))
  142. {
  143. throw new ArgumentException("Can not add property {0} to {1}. Property with the same name already exists on object.".FormatWith(CultureInfo.InvariantCulture, newProperty.Name, GetType()));
  144. }
  145. }
  146. internal override void MergeItem(object content, JsonMergeSettings settings)
  147. {
  148. if (!(content is JObject o))
  149. {
  150. return;
  151. }
  152. foreach (KeyValuePair<string, JToken> contentItem in o)
  153. {
  154. JProperty existingProperty = Property(contentItem.Key);
  155. if (existingProperty == null)
  156. {
  157. Add(contentItem.Key, contentItem.Value);
  158. }
  159. else if (contentItem.Value != null)
  160. {
  161. if (!(existingProperty.Value is JContainer existingContainer) || existingContainer.Type != contentItem.Value.Type)
  162. {
  163. if (!IsNull(contentItem.Value) || settings?.MergeNullValueHandling == MergeNullValueHandling.Merge)
  164. {
  165. existingProperty.Value = contentItem.Value;
  166. }
  167. }
  168. else
  169. {
  170. existingContainer.Merge(contentItem.Value, settings);
  171. }
  172. }
  173. }
  174. }
  175. private static bool IsNull(JToken token)
  176. {
  177. if (token.Type == JTokenType.Null)
  178. {
  179. return true;
  180. }
  181. if (token is JValue v && v.Value == null)
  182. {
  183. return true;
  184. }
  185. return false;
  186. }
  187. internal void InternalPropertyChanged(JProperty childProperty)
  188. {
  189. OnPropertyChanged(childProperty.Name);
  190. if (_listChanged != null)
  191. {
  192. OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, IndexOfItem(childProperty)));
  193. }
  194. #if !NET20
  195. if (_collectionChanged != null)
  196. {
  197. OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, childProperty, childProperty, IndexOfItem(childProperty)));
  198. }
  199. #endif
  200. }
  201. internal void InternalPropertyChanging(JProperty childProperty)
  202. {
  203. #if !NET20
  204. OnPropertyChanging(childProperty.Name);
  205. #endif
  206. }
  207. internal override JToken CloneToken()
  208. {
  209. return new JObject(this);
  210. }
  211. /// <summary>
  212. /// Gets the node type for this <see cref="JToken"/>.
  213. /// </summary>
  214. /// <value>The type.</value>
  215. public override JTokenType Type => JTokenType.Object;
  216. /// <summary>
  217. /// Gets an <see cref="IEnumerable{T}"/> of <see cref="JProperty"/> of this object's properties.
  218. /// </summary>
  219. /// <returns>An <see cref="IEnumerable{T}"/> of <see cref="JProperty"/> of this object's properties.</returns>
  220. public IEnumerable<JProperty> Properties()
  221. {
  222. return _properties.Cast<JProperty>();
  223. }
  224. /// <summary>
  225. /// Gets a <see cref="JProperty"/> the specified name.
  226. /// </summary>
  227. /// <param name="name">The property name.</param>
  228. /// <returns>A <see cref="JProperty"/> with the specified name or <c>null</c>.</returns>
  229. public JProperty Property(string name)
  230. {
  231. if (name == null)
  232. {
  233. return null;
  234. }
  235. _properties.TryGetValue(name, out JToken property);
  236. return (JProperty)property;
  237. }
  238. /// <summary>
  239. /// Gets a <see cref="JEnumerable{T}"/> of <see cref="JToken"/> of this object's property values.
  240. /// </summary>
  241. /// <returns>A <see cref="JEnumerable{T}"/> of <see cref="JToken"/> of this object's property values.</returns>
  242. public JEnumerable<JToken> PropertyValues()
  243. {
  244. return new JEnumerable<JToken>(Properties().Select(p => p.Value));
  245. }
  246. /// <summary>
  247. /// Gets the <see cref="JToken"/> with the specified key.
  248. /// </summary>
  249. /// <value>The <see cref="JToken"/> with the specified key.</value>
  250. public override JToken this[object key]
  251. {
  252. get
  253. {
  254. ValidationUtils.ArgumentNotNull(key, nameof(key));
  255. if (!(key is string propertyName))
  256. {
  257. throw new ArgumentException("Accessed JObject values with invalid key value: {0}. Object property name expected.".FormatWith(CultureInfo.InvariantCulture, MiscellaneousUtils.ToString(key)));
  258. }
  259. return this[propertyName];
  260. }
  261. set
  262. {
  263. ValidationUtils.ArgumentNotNull(key, nameof(key));
  264. if (!(key is string propertyName))
  265. {
  266. throw new ArgumentException("Set JObject values with invalid key value: {0}. Object property name expected.".FormatWith(CultureInfo.InvariantCulture, MiscellaneousUtils.ToString(key)));
  267. }
  268. this[propertyName] = value;
  269. }
  270. }
  271. /// <summary>
  272. /// Gets or sets the <see cref="JToken"/> with the specified property name.
  273. /// </summary>
  274. /// <value></value>
  275. public JToken this[string propertyName]
  276. {
  277. get
  278. {
  279. ValidationUtils.ArgumentNotNull(propertyName, nameof(propertyName));
  280. JProperty property = Property(propertyName);
  281. return property?.Value;
  282. }
  283. set
  284. {
  285. JProperty property = Property(propertyName);
  286. if (property != null)
  287. {
  288. property.Value = value;
  289. }
  290. else
  291. {
  292. #if !NET20
  293. OnPropertyChanging(propertyName);
  294. #endif
  295. Add(new JProperty(propertyName, value));
  296. OnPropertyChanged(propertyName);
  297. }
  298. }
  299. }
  300. /// <summary>
  301. /// Loads a <see cref="JObject"/> from a <see cref="JsonReader"/>.
  302. /// </summary>
  303. /// <param name="reader">A <see cref="JsonReader"/> that will be read for the content of the <see cref="JObject"/>.</param>
  304. /// <returns>A <see cref="JObject"/> that contains the JSON that was read from the specified <see cref="JsonReader"/>.</returns>
  305. /// <exception cref="JsonReaderException">
  306. /// <paramref name="reader"/> is not valid JSON.
  307. /// </exception>
  308. public new static JObject Load(JsonReader reader)
  309. {
  310. return Load(reader, null);
  311. }
  312. /// <summary>
  313. /// Loads a <see cref="JObject"/> from a <see cref="JsonReader"/>.
  314. /// </summary>
  315. /// <param name="reader">A <see cref="JsonReader"/> that will be read for the content of the <see cref="JObject"/>.</param>
  316. /// <param name="settings">The <see cref="JsonLoadSettings"/> used to load the JSON.
  317. /// If this is <c>null</c>, default load settings will be used.</param>
  318. /// <returns>A <see cref="JObject"/> that contains the JSON that was read from the specified <see cref="JsonReader"/>.</returns>
  319. /// <exception cref="JsonReaderException">
  320. /// <paramref name="reader"/> is not valid JSON.
  321. /// </exception>
  322. public new static JObject Load(JsonReader reader, JsonLoadSettings settings)
  323. {
  324. ValidationUtils.ArgumentNotNull(reader, nameof(reader));
  325. if (reader.TokenType == JsonToken.None)
  326. {
  327. if (!reader.Read())
  328. {
  329. throw JsonReaderException.Create(reader, "Error reading JObject from JsonReader.");
  330. }
  331. }
  332. reader.MoveToContent();
  333. if (reader.TokenType != JsonToken.StartObject)
  334. {
  335. throw JsonReaderException.Create(reader, "Error reading JObject from JsonReader. Current JsonReader item is not an object: {0}".FormatWith(CultureInfo.InvariantCulture, reader.TokenType));
  336. }
  337. JObject o = new JObject();
  338. o.SetLineInfo(reader as IJsonLineInfo, settings);
  339. o.ReadTokenFrom(reader, settings);
  340. return o;
  341. }
  342. /// <summary>
  343. /// Load a <see cref="JObject"/> from a string that contains JSON.
  344. /// </summary>
  345. /// <param name="json">A <see cref="String"/> that contains JSON.</param>
  346. /// <returns>A <see cref="JObject"/> populated from the string that contains JSON.</returns>
  347. /// <exception cref="JsonReaderException">
  348. /// <paramref name="json"/> is not valid JSON.
  349. /// </exception>
  350. /// <example>
  351. /// <code lang="cs" source="..\Src\Newtonsoft.Json.Tests\Documentation\LinqToJsonTests.cs" region="LinqToJsonCreateParse" title="Parsing a JSON Object from Text" />
  352. /// </example>
  353. public new static JObject Parse(string json)
  354. {
  355. return Parse(json, null);
  356. }
  357. /// <summary>
  358. /// Load a <see cref="JObject"/> from a string that contains JSON.
  359. /// </summary>
  360. /// <param name="json">A <see cref="String"/> that contains JSON.</param>
  361. /// <param name="settings">The <see cref="JsonLoadSettings"/> used to load the JSON.
  362. /// If this is <c>null</c>, default load settings will be used.</param>
  363. /// <returns>A <see cref="JObject"/> populated from the string that contains JSON.</returns>
  364. /// <exception cref="JsonReaderException">
  365. /// <paramref name="json"/> is not valid JSON.
  366. /// </exception>
  367. /// <example>
  368. /// <code lang="cs" source="..\Src\Newtonsoft.Json.Tests\Documentation\LinqToJsonTests.cs" region="LinqToJsonCreateParse" title="Parsing a JSON Object from Text" />
  369. /// </example>
  370. public new static JObject Parse(string json, JsonLoadSettings settings)
  371. {
  372. using (JsonReader reader = new JsonTextReader(new StringReader(json)))
  373. {
  374. JObject o = Load(reader, settings);
  375. while (reader.Read())
  376. {
  377. // Any content encountered here other than a comment will throw in the reader.
  378. }
  379. return o;
  380. }
  381. }
  382. /// <summary>
  383. /// Creates a <see cref="JObject"/> from an object.
  384. /// </summary>
  385. /// <param name="o">The object that will be used to create <see cref="JObject"/>.</param>
  386. /// <returns>A <see cref="JObject"/> with the values of the specified object.</returns>
  387. public new static JObject FromObject(object o)
  388. {
  389. return FromObject(o, JsonSerializer.CreateDefault());
  390. }
  391. /// <summary>
  392. /// Creates a <see cref="JObject"/> from an object.
  393. /// </summary>
  394. /// <param name="o">The object that will be used to create <see cref="JObject"/>.</param>
  395. /// <param name="jsonSerializer">The <see cref="JsonSerializer"/> that will be used to read the object.</param>
  396. /// <returns>A <see cref="JObject"/> with the values of the specified object.</returns>
  397. public new static JObject FromObject(object o, JsonSerializer jsonSerializer)
  398. {
  399. JToken token = FromObjectInternal(o, jsonSerializer);
  400. if (token != null && token.Type != JTokenType.Object)
  401. {
  402. throw new ArgumentException("Object serialized to {0}. JObject instance expected.".FormatWith(CultureInfo.InvariantCulture, token.Type));
  403. }
  404. return (JObject)token;
  405. }
  406. /// <summary>
  407. /// Writes this token to a <see cref="JsonWriter"/>.
  408. /// </summary>
  409. /// <param name="writer">A <see cref="JsonWriter"/> into which this method will write.</param>
  410. /// <param name="converters">A collection of <see cref="JsonConverter"/> which will be used when writing the token.</param>
  411. public override void WriteTo(JsonWriter writer, params JsonConverter[] converters)
  412. {
  413. writer.WriteStartObject();
  414. for (int i = 0; i < _properties.Count; i++)
  415. {
  416. _properties[i].WriteTo(writer, converters);
  417. }
  418. writer.WriteEndObject();
  419. }
  420. /// <summary>
  421. /// Gets the <see cref="Newtonsoft.Json.Linq.JToken"/> with the specified property name.
  422. /// </summary>
  423. /// <param name="propertyName">Name of the property.</param>
  424. /// <returns>The <see cref="Newtonsoft.Json.Linq.JToken"/> with the specified property name.</returns>
  425. public JToken GetValue(string propertyName)
  426. {
  427. return GetValue(propertyName, StringComparison.Ordinal);
  428. }
  429. /// <summary>
  430. /// Gets the <see cref="Newtonsoft.Json.Linq.JToken"/> with the specified property name.
  431. /// The exact property name will be searched for first and if no matching property is found then
  432. /// the <see cref="StringComparison"/> will be used to match a property.
  433. /// </summary>
  434. /// <param name="propertyName">Name of the property.</param>
  435. /// <param name="comparison">One of the enumeration values that specifies how the strings will be compared.</param>
  436. /// <returns>The <see cref="Newtonsoft.Json.Linq.JToken"/> with the specified property name.</returns>
  437. public JToken GetValue(string propertyName, StringComparison comparison)
  438. {
  439. if (propertyName == null)
  440. {
  441. return null;
  442. }
  443. // attempt to get value via dictionary first for performance
  444. JProperty property = Property(propertyName);
  445. if (property != null)
  446. {
  447. return property.Value;
  448. }
  449. // test above already uses this comparison so no need to repeat
  450. if (comparison != StringComparison.Ordinal)
  451. {
  452. foreach (JProperty p in _properties)
  453. {
  454. if (string.Equals(p.Name, propertyName, comparison))
  455. {
  456. return p.Value;
  457. }
  458. }
  459. }
  460. return null;
  461. }
  462. /// <summary>
  463. /// Tries to get the <see cref="Newtonsoft.Json.Linq.JToken"/> with the specified property name.
  464. /// The exact property name will be searched for first and if no matching property is found then
  465. /// the <see cref="StringComparison"/> will be used to match a property.
  466. /// </summary>
  467. /// <param name="propertyName">Name of the property.</param>
  468. /// <param name="value">The value.</param>
  469. /// <param name="comparison">One of the enumeration values that specifies how the strings will be compared.</param>
  470. /// <returns><c>true</c> if a value was successfully retrieved; otherwise, <c>false</c>.</returns>
  471. public bool TryGetValue(string propertyName, StringComparison comparison, out JToken value)
  472. {
  473. value = GetValue(propertyName, comparison);
  474. return (value != null);
  475. }
  476. #region IDictionary<string,JToken> Members
  477. /// <summary>
  478. /// Adds the specified property name.
  479. /// </summary>
  480. /// <param name="propertyName">Name of the property.</param>
  481. /// <param name="value">The value.</param>
  482. public void Add(string propertyName, JToken value)
  483. {
  484. Add(new JProperty(propertyName, value));
  485. }
  486. /// <summary>
  487. /// Determines whether the JSON object has the specified property name.
  488. /// </summary>
  489. /// <param name="propertyName">Name of the property.</param>
  490. /// <returns><c>true</c> if the JSON object has the specified property name; otherwise, <c>false</c>.</returns>
  491. public bool ContainsKey(string propertyName)
  492. {
  493. ValidationUtils.ArgumentNotNull(propertyName, nameof(propertyName));
  494. return _properties.Contains(propertyName);
  495. }
  496. ICollection<string> IDictionary<string, JToken>.Keys => _properties.Keys;
  497. /// <summary>
  498. /// Removes the property with the specified name.
  499. /// </summary>
  500. /// <param name="propertyName">Name of the property.</param>
  501. /// <returns><c>true</c> if item was successfully removed; otherwise, <c>false</c>.</returns>
  502. public bool Remove(string propertyName)
  503. {
  504. JProperty property = Property(propertyName);
  505. if (property == null)
  506. {
  507. return false;
  508. }
  509. property.Remove();
  510. return true;
  511. }
  512. /// <summary>
  513. /// Tries to get the <see cref="Newtonsoft.Json.Linq.JToken"/> with the specified property name.
  514. /// </summary>
  515. /// <param name="propertyName">Name of the property.</param>
  516. /// <param name="value">The value.</param>
  517. /// <returns><c>true</c> if a value was successfully retrieved; otherwise, <c>false</c>.</returns>
  518. public bool TryGetValue(string propertyName, out JToken value)
  519. {
  520. JProperty property = Property(propertyName);
  521. if (property == null)
  522. {
  523. value = null;
  524. return false;
  525. }
  526. value = property.Value;
  527. return true;
  528. }
  529. ICollection<JToken> IDictionary<string, JToken>.Values => throw new NotImplementedException();
  530. #endregion
  531. #region ICollection<KeyValuePair<string,JToken>> Members
  532. void ICollection<KeyValuePair<string, JToken>>.Add(KeyValuePair<string, JToken> item)
  533. {
  534. Add(new JProperty(item.Key, item.Value));
  535. }
  536. void ICollection<KeyValuePair<string, JToken>>.Clear()
  537. {
  538. RemoveAll();
  539. }
  540. bool ICollection<KeyValuePair<string, JToken>>.Contains(KeyValuePair<string, JToken> item)
  541. {
  542. JProperty property = Property(item.Key);
  543. if (property == null)
  544. {
  545. return false;
  546. }
  547. return (property.Value == item.Value);
  548. }
  549. void ICollection<KeyValuePair<string, JToken>>.CopyTo(KeyValuePair<string, JToken>[] array, int arrayIndex)
  550. {
  551. if (array == null)
  552. {
  553. throw new ArgumentNullException(nameof(array));
  554. }
  555. if (arrayIndex < 0)
  556. {
  557. throw new ArgumentOutOfRangeException(nameof(arrayIndex), "arrayIndex is less than 0.");
  558. }
  559. if (arrayIndex >= array.Length && arrayIndex != 0)
  560. {
  561. throw new ArgumentException("arrayIndex is equal to or greater than the length of array.");
  562. }
  563. if (Count > array.Length - arrayIndex)
  564. {
  565. 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.");
  566. }
  567. int index = 0;
  568. foreach (JProperty property in _properties)
  569. {
  570. array[arrayIndex + index] = new KeyValuePair<string, JToken>(property.Name, property.Value);
  571. index++;
  572. }
  573. }
  574. bool ICollection<KeyValuePair<string, JToken>>.IsReadOnly => false;
  575. bool ICollection<KeyValuePair<string, JToken>>.Remove(KeyValuePair<string, JToken> item)
  576. {
  577. if (!((ICollection<KeyValuePair<string, JToken>>)this).Contains(item))
  578. {
  579. return false;
  580. }
  581. ((IDictionary<string, JToken>)this).Remove(item.Key);
  582. return true;
  583. }
  584. #endregion
  585. internal override int GetDeepHashCode()
  586. {
  587. return ContentsHashCode();
  588. }
  589. /// <summary>
  590. /// Returns an enumerator that can be used to iterate through the collection.
  591. /// </summary>
  592. /// <returns>
  593. /// A <see cref="IEnumerator{T}"/> that can be used to iterate through the collection.
  594. /// </returns>
  595. public IEnumerator<KeyValuePair<string, JToken>> GetEnumerator()
  596. {
  597. foreach (JProperty property in _properties)
  598. {
  599. yield return new KeyValuePair<string, JToken>(property.Name, property.Value);
  600. }
  601. }
  602. /// <summary>
  603. /// Raises the <see cref="PropertyChanged"/> event with the provided arguments.
  604. /// </summary>
  605. /// <param name="propertyName">Name of the property.</param>
  606. protected virtual void OnPropertyChanged(string propertyName)
  607. {
  608. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  609. }
  610. #if !NET20
  611. /// <summary>
  612. /// Raises the <see cref="PropertyChanging"/> event with the provided arguments.
  613. /// </summary>
  614. /// <param name="propertyName">Name of the property.</param>
  615. protected virtual void OnPropertyChanging(string propertyName)
  616. {
  617. PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
  618. }
  619. #endif
  620. // include custom type descriptor on JObject rather than use a provider because the properties are specific to a type
  621. #region ICustomTypeDescriptor
  622. PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
  623. {
  624. return ((ICustomTypeDescriptor)this).GetProperties(null);
  625. }
  626. PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
  627. {
  628. PropertyDescriptorCollection descriptors = new PropertyDescriptorCollection(null);
  629. foreach (KeyValuePair<string, JToken> propertyValue in this)
  630. {
  631. descriptors.Add(new JPropertyDescriptor(propertyValue.Key));
  632. }
  633. return descriptors;
  634. }
  635. AttributeCollection ICustomTypeDescriptor.GetAttributes()
  636. {
  637. return AttributeCollection.Empty;
  638. }
  639. string ICustomTypeDescriptor.GetClassName()
  640. {
  641. return null;
  642. }
  643. string ICustomTypeDescriptor.GetComponentName()
  644. {
  645. return null;
  646. }
  647. TypeConverter ICustomTypeDescriptor.GetConverter()
  648. {
  649. return new TypeConverter();
  650. }
  651. EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
  652. {
  653. return null;
  654. }
  655. PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
  656. {
  657. return null;
  658. }
  659. object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
  660. {
  661. return null;
  662. }
  663. EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
  664. {
  665. return EventDescriptorCollection.Empty;
  666. }
  667. EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
  668. {
  669. return EventDescriptorCollection.Empty;
  670. }
  671. object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
  672. {
  673. return null;
  674. }
  675. #endregion
  676. #if !NET20
  677. /// <summary>
  678. /// Returns the <see cref="DynamicMetaObject"/> responsible for binding operations performed on this object.
  679. /// </summary>
  680. /// <param name="parameter">The expression tree representation of the runtime value.</param>
  681. /// <returns>
  682. /// The <see cref="DynamicMetaObject"/> to bind this object.
  683. /// </returns>
  684. protected override DynamicMetaObject GetMetaObject(Expression parameter)
  685. {
  686. return new DynamicProxyMetaObject<JObject>(parameter, this, new JObjectDynamicProxy());
  687. }
  688. private class JObjectDynamicProxy : DynamicProxy<JObject>
  689. {
  690. public override bool TryGetMember(JObject instance, GetMemberBinder binder, out object result)
  691. {
  692. // result can be null
  693. result = instance[binder.Name];
  694. return true;
  695. }
  696. public override bool TrySetMember(JObject instance, SetMemberBinder binder, object value)
  697. {
  698. // this can throw an error if value isn't a valid for a JValue
  699. if (!(value is JToken v))
  700. {
  701. v = new JValue(value);
  702. }
  703. instance[binder.Name] = v;
  704. return true;
  705. }
  706. public override IEnumerable<string> GetDynamicMemberNames(JObject instance)
  707. {
  708. return instance.Properties().Select(p => p.Name);
  709. }
  710. }
  711. #endif
  712. }
  713. }