22{
33 using System ;
44 using System . Collections . Generic ;
5+ using System . Diagnostics ;
56 using System . Linq ;
67 using net . openstack . Core . Collections ;
78 using Newtonsoft . Json ;
89 using Newtonsoft . Json . Linq ;
10+ using IEnumerable = System . Collections . IEnumerable ;
11+ using IEnumerator = System . Collections . IEnumerator ;
912
1013 /// <summary>
1114 /// This is the abstract base class for types modeling the JSON representation of a resource
@@ -27,7 +30,6 @@ public abstract class ExtensibleJsonObject
2730 /// <summary>
2831 /// This is the backing field for the <see cref="ExtensionData"/> property.
2932 /// </summary>
30- [ JsonExtensionData ]
3133 private Dictionary < string , JToken > _extensionData ;
3234
3335 /// <summary>
@@ -113,5 +115,274 @@ public ReadOnlyDictionary<string, JToken> ExtensionData
113115 return new ReadOnlyDictionary < string , JToken > ( _extensionData ) ;
114116 }
115117 }
118+
119+ /// <summary>
120+ /// This property exposes the <see cref="_extensionData"/> field to Json.NET as a dictionary with
121+ /// <see cref="object"/> values instead of <see cref="JToken"/> values, which works around a known bug in the
122+ /// way Json.NET 5.x handled <see langword="null"/> values in the extension data.
123+ /// </summary>
124+ [ JsonExtensionData ]
125+ [ DebuggerBrowsable ( DebuggerBrowsableState . Never ) ]
126+ private ExtensionDataDictionary ExtensionDataWrapper
127+ {
128+ get
129+ {
130+ // This can never return null or Json.NET will attempt to set the value.
131+ return new ExtensionDataDictionary ( this ) ;
132+ }
133+
134+ set
135+ {
136+ // This setter must exist or Json.NET will not recognize the extension data. It cannot be used because
137+ // Json.NET will bypass the getter, resulting in a lost update.
138+ throw new NotSupportedException ( "Attempted to set the extension data wrapper. See issue openstacknetsdk/openstack.net#419." ) ;
139+ }
140+ }
141+
142+ /// <summary>
143+ /// Converts an object to a <see cref="JToken"/>.
144+ /// </summary>
145+ /// <remarks>
146+ /// <para>
147+ /// Unlike <see cref="JToken.FromObject(object)"/>, this method supports <see langword="null"/> values.
148+ /// </para>
149+ /// </remarks>
150+ /// <param name="obj">The object.</param>
151+ /// <returns>
152+ /// <para>The result of calling <see cref="JToken.FromObject(object)"/> on the input object.</para>
153+ /// <para>-or-</para>
154+ /// <para><see langword="null"/> if <paramref name="obj"/> is <see langword="null"/>.</para>
155+ /// </returns>
156+ private static JToken ToJToken ( object obj )
157+ {
158+ if ( obj == null )
159+ return null ;
160+
161+ return JToken . FromObject ( obj ) ;
162+ }
163+
164+ /// <summary>
165+ /// This class works around a known bug in Json.NET's handling of JSON extension data.
166+ /// </summary>
167+ /// <remarks>
168+ /// <para>Adding values to the underlying dictionary requires converting the value to a <see cref="JToken"/> by
169+ /// calling <see cref="ToJToken(object)"/>. Reading values does not require the inverse because the serializer
170+ /// in Json.NET has no trouble handling <see cref="JToken"/> values as input.</para>
171+ /// </remarks>
172+ /// <seealso cref="ExtensionDataWrapper"/>
173+ private sealed class ExtensionDataDictionary : IDictionary < string , object >
174+ {
175+ private readonly ExtensibleJsonObject _underlying ;
176+
177+ [ JsonConstructor ]
178+ private ExtensionDataDictionary ( )
179+ {
180+ // This constructor must exist or Json.NET will not be able to set the extension data. It cannot be used
181+ // because Json.NET will not set the required _underlying field.
182+ throw new NotSupportedException ( "Attempted to create the extension data wrapper with its underlying object. See issue openstacknetsdk/openstack.net#419." ) ;
183+ }
184+
185+ public ExtensionDataDictionary ( ExtensibleJsonObject extensibleJsonObject )
186+ {
187+ if ( extensibleJsonObject == null )
188+ throw new ArgumentNullException ( "extensibleJsonObject" ) ;
189+
190+ _underlying = extensibleJsonObject ;
191+ }
192+
193+ public object this [ string key ]
194+ {
195+ get
196+ {
197+ return _underlying . ExtensionData [ key ] ;
198+ }
199+
200+ set
201+ {
202+ GetOrCreateExtensionData ( ) [ key ] = ToJToken ( value ) ;
203+ }
204+ }
205+
206+ public int Count
207+ {
208+ get
209+ {
210+ return _underlying . ExtensionData . Count ;
211+ }
212+ }
213+
214+ public bool IsReadOnly
215+ {
216+ get
217+ {
218+ return false ;
219+ }
220+ }
221+
222+ public ICollection < string > Keys
223+ {
224+ get
225+ {
226+ return _underlying . ExtensionData . Keys ;
227+ }
228+ }
229+
230+ public ICollection < object > Values
231+ {
232+ get
233+ {
234+ return new ExtensionDataValues ( _underlying . ExtensionData . Values ) ;
235+ }
236+ }
237+
238+ public void Add ( KeyValuePair < string , object > item )
239+ {
240+ IDictionary < string , JToken > extensionData = GetOrCreateExtensionData ( ) ;
241+ extensionData . Add ( new KeyValuePair < string , JToken > ( item . Key , ToJToken ( item . Value ) ) ) ;
242+ }
243+
244+ public void Add ( string key , object value )
245+ {
246+ GetOrCreateExtensionData ( ) . Add ( key , ToJToken ( value ) ) ;
247+ }
248+
249+ public void Clear ( )
250+ {
251+ GetOrCreateExtensionData ( ) . Clear ( ) ;
252+ }
253+
254+ public bool Contains ( KeyValuePair < string , object > item )
255+ {
256+ return _underlying . ExtensionData . Contains ( new KeyValuePair < string , JToken > ( item . Key , ToJToken ( item . Value ) ) ) ;
257+ }
258+
259+ public bool ContainsKey ( string key )
260+ {
261+ return _underlying . ExtensionData . ContainsKey ( key ) ;
262+ }
263+
264+ public void CopyTo ( KeyValuePair < string , object > [ ] array , int arrayIndex )
265+ {
266+ IDictionary < string , object > intermediate = new Dictionary < string , object > ( _underlying . ExtensionData . ToDictionary ( i => i . Key , i => ( object ) i . Value ) ) ;
267+ intermediate . CopyTo ( array , arrayIndex ) ;
268+ }
269+
270+ public IEnumerator < KeyValuePair < string , object > > GetEnumerator ( )
271+ {
272+ return _underlying . ExtensionData . Select ( i => new KeyValuePair < string , object > ( i . Key , i . Value ) ) . GetEnumerator ( ) ;
273+ }
274+
275+ public bool Remove ( KeyValuePair < string , object > item )
276+ {
277+ IDictionary < string , JToken > extensionData = _underlying . _extensionData ;
278+ if ( extensionData == null )
279+ return false ;
280+
281+ return extensionData . Remove ( new KeyValuePair < string , JToken > ( item . Key , ToJToken ( item . Value ) ) ) ;
282+ }
283+
284+ public bool Remove ( string key )
285+ {
286+ var extensionData = _underlying . _extensionData ;
287+ if ( extensionData == null )
288+ return false ;
289+
290+ return extensionData . Remove ( key ) ;
291+ }
292+
293+ public bool TryGetValue ( string key , out object value )
294+ {
295+ JToken intermediate ;
296+ bool result = _underlying . ExtensionData . TryGetValue ( key , out intermediate ) ;
297+ value = intermediate ;
298+ return result ;
299+ }
300+
301+ IEnumerator IEnumerable . GetEnumerator ( )
302+ {
303+ return GetEnumerator ( ) ;
304+ }
305+
306+ private Dictionary < string , JToken > GetOrCreateExtensionData ( )
307+ {
308+ var result = _underlying . _extensionData ;
309+ if ( result == null )
310+ {
311+ result = new Dictionary < string , JToken > ( ) ;
312+ _underlying . _extensionData = result ;
313+ }
314+
315+ return result ;
316+ }
317+ }
318+
319+ /// <summary>
320+ /// This class works around a known bug in Json.NET's handling of JSON extension data.
321+ /// </summary>
322+ /// <seealso cref="ExtensionDataWrapper"/>
323+ private class ExtensionDataValues : ICollection < object >
324+ {
325+ private readonly ICollection < JToken > _values ;
326+
327+ public ExtensionDataValues ( ICollection < JToken > values )
328+ {
329+ if ( values == null )
330+ throw new ArgumentNullException ( "values" ) ;
331+
332+ _values = values ;
333+ }
334+
335+ public int Count
336+ {
337+ get
338+ {
339+ return _values . Count ;
340+ }
341+ }
342+
343+ public bool IsReadOnly
344+ {
345+ get
346+ {
347+ return _values . IsReadOnly ;
348+ }
349+ }
350+
351+ public void Add ( object item )
352+ {
353+ _values . Add ( ToJToken ( item ) ) ;
354+ }
355+
356+ public void Clear ( )
357+ {
358+ _values . Clear ( ) ;
359+ }
360+
361+ public bool Contains ( object item )
362+ {
363+ return _values . Contains ( ToJToken ( item ) ) ;
364+ }
365+
366+ public void CopyTo ( object [ ] array , int arrayIndex )
367+ {
368+ ICollection < object > intermediate = _values . ToArray ( ) ;
369+ intermediate . CopyTo ( array , arrayIndex ) ;
370+ }
371+
372+ public IEnumerator < object > GetEnumerator ( )
373+ {
374+ return _values . Cast < object > ( ) . GetEnumerator ( ) ;
375+ }
376+
377+ public bool Remove ( object item )
378+ {
379+ return _values . Remove ( ToJToken ( item ) ) ;
380+ }
381+
382+ IEnumerator IEnumerable . GetEnumerator ( )
383+ {
384+ return GetEnumerator ( ) ;
385+ }
386+ }
116387 }
117388}
0 commit comments