2d56b20edb7e8fba8830d47e1be947019a1d008f
[platform/upstream/dotnet/runtime.git] /
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4
5 using System.Collections;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Text.Json.Serialization.Converters;
9
10 namespace System.Text.Json
11 {
12     public static partial class JsonSerializer
13     {
14         private static void HandleStartArray(
15             JsonSerializerOptions options,
16             ref Utf8JsonReader reader,
17             ref ReadStack state)
18         {
19             if (state.Current.SkipProperty)
20             {
21                 // The array is not being applied to the object.
22                 state.Push();
23                 state.Current.Drain = true;
24                 return;
25             }
26
27             JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
28             if (jsonPropertyInfo == null)
29             {
30                 jsonPropertyInfo = state.Current.JsonClassInfo.CreateRootObject(options);
31             }
32             else if (state.Current.JsonClassInfo.ClassType == ClassType.Unknown)
33             {
34                 jsonPropertyInfo = state.Current.JsonClassInfo.CreatePolymorphicProperty(jsonPropertyInfo, typeof(object), options);
35             }
36
37             // Verify that we have a valid enumerable.
38             Type arrayType = jsonPropertyInfo.RuntimePropertyType;
39             if (!typeof(IEnumerable).IsAssignableFrom(arrayType))
40             {
41                 ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(arrayType);
42             }
43
44             Debug.Assert(state.Current.IsProcessingCollection());
45
46             if (state.Current.CollectionPropertyInitialized)
47             {
48                 // A nested json array so push a new stack frame.
49                 Type elementType = jsonPropertyInfo.ElementClassInfo.Type;
50
51                 state.Push();
52                 state.Current.Initialize(elementType, options);
53             }
54
55             state.Current.CollectionPropertyInitialized = true;
56
57             // We should not be processing custom converters here (converters are of ClassType.Value).
58             Debug.Assert(state.Current.JsonClassInfo.ClassType != ClassType.Value);
59
60             // Set or replace the existing enumerable value.
61             object value = ReadStackFrame.CreateEnumerableValue(ref reader, ref state);
62
63             // If value is not null, then we don't have a converter so apply the value.
64             if (value != null)
65             {
66                 if (state.Current.ReturnValue != null)
67                 {
68                     state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
69                 }
70                 else
71                 {
72                     // Primitive arrays being returned without object
73                     state.Current.SetReturnValue(value);
74                 }
75             }
76         }
77
78         private static bool HandleEndArray(
79             JsonSerializerOptions options,
80             ref ReadStack state)
81         {
82             bool lastFrame = state.IsLastFrame;
83
84             if (state.Current.Drain)
85             {
86                 // The array is not being applied to the object.
87                 state.Pop();
88                 return lastFrame;
89             }
90
91             IEnumerable value = ReadStackFrame.GetEnumerableValue(ref state.Current);
92
93             if (state.Current.TempEnumerableValues != null)
94             {
95                 // We have a converter; possibilities:
96                 // - Add value to current frame's current property or TempEnumerableValues.
97                 // - Add value to previous frame's current property or TempEnumerableValues.
98                 // - Set current property on current frame to value.
99                 // - Set current property on previous frame to value.
100                 // - Set ReturnValue if root frame and value is the actual return value.
101                 JsonEnumerableConverter converter = state.Current.JsonPropertyInfo.EnumerableConverter;
102                 Debug.Assert(converter != null);
103
104                 value = converter.CreateFromList(ref state, (IList)value, options);
105                 state.Current.TempEnumerableValues = null;
106             }
107             else if (state.Current.IsProcessingProperty(ClassType.Enumerable))
108             {
109                 // We added the items to the list already.
110                 state.Current.EndProperty();
111                 return false;
112             }
113
114             if (lastFrame)
115             {
116                 if (state.Current.ReturnValue == null)
117                 {
118                     // Returning a converted list or object.
119                     state.Current.Reset();
120                     state.Current.ReturnValue = value;
121                     return true;
122                 }
123                 else if (state.Current.IsProcessingCollectionObject())
124                 {
125                     // Returning a non-converted list.
126                     return true;
127                 }
128                 // else there must be an outer object, so we'll return false here.
129             }
130             else if (state.Current.IsProcessingObject(ClassType.Enumerable))
131             {
132                 state.Pop();
133             }
134
135             ApplyObjectToEnumerable(value, ref state);
136
137             return false;
138         }
139
140         // If this method is changed, also change ApplyValueToEnumerable.
141         internal static void ApplyObjectToEnumerable(
142             object value,
143             ref ReadStack state,
144             bool setPropertyDirectly = false)
145         {
146             Debug.Assert(!state.Current.SkipProperty);
147
148             if (state.Current.IsProcessingObject(ClassType.Enumerable))
149             {
150                 if (state.Current.TempEnumerableValues != null)
151                 {
152                     state.Current.TempEnumerableValues.Add(value);
153                 }
154                 else
155                 {
156                     if (!(state.Current.ReturnValue is IList list))
157                     {
158                         ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(value.GetType());
159                         return;
160                     }
161                     list.Add(value);
162                 }
163             }
164             else if (!setPropertyDirectly && state.Current.IsProcessingProperty(ClassType.Enumerable))
165             {
166                 Debug.Assert(state.Current.JsonPropertyInfo != null);
167                 Debug.Assert(state.Current.ReturnValue != null);
168                 if (state.Current.TempEnumerableValues != null)
169                 {
170                     state.Current.TempEnumerableValues.Add(value);
171                 }
172                 else
173                 {
174                     IList list = (IList)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
175                     if (list == null ||
176                         // ImmutableArray<T> is a struct, so default value won't be null.
177                         state.Current.JsonPropertyInfo.RuntimePropertyType.FullName.StartsWith(DefaultImmutableEnumerableConverter.ImmutableArrayGenericTypeName))
178                     {
179                         state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
180                     }
181                     else
182                     {
183                         list.Add(value);
184                     }
185                 }
186             }
187             else if (state.Current.IsProcessingObject(ClassType.Dictionary) ||
188                 (state.Current.IsProcessingProperty(ClassType.Dictionary) && !setPropertyDirectly))
189             {
190                 Debug.Assert(state.Current.ReturnValue != null);
191                 IDictionary dictionary = (IDictionary)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
192
193                 string key = state.Current.KeyName;
194                 Debug.Assert(!string.IsNullOrEmpty(key));
195                 dictionary[key] = value;
196             }
197             else if (state.Current.IsProcessingObject(ClassType.IDictionaryConstructible) ||
198                 (state.Current.IsProcessingProperty(ClassType.IDictionaryConstructible) && !setPropertyDirectly))
199             {
200                 Debug.Assert(state.Current.TempDictionaryValues != null);
201                 IDictionary dictionary = (IDictionary)state.Current.TempDictionaryValues;
202
203                 string key = state.Current.KeyName;
204                 Debug.Assert(!string.IsNullOrEmpty(key));
205                 dictionary[key] = value;
206             }
207             else
208             {
209                 Debug.Assert(state.Current.JsonPropertyInfo != null);
210                 state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
211             }
212         }
213
214         // If this method is changed, also change ApplyObjectToEnumerable.
215         internal static void ApplyValueToEnumerable<TProperty>(
216             ref TProperty value,
217             ref ReadStack state)
218         {
219             Debug.Assert(!state.Current.SkipProperty);
220
221             if (state.Current.IsProcessingObject(ClassType.Enumerable))
222             {
223                 if (state.Current.TempEnumerableValues != null)
224                 {
225                     ((IList<TProperty>)state.Current.TempEnumerableValues).Add(value);
226                 }
227                 else
228                 {
229                     ((IList<TProperty>)state.Current.ReturnValue).Add(value);
230                 }
231             }
232             else if (state.Current.IsProcessingProperty(ClassType.Enumerable))
233             {
234                 Debug.Assert(state.Current.JsonPropertyInfo != null);
235                 Debug.Assert(state.Current.ReturnValue != null);
236                 if (state.Current.TempEnumerableValues != null)
237                 {
238                     ((IList<TProperty>)state.Current.TempEnumerableValues).Add(value);
239                 }
240                 else
241                 {
242                     IList<TProperty> list = (IList<TProperty>)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
243                     if (list == null)
244                     {
245                         state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
246                     }
247                     else
248                     {
249                         list.Add(value);
250                     }
251                 }
252             }
253             else if (state.Current.IsProcessingDictionary())
254             {
255                 Debug.Assert(state.Current.ReturnValue != null);
256                 IDictionary<string, TProperty> dictionary = (IDictionary<string, TProperty>)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
257
258                 string key = state.Current.KeyName;
259                 Debug.Assert(!string.IsNullOrEmpty(key));
260                 dictionary[key] = value;
261             }
262             else if (state.Current.IsProcessingIDictionaryConstructible())
263             {
264                 Debug.Assert(state.Current.TempDictionaryValues != null);
265                 IDictionary<string, TProperty> dictionary = (IDictionary<string, TProperty>)state.Current.TempDictionaryValues;
266
267                 string key = state.Current.KeyName;
268                 Debug.Assert(!string.IsNullOrEmpty(key));
269                 dictionary[key] = value;
270             }
271             else
272             {
273                 Debug.Assert(state.Current.JsonPropertyInfo != null);
274                 state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
275             }
276         }
277     }
278 }