public bool TryGetParameter( ReadOnlySpan <byte> propertyName, ref ReadStackFrame frame, out JsonParameterInfo?jsonParameterInfo) { JsonParameterInfo?info = null; // Keep a local copy of the cache in case it changes by another thread. ParameterRef[]? localParameterRefsSorted = _parameterRefsSorted; ulong key = GetKey(propertyName); // If there is an existing cache, then use it. if (localParameterRefsSorted != null) { // Start with the current parameter index, and then go forwards\backwards. int parameterIndex = frame.CtorArgumentState !.ParameterIndex; int count = localParameterRefsSorted.Length; int iForward = Math.Min(parameterIndex, count); int iBackward = iForward - 1; while (true) { if (iForward < count) { ParameterRef parameterRef = localParameterRefsSorted[iForward]; if (TryIsParameterRefEqual(parameterRef, propertyName, key, ref info)) { jsonParameterInfo = info; return(true); } ++iForward; if (iBackward >= 0) { parameterRef = localParameterRefsSorted[iBackward]; if (TryIsParameterRefEqual(parameterRef, propertyName, key, ref info)) { jsonParameterInfo = info; return(true); } --iBackward; } } else if (iBackward >= 0) { ParameterRef parameterRef = localParameterRefsSorted[iBackward]; if (TryIsParameterRefEqual(parameterRef, propertyName, key, ref info)) { jsonParameterInfo = info; return(true); } --iBackward; } else { // Property was not found. break; } } } string propertyNameAsString = JsonHelpers.Utf8GetString(propertyName); Debug.Assert(ParameterCache != null); if (!ParameterCache.TryGetValue(propertyNameAsString, out info)) { // Constructor parameter not found. We'll check if it's a property next. jsonParameterInfo = null; return(false); } jsonParameterInfo = info; Debug.Assert(info != null); // Two code paths to get here: // 1) key == info.PropertyNameKey. Exact match found. // 2) key != info.PropertyNameKey. Match found due to case insensitivity. // TODO: recheck these conditions Debug.Assert(key == info.ParameterNameKey || propertyNameAsString.Equals(info.NameAsString, StringComparison.OrdinalIgnoreCase)); // Check if we should add this to the cache. // Only cache up to a threshold length and then just use the dictionary when an item is not found in the cache. int cacheCount = 0; if (localParameterRefsSorted != null) { cacheCount = localParameterRefsSorted.Length; } // Do a quick check for the stable (after warm-up) case. if (cacheCount < ParameterNameCountCacheThreshold) { // Do a slower check for the warm-up case. if (frame.CtorArgumentState !.ParameterRefCache != null) { cacheCount += frame.CtorArgumentState.ParameterRefCache.Count; } // Check again to append the cache up to the threshold. if (cacheCount < ParameterNameCountCacheThreshold) { if (frame.CtorArgumentState.ParameterRefCache == null) { frame.CtorArgumentState.ParameterRefCache = new List <ParameterRef>(); } ParameterRef parameterRef = new ParameterRef(key, jsonParameterInfo); frame.CtorArgumentState.ParameterRefCache.Add(parameterRef); } } return(true); }
/// <summary> /// Parses a string representing JSON document into <see cref="JsonNode"/>. /// </summary> /// <param name="json">JSON to parse.</param> /// <param name="options">Options to control the parsing behavior.</param> /// <returns><see cref="JsonNode"/> representation of <paramref name="json"/>.</returns> public static JsonNode Parse(string json, JsonNodeOptions options = default) { Utf8JsonReader reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json), options.GetReaderOptions()); var currentNodes = new Stack <KeyValuePair <string?, JsonNode?> >(); // nodes currently being created JsonNode?toReturn = null; while (reader.Read()) { JsonTokenType tokenType = reader.TokenType; currentNodes.TryPeek(out KeyValuePair <string?, JsonNode?> currentPair); void AddNewPair(JsonNode jsonNode, bool keepInCurrentNodes = false) { KeyValuePair <string?, JsonNode?> newProperty; if (currentPair.Value == null) { // If previous token was property name, // it was added to stack with not null name and null value, // otherwise, this is first JsonNode added if (currentPair.Key != null) { // Create as property, keep name, replace null with new JsonNode: currentNodes.Pop(); newProperty = new KeyValuePair <string?, JsonNode?>(currentPair.Key, jsonNode); } else { // Add first JsonNode: newProperty = new KeyValuePair <string?, JsonNode?>(null, jsonNode); } } else { // Create as value: newProperty = new KeyValuePair <string?, JsonNode?>(null, jsonNode); } if (keepInCurrentNodes) { // If after adding property, it should be kept in currentNodes, it must be JsonObject or JsonArray Debug.Assert(jsonNode.ValueKind == JsonValueKind.Object || jsonNode.ValueKind == JsonValueKind.Array); currentNodes.Push(newProperty); } else { AddToParent(newProperty, ref currentNodes, ref toReturn, options.DuplicatePropertyNameHandling); } } switch (tokenType) { case JsonTokenType.StartObject: AddNewPair(new JsonObject(), true); break; case JsonTokenType.EndObject: Debug.Assert(currentPair.Value is JsonObject); currentNodes.Pop(); AddToParent(currentPair, ref currentNodes, ref toReturn, options.DuplicatePropertyNameHandling); break; case JsonTokenType.StartArray: AddNewPair(new JsonArray(), true); break; case JsonTokenType.EndArray: Debug.Assert(currentPair.Value is JsonArray); currentNodes.Pop(); AddToParent(currentPair, ref currentNodes, ref toReturn, options.DuplicatePropertyNameHandling); break; case JsonTokenType.PropertyName: currentNodes.Push(new KeyValuePair <string?, JsonNode?>(reader.GetString(), null)); break; case JsonTokenType.Number: AddNewPair(new JsonNumber(JsonHelpers.Utf8GetString(reader.ValueSpan))); break; case JsonTokenType.String: AddNewPair(new JsonString(reader.GetString() !)); break; case JsonTokenType.True: AddNewPair(new JsonBoolean(true)); break; case JsonTokenType.False: AddNewPair(new JsonBoolean(false)); break; case JsonTokenType.Null: AddNewPair(JsonNull.Instance); break; } } Debug.Assert(toReturn != null); return(toReturn); }
public T?SetValue(string propertyName, T?value, Action?assignParent = null) { if (IsReadOnly) { ThrowHelper.ThrowNotSupportedException_NodeCollectionIsReadOnly(); } if (propertyName == null) { throw new ArgumentNullException(nameof(propertyName)); } CreateDictionaryIfThresholdMet(); T?existing = null; if (_propertyDictionary != null) { // Fast path if item doesn't exist in dictionary. if (JsonHelpers.TryAdd(_propertyDictionary, propertyName, value)) { assignParent?.Invoke(); _propertyList.Add(new KeyValuePair <string, T?>(propertyName, value)); return(null); } existing = _propertyDictionary[propertyName]; if (ReferenceEquals(existing, value)) { // Ignore if the same value. return(null); } } int i = FindValueIndex(propertyName); if (i >= 0) { if (_propertyDictionary != null) { _propertyDictionary[propertyName] = value; } else { KeyValuePair <string, T?> current = _propertyList[i]; if (ReferenceEquals(current.Value, value)) { // Ignore if the same value. return(null); } existing = current.Value; } assignParent?.Invoke(); _propertyList[i] = new KeyValuePair <string, T?>(propertyName, value); } else { assignParent?.Invoke(); _propertyDictionary?.Add(propertyName, value); _propertyList.Add(new KeyValuePair <string, T?>(propertyName, value)); Debug.Assert(existing == null); } return(existing); }
public JsonPropertyInfo GetProperty(ReadOnlySpan <byte> propertyName, ref ReadStackFrame frame) { JsonPropertyInfo?info = null; // Keep a local copy of the cache in case it changes by another thread. PropertyRef[]? localPropertyRefsSorted = _propertyRefsSorted; ulong key = GetKey(propertyName); // If there is an existing cache, then use it. if (localPropertyRefsSorted != null) { // Start with the current property index, and then go forwards\backwards. int propertyIndex = frame.PropertyIndex; int count = localPropertyRefsSorted.Length; int iForward = Math.Min(propertyIndex, count); int iBackward = iForward - 1; while (true) { if (iForward < count) { PropertyRef propertyRef = localPropertyRefsSorted[iForward]; if (TryIsPropertyRefEqual(propertyRef, propertyName, key, ref info)) { return(info); } ++iForward; if (iBackward >= 0) { propertyRef = localPropertyRefsSorted[iBackward]; if (TryIsPropertyRefEqual(propertyRef, propertyName, key, ref info)) { return(info); } --iBackward; } } else if (iBackward >= 0) { PropertyRef propertyRef = localPropertyRefsSorted[iBackward]; if (TryIsPropertyRefEqual(propertyRef, propertyName, key, ref info)) { return(info); } --iBackward; } else { // Property was not found. break; } } } // No cached item was found. Try the main list which has all of the properties. string stringPropertyName = JsonHelpers.Utf8GetString(propertyName); Debug.Assert(PropertyCache != null); if (!PropertyCache.TryGetValue(stringPropertyName, out info)) { info = JsonPropertyInfo.s_missingProperty; } Debug.Assert(info != null); // Three code paths to get here: // 1) info == s_missingProperty. Property not found. // 2) key == info.PropertyNameKey. Exact match found. // 3) key != info.PropertyNameKey. Match found due to case insensitivity. Debug.Assert(info == JsonPropertyInfo.s_missingProperty || key == info.PropertyNameKey || Options.PropertyNameCaseInsensitive); // Check if we should add this to the cache. // Only cache up to a threshold length and then just use the dictionary when an item is not found in the cache. int cacheCount = 0; if (localPropertyRefsSorted != null) { cacheCount = localPropertyRefsSorted.Length; } // Do a quick check for the stable (after warm-up) case. if (cacheCount < PropertyNameCountCacheThreshold) { // Do a slower check for the warm-up case. if (frame.PropertyRefCache != null) { cacheCount += frame.PropertyRefCache.Count; } // Check again to append the cache up to the threshold. if (cacheCount < PropertyNameCountCacheThreshold) { if (frame.PropertyRefCache == null) { frame.PropertyRefCache = new List <PropertyRef>(); } PropertyRef propertyRef = new PropertyRef(key, info); frame.PropertyRefCache.Add(propertyRef); } } return(info); }
internal static void ValidateNumber(ReadOnlySpan <byte> utf8FormattedNumber) { // This is a simplified version of the number reader from Utf8JsonReader.TryGetNumber, // because it doesn't need to deal with "NeedsMoreData", or remembering the format. if (utf8FormattedNumber.IsEmpty) { throw new ArgumentException(SR.RequiredDigitNotFoundEndOfData, nameof(utf8FormattedNumber)); } int i = 0; if (utf8FormattedNumber[i] == '-') { i++; if (utf8FormattedNumber.Length <= i) { throw new ArgumentException(SR.RequiredDigitNotFoundEndOfData, nameof(utf8FormattedNumber)); } } if (utf8FormattedNumber[i] == '0') { i++; } else { while (i < utf8FormattedNumber.Length && JsonHelpers.IsDigit(utf8FormattedNumber[i])) { i++; } } if (i == utf8FormattedNumber.Length) { return; } byte val = utf8FormattedNumber[i]; if (val == '.') { i++; } else if (val == 'e' || val == 'E') { i++; if (i >= utf8FormattedNumber.Length) { throw new ArgumentException( SR.RequiredDigitNotFoundEndOfData, nameof(utf8FormattedNumber)); } val = utf8FormattedNumber[i]; if (val == '+' || val == '-') { i++; } } else { throw new ArgumentException( SR.Format(SR.ExpectedEndOfDigitNotFound, ThrowHelper.GetPrintableString(val)), nameof(utf8FormattedNumber)); } if (i >= utf8FormattedNumber.Length) { throw new ArgumentException( SR.RequiredDigitNotFoundEndOfData, nameof(utf8FormattedNumber)); } while (i < utf8FormattedNumber.Length && JsonHelpers.IsDigit(utf8FormattedNumber[i])) { i++; } if (i != utf8FormattedNumber.Length) { throw new ArgumentException( SR.Format(SR.ExpectedEndOfDigitNotFound, ThrowHelper.GetPrintableString(utf8FormattedNumber[i])), nameof(utf8FormattedNumber)); } }
public JsonParameterInfo?GetParameter( ReadOnlySpan <byte> propertyName, ref ReadStackFrame frame, out byte[] utf8PropertyName) { ParameterRef parameterRef; ulong key = GetKey(propertyName); // Keep a local copy of the cache in case it changes by another thread. ParameterRef[]? localParameterRefsSorted = _parameterRefsSorted; // If there is an existing cache, then use it. if (localParameterRefsSorted != null) { // Start with the current parameter index, and then go forwards\backwards. int parameterIndex = frame.CtorArgumentState !.ParameterIndex; int count = localParameterRefsSorted.Length; int iForward = Math.Min(parameterIndex, count); int iBackward = iForward - 1; while (true) { if (iForward < count) { parameterRef = localParameterRefsSorted[iForward]; if (IsParameterRefEqual(parameterRef, propertyName, key)) { utf8PropertyName = parameterRef.NameFromJson; return(parameterRef.Info); } ++iForward; if (iBackward >= 0) { parameterRef = localParameterRefsSorted[iBackward]; if (IsParameterRefEqual(parameterRef, propertyName, key)) { utf8PropertyName = parameterRef.NameFromJson; return(parameterRef.Info); } --iBackward; } } else if (iBackward >= 0) { parameterRef = localParameterRefsSorted[iBackward]; if (IsParameterRefEqual(parameterRef, propertyName, key)) { utf8PropertyName = parameterRef.NameFromJson; return(parameterRef.Info); } --iBackward; } else { // Property was not found. break; } } } // No cached item was found. Try the main dictionary which has all of the parameters. Debug.Assert(ParameterCache != null); if (ParameterCache.TryGetValue(JsonHelpers.Utf8GetString(propertyName), out JsonParameterInfo? info)) { if (Options.PropertyNameCaseInsensitive) { if (propertyName.SequenceEqual(info.NameAsUtf8Bytes)) { Debug.Assert(key == GetKey(info.NameAsUtf8Bytes.AsSpan())); // Use the existing byte[] reference instead of creating another one. utf8PropertyName = info.NameAsUtf8Bytes !; } else { // Make a copy of the original Span. utf8PropertyName = propertyName.ToArray(); } } else { Debug.Assert(key == GetKey(info.NameAsUtf8Bytes !.AsSpan())); utf8PropertyName = info.NameAsUtf8Bytes !; } } else { Debug.Assert(info == null); // Make a copy of the original Span. utf8PropertyName = propertyName.ToArray(); } // Check if we should add this to the cache. // Only cache up to a threshold length and then just use the dictionary when an item is not found in the cache. int cacheCount = 0; if (localParameterRefsSorted != null) { cacheCount = localParameterRefsSorted.Length; } // Do a quick check for the stable (after warm-up) case. if (cacheCount < ParameterNameCountCacheThreshold) { // Do a slower check for the warm-up case. if (frame.CtorArgumentState !.ParameterRefCache != null) { cacheCount += frame.CtorArgumentState.ParameterRefCache.Count; } // Check again to append the cache up to the threshold. if (cacheCount < ParameterNameCountCacheThreshold) { if (frame.CtorArgumentState.ParameterRefCache == null) { frame.CtorArgumentState.ParameterRefCache = new List <ParameterRef>(); } parameterRef = new ParameterRef(key, info !, utf8PropertyName); frame.CtorArgumentState.ParameterRefCache.Add(parameterRef); } } return(info); }
public JsonClassInfo(Type type, JsonSerializerOptions options) { Type = type; Options = options; JsonConverter converter = GetConverter( Type, parentClassType: null, // A ClassInfo never has a "parent" class. propertyInfo: null, // A ClassInfo never has a "parent" property. out Type runtimeType, Options); ClassType = converter.ClassType; PropertyInfoForClassInfo = CreatePropertyInfoForClassInfo(Type, runtimeType, converter, Options); switch (ClassType) { case ClassType.Object: { CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); Dictionary <string, JsonPropertyInfo> cache = new Dictionary <string, JsonPropertyInfo>( Options.PropertyNameCaseInsensitive ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); for (Type?currentType = type; currentType != null; currentType = currentType.BaseType) { foreach (PropertyInfo propertyInfo in currentType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)) { // Ignore indexers if (propertyInfo.GetIndexParameters().Length > 0) { continue; } if (IsNonPublicProperty(propertyInfo)) { if (JsonPropertyInfo.GetAttribute <JsonIncludeAttribute>(propertyInfo) != null) { ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(propertyInfo, currentType); } // Non-public properties should not be included for (de)serialization. continue; } // For now we only support public getters\setters if (propertyInfo.GetMethod?.IsPublic == true || propertyInfo.SetMethod?.IsPublic == true) { JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo, currentType, options); Debug.Assert(jsonPropertyInfo != null && jsonPropertyInfo.NameAsString != null); // If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception. if (!JsonHelpers.TryAdd(cache, jsonPropertyInfo.NameAsString, jsonPropertyInfo)) { JsonPropertyInfo other = cache[jsonPropertyInfo.NameAsString]; if (other.ShouldDeserialize == false && other.ShouldSerialize == false) { // Overwrite the one just added since it has [JsonIgnore]. cache[jsonPropertyInfo.NameAsString] = jsonPropertyInfo; } else if (other.PropertyInfo?.Name != jsonPropertyInfo.PropertyInfo?.Name && (jsonPropertyInfo.ShouldDeserialize == true || jsonPropertyInfo.ShouldSerialize == true)) { // Check for name equality is required to determine when a new slot is used for the member. // Therefore, if names are not the same, there is conflict due to the name policy or attributes. ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(Type, jsonPropertyInfo); } // else ignore jsonPropertyInfo since it has [JsonIgnore] or it's hidden by a new slot. } } } } JsonPropertyInfo[] cacheArray; if (DetermineExtensionDataProperty(cache)) { // Remove from cache since it is handled independently. cache.Remove(DataExtensionProperty !.NameAsString !); cacheArray = new JsonPropertyInfo[cache.Count + 1]; // Set the last element to the extension property. cacheArray[cache.Count] = DataExtensionProperty; } else { cacheArray = new JsonPropertyInfo[cache.Count]; } // Set fields when finished to avoid concurrency issues. PropertyCache = cache; cache.Values.CopyTo(cacheArray, 0); PropertyCacheArray = cacheArray; if (converter.ConstructorIsParameterized) { InitializeConstructorParameters(converter.ConstructorInfo !); } } break; case ClassType.Enumerable: case ClassType.Dictionary: { ElementType = converter.ElementType; CreateObject = options.MemberAccessorStrategy.CreateConstructor(runtimeType); } break; case ClassType.Value: case ClassType.NewValue: { CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); } break; case ClassType.None: { ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type); } break; default: Debug.Fail($"Unexpected class type: {ClassType}"); throw new InvalidOperationException(); } }
public JsonClassInfo(Type type, JsonSerializerOptions options) { Type = type; Options = options; ClassType = GetClassType(type, options); CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); // Ignore properties on enumerable. switch (ClassType) { case ClassType.Object: { PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Dictionary <string, JsonPropertyInfo> cache = CreatePropertyCache(properties.Length); foreach (PropertyInfo propertyInfo in properties) { // Ignore indexers if (propertyInfo.GetIndexParameters().Length > 0) { continue; } // For now we only support public getters\setters if (propertyInfo.GetMethod?.IsPublic == true || propertyInfo.SetMethod?.IsPublic == true) { JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options); Debug.Assert(jsonPropertyInfo != null); // If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception. if (!JsonHelpers.TryAdd(cache, jsonPropertyInfo.NameAsString, jsonPropertyInfo)) { JsonPropertyInfo other = cache[jsonPropertyInfo.NameAsString]; if (other.ShouldDeserialize == false && other.ShouldSerialize == false) { // Overwrite the one just added since it has [JsonIgnore]. cache[jsonPropertyInfo.NameAsString] = jsonPropertyInfo; } else if (jsonPropertyInfo.ShouldDeserialize == true || jsonPropertyInfo.ShouldSerialize == true) { ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(this, jsonPropertyInfo); } // else ignore jsonPropertyInfo since it has [JsonIgnore]. } } } if (DetermineExtensionDataProperty(cache)) { // Remove from cache since it is handled independently. cache.Remove(DataExtensionProperty.NameAsString); } // Set as a unit to avoid concurrency issues. PropertyCache = cache; } break; case ClassType.Enumerable: case ClassType.Dictionary: { // Add a single property that maps to the class type so we can have policies applied. AddPolicyProperty(type, options); Type objectType; if (IsNativelySupportedCollection(type)) { // Use the type from the property policy to get any late-bound concrete types (from an interface like IDictionary). objectType = PolicyProperty.RuntimePropertyType; } else { // We need to create the declared instance for types implementing natively supported collections. objectType = PolicyProperty.DeclaredPropertyType; } CreateObject = options.MemberAccessorStrategy.CreateConstructor(objectType); ElementType = GetElementType(type, parentType: null, memberInfo: null, options: options); } break; case ClassType.IDictionaryConstructible: { // Add a single property that maps to the class type so we can have policies applied. AddPolicyProperty(type, options); ElementType = GetElementType(type, parentType: null, memberInfo: null, options: options); CreateConcreteDictionary = options.MemberAccessorStrategy.CreateConstructor( typeof(Dictionary <,>).MakeGenericType(typeof(string), ElementType)); CreateObject = options.MemberAccessorStrategy.CreateConstructor(PolicyProperty.DeclaredPropertyType); } break; case ClassType.Value: // Add a single property that maps to the class type so we can have policies applied. AddPolicyProperty(type, options); break; case ClassType.Unknown: // Add a single property that maps to the class type so we can have policies applied. AddPolicyProperty(type, options); PropertyCache = new Dictionary <string, JsonPropertyInfo>(); break; default: Debug.Fail($"Unexpected class type: {ClassType}"); break; } }
// We can't use the type Rune since it is not available on netstandard2.0 // To avoid extensive ifdefs and for simplicity, just using an int to reprepsent the scalar value, instead. public static SequenceValidity PeekFirstSequence(ReadOnlySpan <byte> data, out int numBytesConsumed, out int rune) { // This method is implemented to match the behavior of System.Text.Encoding.UTF8 in terms of // how many bytes it consumes when reporting invalid sequences. The behavior is as follows: // // - Some bytes are *always* invalid (ranges [ C0..C1 ] and [ F5..FF ]), and when these // are encountered it's an invalid sequence of length 1. // // - Multi-byte sequences which are overlong are reported as an invalid sequence of length 2, // since per the Unicode Standard Table 3-7 it's always possible to tell these by the second byte. // Exception: Sequences which begin with [ C0..C1 ] are covered by the above case, thus length 1. // // - Multi-byte sequences which are improperly terminated (no continuation byte when one is // expected) are reported as invalid sequences up to and including the last seen continuation byte. Debug.Assert(JsonHelpers.IsValidUnicodeScalar(ReplacementChar)); rune = ReplacementChar; if (data.IsEmpty) { // No data to peek at numBytesConsumed = 0; return(SequenceValidity.Empty); } byte firstByte = data[0]; if (IsAsciiValue(firstByte)) { // ASCII byte = well-formed one-byte sequence. Debug.Assert(JsonHelpers.IsValidUnicodeScalar(firstByte)); rune = firstByte; numBytesConsumed = 1; return(SequenceValidity.WellFormed); } if (!JsonHelpers.IsInRangeInclusive(firstByte, (byte)0xC2U, (byte)0xF4U)) { // Standalone continuation byte or "always invalid" byte = ill-formed one-byte sequence. goto InvalidOneByteSequence; } // At this point, we know we're working with a multi-byte sequence, // and we know that at least the first byte is potentially valid. if (data.Length < 2) { // One byte of an incomplete multi-byte sequence. goto OneByteOfIncompleteMultiByteSequence; } byte secondByte = data[1]; if (!IsUtf8ContinuationByte(secondByte)) { // One byte of an improperly terminated multi-byte sequence. goto InvalidOneByteSequence; } if (firstByte < (byte)0xE0U) { // Well-formed two-byte sequence. uint scalar = (((uint)firstByte & 0x1FU) << 6) | ((uint)secondByte & 0x3FU); Debug.Assert(JsonHelpers.IsValidUnicodeScalar(scalar)); rune = (int)scalar; numBytesConsumed = 2; return(SequenceValidity.WellFormed); } if (firstByte < (byte)0xF0U) { // Start of a three-byte sequence. // Need to check for overlong or surrogate sequences. uint scalar = (((uint)firstByte & 0x0FU) << 12) | (((uint)secondByte & 0x3FU) << 6); if (scalar < 0x800U || IsLowWordSurrogate(scalar)) { goto OverlongOutOfRangeOrSurrogateSequence; } // At this point, we have a valid two-byte start of a three-byte sequence. if (data.Length < 3) { // Two bytes of an incomplete three-byte sequence. goto TwoBytesOfIncompleteMultiByteSequence; } else { byte thirdByte = data[2]; if (IsUtf8ContinuationByte(thirdByte)) { // Well-formed three-byte sequence. scalar |= (uint)thirdByte & 0x3FU; Debug.Assert(JsonHelpers.IsValidUnicodeScalar(scalar)); rune = (int)scalar; numBytesConsumed = 3; return(SequenceValidity.WellFormed); } else { // Two bytes of improperly terminated multi-byte sequence. goto InvalidTwoByteSequence; } } } { // Start of four-byte sequence. // Need to check for overlong or out-of-range sequences. uint scalar = (((uint)firstByte & 0x07U) << 18) | (((uint)secondByte & 0x3FU) << 12); Debug.Assert(JsonHelpers.IsValidUnicodeScalar(scalar)); if (!JsonHelpers.IsInRangeInclusive(scalar, 0x10000U, 0x10FFFFU)) { goto OverlongOutOfRangeOrSurrogateSequence; } // At this point, we have a valid two-byte start of a four-byte sequence. if (data.Length < 3) { // Two bytes of an incomplete four-byte sequence. goto TwoBytesOfIncompleteMultiByteSequence; } else { byte thirdByte = data[2]; if (IsUtf8ContinuationByte(thirdByte)) { // Valid three-byte start of a four-byte sequence. if (data.Length < 4) { // Three bytes of an incomplete four-byte sequence. goto ThreeBytesOfIncompleteMultiByteSequence; } else { byte fourthByte = data[3]; if (IsUtf8ContinuationByte(fourthByte)) { // Well-formed four-byte sequence. scalar |= (((uint)thirdByte & 0x3FU) << 6) | ((uint)fourthByte & 0x3FU); Debug.Assert(JsonHelpers.IsValidUnicodeScalar(scalar)); rune = (int)scalar; numBytesConsumed = 4; return(SequenceValidity.WellFormed); } else { // Three bytes of an improperly terminated multi-byte sequence. goto InvalidThreeByteSequence; } } } else { // Two bytes of improperly terminated multi-byte sequence. goto InvalidTwoByteSequence; } } } // Everything below here is error handling. InvalidOneByteSequence: numBytesConsumed = 1; return(SequenceValidity.Invalid); InvalidTwoByteSequence: OverlongOutOfRangeOrSurrogateSequence: numBytesConsumed = 2; return(SequenceValidity.Invalid); InvalidThreeByteSequence: numBytesConsumed = 3; return(SequenceValidity.Invalid); OneByteOfIncompleteMultiByteSequence: numBytesConsumed = 1; return(SequenceValidity.Incomplete); TwoBytesOfIncompleteMultiByteSequence: numBytesConsumed = 2; return(SequenceValidity.Incomplete); ThreeBytesOfIncompleteMultiByteSequence: numBytesConsumed = 3; return(SequenceValidity.Incomplete); }
internal static JsonPropertyInfo LookupProperty( object obj, ref Utf8JsonReader reader, JsonSerializerOptions options, ref ReadStack state, out bool useExtensionProperty, bool createExtensionProperty = true) { Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Object); ReadOnlySpan <byte> unescapedPropertyName = GetPropertyName(ref state, ref reader, options); JsonPropertyInfo jsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(unescapedPropertyName, ref state.Current); // Increment PropertyIndex so GetProperty() starts with the next property the next time this function is called. state.Current.PropertyIndex++; // Determine if we should use the extension property. if (jsonPropertyInfo == JsonPropertyInfo.s_missingProperty) { JsonPropertyInfo?dataExtProperty = state.Current.JsonClassInfo.DataExtensionProperty; if (dataExtProperty != null) { state.Current.JsonPropertyNameAsString = JsonHelpers.Utf8GetString(unescapedPropertyName); if (createExtensionProperty) { CreateDataExtensionProperty(obj, dataExtProperty); } jsonPropertyInfo = dataExtProperty; useExtensionProperty = true; } else { useExtensionProperty = false; } state.Current.JsonPropertyInfo = jsonPropertyInfo; return(jsonPropertyInfo); } // Support JsonException.Path. Debug.Assert( jsonPropertyInfo.JsonPropertyName == null || options.PropertyNameCaseInsensitive || unescapedPropertyName.SequenceEqual(jsonPropertyInfo.JsonPropertyName)); state.Current.JsonPropertyInfo = jsonPropertyInfo; if (jsonPropertyInfo.JsonPropertyName == null) { byte[] propertyNameArray = unescapedPropertyName.ToArray(); if (options.PropertyNameCaseInsensitive) { // Each payload can have a different name here; remember the value on the temporary stack. state.Current.JsonPropertyName = propertyNameArray; } else { // Prevent future allocs by caching globally on the JsonPropertyInfo which is specific to a Type+PropertyName // so it will match the incoming payload except when case insensitivity is enabled (which is handled above). state.Current.JsonPropertyInfo.JsonPropertyName = propertyNameArray; } } state.Current.JsonPropertyInfo = jsonPropertyInfo; useExtensionProperty = false; return(jsonPropertyInfo); }
private static void EscapeNextChars(ReadOnlySpan <char> value, int firstChar, Span <char> destination, ref int consumed, ref int written) { int nextChar = -1; if (JsonHelpers.IsInRangeInclusive(firstChar, JsonConstants.HighSurrogateStartValue, JsonConstants.LowSurrogateEndValue)) { consumed++; if (value.Length <= consumed || firstChar >= JsonConstants.LowSurrogateStartValue) { ThrowHelper.ThrowArgumentException_InvalidUTF16(firstChar); } nextChar = value[consumed]; if (!JsonHelpers.IsInRangeInclusive(nextChar, JsonConstants.LowSurrogateStartValue, JsonConstants.LowSurrogateEndValue)) { ThrowHelper.ThrowArgumentException_InvalidUTF16(nextChar); } } destination[written++] = '\\'; switch (firstChar) { case JsonConstants.LineFeed: destination[written++] = 'n'; break; case JsonConstants.CarriageReturn: destination[written++] = 'r'; break; case JsonConstants.Tab: destination[written++] = 't'; break; case JsonConstants.BackSlash: destination[written++] = '\\'; break; case JsonConstants.BackSpace: destination[written++] = 'b'; break; case JsonConstants.FormFeed: destination[written++] = 'f'; break; default: destination[written++] = 'u'; #if BUILDING_INBOX_LIBRARY firstChar.TryFormat(destination.Slice(written), out int charsWritten, HexFormatString); Debug.Assert(charsWritten == 4); written += charsWritten; #else written = WriteHex(firstChar, destination, written); #endif if (nextChar != -1) { destination[written++] = '\\'; destination[written++] = 'u'; #if BUILDING_INBOX_LIBRARY nextChar.TryFormat(destination.Slice(written), out charsWritten, HexFormatString); Debug.Assert(charsWritten == 4); written += charsWritten; #else written = WriteHex(nextChar, destination, written); #endif } break; } }
public JsonClassInfo(Type type, JsonSerializerOptions options) { Type = type; Options = options; JsonConverter converter = GetConverter( Type, parentClassType: null, // A ClassInfo never has a "parent" class. propertyInfo: null, // A ClassInfo never has a "parent" property. out Type runtimeType, Options); ClassType = converter.ClassType; PropertyInfoForClassInfo = CreatePropertyInfoForClassInfo(Type, runtimeType, converter, Options); switch (ClassType) { case ClassType.Object: { CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); Dictionary <string, JsonPropertyInfo> cache = new Dictionary <string, JsonPropertyInfo>( Options.PropertyNameCaseInsensitive ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); HashSet <string>?ignoredProperties = null; // We start from the most derived type and ascend to the base type. for (Type?currentType = type; currentType != null; currentType = currentType.BaseType) { foreach (PropertyInfo propertyInfo in currentType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)) { // Ignore indexers if (propertyInfo.GetIndexParameters().Length > 0) { continue; } // For now we only support public properties (i.e. setter and/or getter is public). if (propertyInfo.GetMethod?.IsPublic == true || propertyInfo.SetMethod?.IsPublic == true) { JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo, currentType, options); Debug.Assert(jsonPropertyInfo != null && jsonPropertyInfo.NameAsString != null); string propertyName = propertyInfo.Name; // The JsonPropertyNameAttribute or naming policy resulted in a collision. if (!JsonHelpers.TryAdd(cache, jsonPropertyInfo.NameAsString, jsonPropertyInfo)) { JsonPropertyInfo other = cache[jsonPropertyInfo.NameAsString]; if (other.IsIgnored) { // Overwrite previously cached property since it has [JsonIgnore]. cache[jsonPropertyInfo.NameAsString] = jsonPropertyInfo; } else if ( // Does the current property have `JsonIgnoreAttribute`? !jsonPropertyInfo.IsIgnored && // Is the current property hidden by the previously cached property // (with `new` keyword, or by overriding)? other.PropertyInfo !.Name != propertyName && // Was a property with the same CLR name was ignored? That property hid the current property, // thus, if it was ignored, the current property should be ignored too. ignoredProperties?.Contains(propertyName) != true) { // We throw if we have two public properties that have the same JSON property name, and neither have been ignored. ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(Type, jsonPropertyInfo); } // Ignore the current property. } if (jsonPropertyInfo.IsIgnored) { (ignoredProperties ??= new HashSet <string>()).Add(propertyName); } } else { if (JsonPropertyInfo.GetAttribute <JsonIncludeAttribute>(propertyInfo) != null) { ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(propertyInfo, currentType); } // Non-public properties should not be included for (de)serialization. } } } JsonPropertyInfo[] cacheArray; if (DetermineExtensionDataProperty(cache)) { // Remove from cache since it is handled independently. cache.Remove(DataExtensionProperty !.NameAsString !); cacheArray = new JsonPropertyInfo[cache.Count + 1]; // Set the last element to the extension property. cacheArray[cache.Count] = DataExtensionProperty; } else { cacheArray = new JsonPropertyInfo[cache.Count]; } // Copy the dictionary cache to the array cache. cache.Values.CopyTo(cacheArray, 0); // Set the array cache field at this point since it is completely initialized. // It can now be safely accessed by other threads. PropertyCacheArray = cacheArray; // Allow constructor parameter logic to remove items from the dictionary since the JSON // property values will be passed to the constructor and do not call a property setter. if (converter.ConstructorIsParameterized) { InitializeConstructorParameters(cache, converter.ConstructorInfo !); } // Set the dictionary cache field at this point since it is completely initialized. // It can now be safely accessed by other threads. PropertyCache = cache; } break; case ClassType.Enumerable: case ClassType.Dictionary: { ElementType = converter.ElementType; CreateObject = options.MemberAccessorStrategy.CreateConstructor(runtimeType); } break; case ClassType.Value: case ClassType.NewValue: { CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); } break; case ClassType.None: { ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type); } break; default: Debug.Fail($"Unexpected class type: {ClassType}"); throw new InvalidOperationException(); } }
private static void ReadCore( JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack readStack) { try { JsonReaderState initialState = default; long initialBytesConsumed = default; while (true) { if (readStack.ReadAhead) { // When we're reading ahead we always have to save the state // as we don't know if the next token is an opening object or // array brace. initialState = reader.CurrentState; initialBytesConsumed = reader.BytesConsumed; } if (!reader.Read()) { // Need more data break; } JsonTokenType tokenType = reader.TokenType; if (JsonHelpers.IsInRangeInclusive(tokenType, JsonTokenType.String, JsonTokenType.False)) { Debug.Assert(tokenType == JsonTokenType.String || tokenType == JsonTokenType.Number || tokenType == JsonTokenType.True || tokenType == JsonTokenType.False); HandleValue(tokenType, options, ref reader, ref readStack); } else if (tokenType == JsonTokenType.PropertyName) { HandlePropertyName(options, ref reader, ref readStack); } else if (tokenType == JsonTokenType.StartObject) { if (readStack.Current.SkipProperty) { readStack.Push(); readStack.Current.Drain = true; } else if (readStack.Current.IsProcessingValue()) { if (!HandleObjectAsValue(tokenType, options, ref reader, ref readStack, ref initialState, initialBytesConsumed)) { // Need more data break; } } else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingIDictionaryConstructible) { HandleStartDictionary(options, ref reader, ref readStack); } else { HandleStartObject(options, ref readStack); } } else if (tokenType == JsonTokenType.EndObject) { if (readStack.Current.Drain) { readStack.Pop(); // Clear the current property in case it is a dictionary, since dictionaries must have EndProperty() called when completed. // A non-dictionary property can also have EndProperty() called when completed, although it is redundant. readStack.Current.EndProperty(); } else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingIDictionaryConstructible) { HandleEndDictionary(options, ref reader, ref readStack); } else { HandleEndObject(ref reader, ref readStack); } } else if (tokenType == JsonTokenType.StartArray) { if (!readStack.Current.IsProcessingValue()) { HandleStartArray(options, ref reader, ref readStack); } else if (!HandleObjectAsValue(tokenType, options, ref reader, ref readStack, ref initialState, initialBytesConsumed)) { // Need more data break; } } else if (tokenType == JsonTokenType.EndArray) { HandleEndArray(options, ref reader, ref readStack); } else if (tokenType == JsonTokenType.Null) { HandleNull(ref reader, ref readStack); } } } catch (JsonReaderException ex) { // Re-throw with Path information. ThrowHelper.ReThrowWithPath(readStack, ex); } catch (FormatException ex) when(ex.Source == ThrowHelper.ExceptionSourceValueToRethrowAsJsonException) { ThrowHelper.ReThrowWithPath(readStack, reader, ex); } catch (InvalidOperationException ex) when(ex.Source == ThrowHelper.ExceptionSourceValueToRethrowAsJsonException) { ThrowHelper.ReThrowWithPath(readStack, reader, ex); } catch (JsonException ex) { ThrowHelper.AddExceptionInformation(readStack, reader, ex); throw; } readStack.BytesConsumed += reader.BytesConsumed; return; }
internal static JsonPropertyInfo LookupProperty( object obj, ref Utf8JsonReader reader, JsonSerializerOptions options, ref ReadStack state, out bool useExtensionProperty) { Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Object); JsonPropertyInfo jsonPropertyInfo; ReadOnlySpan <byte> unescapedPropertyName; ReadOnlySpan <byte> propertyName = reader.GetSpan(); if (reader._stringHasEscaping) { int idx = propertyName.IndexOf(JsonConstants.BackSlash); Debug.Assert(idx != -1); unescapedPropertyName = JsonReaderHelper.GetUnescapedSpan(propertyName, idx); } else { unescapedPropertyName = propertyName; } if (options.ReferenceHandling.ShouldReadPreservedReferences()) { if (propertyName.Length > 0 && propertyName[0] == '$') { ThrowHelper.ThrowUnexpectedMetadataException(propertyName, ref reader, ref state); } } jsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(unescapedPropertyName, ref state.Current); // Increment PropertyIndex so GetProperty() starts with the next property the next time this function is called. state.Current.PropertyIndex++; // Determine if we should use the extension property. if (jsonPropertyInfo == JsonPropertyInfo.s_missingProperty) { JsonPropertyInfo?dataExtProperty = state.Current.JsonClassInfo.DataExtensionProperty; if (dataExtProperty != null) { state.Current.JsonPropertyNameAsString = JsonHelpers.Utf8GetString(unescapedPropertyName); CreateDataExtensionProperty(obj, dataExtProperty); jsonPropertyInfo = dataExtProperty; } state.Current.JsonPropertyInfo = jsonPropertyInfo; useExtensionProperty = true; return(jsonPropertyInfo); } // Support JsonException.Path. Debug.Assert( jsonPropertyInfo.JsonPropertyName == null || options.PropertyNameCaseInsensitive || unescapedPropertyName.SequenceEqual(jsonPropertyInfo.JsonPropertyName)); state.Current.JsonPropertyInfo = jsonPropertyInfo; if (jsonPropertyInfo.JsonPropertyName == null) { byte[] propertyNameArray = unescapedPropertyName.ToArray(); if (options.PropertyNameCaseInsensitive) { // Each payload can have a different name here; remember the value on the temporary stack. state.Current.JsonPropertyName = propertyNameArray; } else { // Prevent future allocs by caching globally on the JsonPropertyInfo which is specific to a Type+PropertyName // so it will match the incoming payload except when case insensitivity is enabled (which is handled above). state.Current.JsonPropertyInfo.JsonPropertyName = propertyNameArray; } } state.Current.JsonPropertyInfo = jsonPropertyInfo; useExtensionProperty = false; return(jsonPropertyInfo); }
internal static void Unescape(ReadOnlySpan <byte> source, Span <byte> destination, int idx, out int written) { Debug.Assert(idx >= 0 && idx < source.Length); Debug.Assert(source[idx] == JsonConstants.BackSlash); Debug.Assert(destination.Length >= source.Length); source.Slice(0, idx).CopyTo(destination); written = idx; for (; idx < source.Length; idx++) { byte currentByte = source[idx]; if (currentByte == JsonConstants.BackSlash) { idx++; currentByte = source[idx]; if (currentByte == JsonConstants.Quote) { destination[written++] = JsonConstants.Quote; } else if (currentByte == 'n') { destination[written++] = JsonConstants.LineFeed; } else if (currentByte == 'r') { destination[written++] = JsonConstants.CarriageReturn; } else if (currentByte == JsonConstants.BackSlash) { destination[written++] = JsonConstants.BackSlash; } else if (currentByte == JsonConstants.Slash) { destination[written++] = JsonConstants.Slash; } else if (currentByte == 't') { destination[written++] = JsonConstants.Tab; } else if (currentByte == 'b') { destination[written++] = JsonConstants.BackSpace; } else if (currentByte == 'f') { destination[written++] = JsonConstants.FormFeed; } else if (currentByte == 'u') { // The source is known to be valid JSON, and hence if we see a \u, it is guaranteed to have 4 hex digits following it // Otherwise, the Utf8JsonReader would have alreayd thrown an exception. Debug.Assert(source.Length >= idx + 5); bool result = Utf8Parser.TryParse(source.Slice(idx + 1, 4), out int scalar, out int bytesConsumed, 'x'); Debug.Assert(result); Debug.Assert(bytesConsumed == 4); idx += bytesConsumed; // The loop iteration will increment idx past the last hex digit if (JsonHelpers.IsInRangeInclusive((uint)scalar, JsonConstants.HighSurrogateStartValue, JsonConstants.LowSurrogateEndValue)) { // The first hex value cannot be a low surrogate. if (scalar >= JsonConstants.LowSurrogateStartValue) { ThrowHelper.ThrowInvalidOperationException_ReadInvalidUTF16(scalar); } Debug.Assert(JsonHelpers.IsInRangeInclusive((uint)scalar, JsonConstants.HighSurrogateStartValue, JsonConstants.HighSurrogateEndValue)); idx += 3; // Skip the last hex digit and the next \u // We must have a low surrogate following a high surrogate. if (source.Length < idx + 4 || source[idx - 2] != '\\' || source[idx - 1] != 'u') { ThrowHelper.ThrowInvalidOperationException_ReadInvalidUTF16(); } // The source is known to be valid JSON, and hence if we see a \u, it is guaranteed to have 4 hex digits following it // Otherwise, the Utf8JsonReader would have alreayd thrown an exception. result = Utf8Parser.TryParse(source.Slice(idx, 4), out int lowSurrogate, out bytesConsumed, 'x'); Debug.Assert(result); Debug.Assert(bytesConsumed == 4); // If the first hex value is a high surrogate, the next one must be a low surrogate. if (!JsonHelpers.IsInRangeInclusive((uint)lowSurrogate, JsonConstants.LowSurrogateStartValue, JsonConstants.LowSurrogateEndValue)) { ThrowHelper.ThrowInvalidOperationException_ReadInvalidUTF16(lowSurrogate); } idx += bytesConsumed - 1; // The loop iteration will increment idx past the last hex digit // To find the unicode scalar: // (0x400 * (High surrogate - 0xD800)) + Low surrogate - 0xDC00 + 0x10000 scalar = (JsonConstants.BitShiftBy10 * (scalar - JsonConstants.HighSurrogateStartValue)) + (lowSurrogate - JsonConstants.LowSurrogateStartValue) + JsonConstants.UnicodePlane01StartValue; } #if BUILDING_INBOX_LIBRARY var rune = new Rune(scalar); result = rune.TryEncodeToUtf8Bytes(destination.Slice(written), out int bytesWritten); Debug.Assert(result); #else EncodeToUtf8Bytes((uint)scalar, destination.Slice(written), out int bytesWritten); #endif Debug.Assert(bytesWritten <= 4); written += bytesWritten; } } else { destination[written++] = currentByte; } } }
// TODO: Replace this with publicly shipping implementation: https://github.com/dotnet/runtime/issues/28204 /// <summary> /// Converts a span containing a sequence of UTF-16 bytes into UTF-8 bytes. /// /// This method will consume as many of the input bytes as possible. /// /// On successful exit, the entire input was consumed and encoded successfully. In this case, <paramref name="bytesConsumed"/> will be /// equal to the length of the <paramref name="utf16Source"/> and <paramref name="bytesWritten"/> will equal the total number of bytes written to /// the <paramref name="utf8Destination"/>. /// </summary> /// <param name="utf16Source">A span containing a sequence of UTF-16 bytes.</param> /// <param name="utf8Destination">A span to write the UTF-8 bytes into.</param> /// <param name="bytesConsumed">On exit, contains the number of bytes that were consumed from the <paramref name="utf16Source"/>.</param> /// <param name="bytesWritten">On exit, contains the number of bytes written to <paramref name="utf8Destination"/></param> /// <returns>A <see cref="OperationStatus"/> value representing the state of the conversion.</returns> public static unsafe OperationStatus ToUtf8(ReadOnlySpan <byte> utf16Source, Span <byte> utf8Destination, out int bytesConsumed, out int bytesWritten) { // // // KEEP THIS IMPLEMENTATION IN SYNC WITH https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Text/UTF8Encoding.cs#L841 // // fixed(byte *chars = &MemoryMarshal.GetReference(utf16Source)) fixed(byte *bytes = &MemoryMarshal.GetReference(utf8Destination)) { char *pSrc = (char *)chars; byte *pTarget = bytes; char *pEnd = (char *)(chars + utf16Source.Length); byte *pAllocatedBufferEnd = pTarget + utf8Destination.Length; // assume that JIT will enregister pSrc, pTarget and ch // Entering the fast encoding loop incurs some overhead that does not get amortized for small // number of characters, and the slow encoding loop typically ends up running for the last few // characters anyway since the fast encoding loop needs 5 characters on input at least. // Thus don't use the fast decoding loop at all if we don't have enough characters. The threashold // was chosen based on performance testing. // Note that if we don't have enough bytes, pStop will prevent us from entering the fast loop. while (pEnd - pSrc > 13) { // we need at least 1 byte per character, but Convert might allow us to convert // only part of the input, so try as much as we can. Reduce charCount if necessary int available = Math.Min(PtrDiff(pEnd, pSrc), PtrDiff(pAllocatedBufferEnd, pTarget)); // FASTLOOP: // - optimistic range checks // - fallbacks to the slow loop for all special cases, exception throwing, etc. // To compute the upper bound, assume that all characters are ASCII characters at this point, // the boundary will be decreased for every non-ASCII character we encounter // Also, we need 5 chars reserve for the unrolled ansi decoding loop and for decoding of surrogates // If there aren't enough bytes for the output, then pStop will be <= pSrc and will bypass the loop. char *pStop = pSrc + available - 5; if (pSrc >= pStop) { break; } do { int ch = *pSrc; pSrc++; if (ch > 0x7F) { goto LongCode; } *pTarget = (byte)ch; pTarget++; // get pSrc aligned if ((unchecked ((int)pSrc) & 0x2) != 0) { ch = *pSrc; pSrc++; if (ch > 0x7F) { goto LongCode; } *pTarget = (byte)ch; pTarget++; } // Run 4 characters at a time! while (pSrc < pStop) { ch = *(int *)pSrc; int chc = *(int *)(pSrc + 2); if (((ch | chc) & unchecked ((int)0xFF80FF80)) != 0) { goto LongCodeWithMask; } // Unfortunately, this is endianness sensitive if (!BitConverter.IsLittleEndian) { *pTarget = (byte)(ch >> 16); *(pTarget + 1) = (byte)ch; pSrc += 4; *(pTarget + 2) = (byte)(chc >> 16); *(pTarget + 3) = (byte)chc; pTarget += 4; } else { *pTarget = (byte)ch; *(pTarget + 1) = (byte)(ch >> 16); pSrc += 4; *(pTarget + 2) = (byte)chc; *(pTarget + 3) = (byte)(chc >> 16); pTarget += 4; } } continue; LongCodeWithMask: if (!BitConverter.IsLittleEndian) { // be careful about the sign extension ch = (int)(((uint)ch) >> 16); } else { ch = (char)ch; } pSrc++; if (ch > 0x7F) { goto LongCode; } *pTarget = (byte)ch; pTarget++; continue; LongCode: // use separate helper variables for slow and fast loop so that the jit optimizations // won't get confused about the variable lifetimes int chd; if (ch <= 0x7FF) { // 2 byte encoding chd = unchecked ((sbyte)0xC0) | (ch >> 6); } else { // if (!IsLowSurrogate(ch) && !IsHighSurrogate(ch)) if (!JsonHelpers.IsInRangeInclusive(ch, JsonConstants.HighSurrogateStart, JsonConstants.LowSurrogateEnd)) { // 3 byte encoding chd = unchecked ((sbyte)0xE0) | (ch >> 12); } else { // 4 byte encoding - high surrogate + low surrogate // if (!IsHighSurrogate(ch)) if (ch > JsonConstants.HighSurrogateEnd) { // low without high -> bad goto InvalidData; } chd = *pSrc; // if (!IsLowSurrogate(chd)) { if (!JsonHelpers.IsInRangeInclusive(chd, JsonConstants.LowSurrogateStart, JsonConstants.LowSurrogateEnd)) { // high not followed by low -> bad goto InvalidData; } pSrc++; ch = chd + (ch << 10) + (0x10000 - JsonConstants.LowSurrogateStart - (JsonConstants.HighSurrogateStart << 10)); *pTarget = (byte)(unchecked ((sbyte)0xF0) | (ch >> 18)); // pStop - this byte is compensated by the second surrogate character // 2 input chars require 4 output bytes. 2 have been anticipated already // and 2 more will be accounted for by the 2 pStop-- calls below. pTarget++; chd = unchecked ((sbyte)0x80) | (ch >> 12) & 0x3F; } *pTarget = (byte)chd; pStop--; // 3 byte sequence for 1 char, so need pStop-- and the one below too. pTarget++; chd = unchecked ((sbyte)0x80) | (ch >> 6) & 0x3F; } *pTarget = (byte)chd; pStop--; // 2 byte sequence for 1 char so need pStop--. *(pTarget + 1) = (byte)(unchecked ((sbyte)0x80) | ch & 0x3F); // pStop - this byte is already included pTarget += 2; }while (pSrc < pStop); Debug.Assert(pTarget <= pAllocatedBufferEnd, "[UTF8Encoding.GetBytes]pTarget <= pAllocatedBufferEnd"); } while (pSrc < pEnd) { // SLOWLOOP: does all range checks, handles all special cases, but it is slow // read next char. The JIT optimization seems to be getting confused when // compiling "ch = *pSrc++;", so rather use "ch = *pSrc; pSrc++;" instead int ch = *pSrc; pSrc++; if (ch <= 0x7F) { if (pAllocatedBufferEnd - pTarget <= 0) { goto DestinationFull; } *pTarget = (byte)ch; pTarget++; continue; } int chd; if (ch <= 0x7FF) { if (pAllocatedBufferEnd - pTarget <= 1) { goto DestinationFull; } // 2 byte encoding chd = unchecked ((sbyte)0xC0) | (ch >> 6); } else { // if (!IsLowSurrogate(ch) && !IsHighSurrogate(ch)) if (!JsonHelpers.IsInRangeInclusive(ch, JsonConstants.HighSurrogateStart, JsonConstants.LowSurrogateEnd)) { if (pAllocatedBufferEnd - pTarget <= 2) { goto DestinationFull; } // 3 byte encoding chd = unchecked ((sbyte)0xE0) | (ch >> 12); } else { if (pAllocatedBufferEnd - pTarget <= 3) { goto DestinationFull; } // 4 byte encoding - high surrogate + low surrogate // if (!IsHighSurrogate(ch)) if (ch > JsonConstants.HighSurrogateEnd) { // low without high -> bad goto InvalidData; } if (pSrc >= pEnd) { goto NeedMoreData; } chd = *pSrc; // if (!IsLowSurrogate(chd)) { if (!JsonHelpers.IsInRangeInclusive(chd, JsonConstants.LowSurrogateStart, JsonConstants.LowSurrogateEnd)) { // high not followed by low -> bad goto InvalidData; } pSrc++; ch = chd + (ch << 10) + (0x10000 - JsonConstants.LowSurrogateStart - (JsonConstants.HighSurrogateStart << 10)); *pTarget = (byte)(unchecked ((sbyte)0xF0) | (ch >> 18)); pTarget++; chd = unchecked ((sbyte)0x80) | (ch >> 12) & 0x3F; } *pTarget = (byte)chd; pTarget++; chd = unchecked ((sbyte)0x80) | (ch >> 6) & 0x3F; } *pTarget = (byte)chd; *(pTarget + 1) = (byte)(unchecked ((sbyte)0x80) | ch & 0x3F); pTarget += 2; } bytesConsumed = (int)((byte *)pSrc - chars); bytesWritten = (int)(pTarget - bytes); return(OperationStatus.Done); InvalidData: bytesConsumed = (int)((byte *)(pSrc - 1) - chars); bytesWritten = (int)(pTarget - bytes); return(OperationStatus.InvalidData); DestinationFull: bytesConsumed = (int)((byte *)(pSrc - 1) - chars); bytesWritten = (int)(pTarget - bytes); return(OperationStatus.DestinationTooSmall); NeedMoreData: bytesConsumed = (int)((byte *)(pSrc - 1) - chars); bytesWritten = (int)(pTarget - bytes); return(OperationStatus.NeedMoreData); } }
/// <summary> /// Used when writing to buffers not guaranteed to fit the unescaped result. /// </summary> private static bool TryUnescape(ReadOnlySpan <byte> source, Span <byte> destination, int idx, out int written) { Debug.Assert(idx >= 0 && idx < source.Length); Debug.Assert(source[idx] == JsonConstants.BackSlash); if (!source.Slice(0, idx).TryCopyTo(destination)) { written = 0; goto DestinationTooShort; } written = idx; while (true) { Debug.Assert(source[idx] == JsonConstants.BackSlash); if (written == destination.Length) { goto DestinationTooShort; } switch (source[++idx]) { case JsonConstants.Quote: destination[written++] = JsonConstants.Quote; break; case (byte)'n': destination[written++] = JsonConstants.LineFeed; break; case (byte)'r': destination[written++] = JsonConstants.CarriageReturn; break; case JsonConstants.BackSlash: destination[written++] = JsonConstants.BackSlash; break; case JsonConstants.Slash: destination[written++] = JsonConstants.Slash; break; case (byte)'t': destination[written++] = JsonConstants.Tab; break; case (byte)'b': destination[written++] = JsonConstants.BackSpace; break; case (byte)'f': destination[written++] = JsonConstants.FormFeed; break; default: Debug.Assert(source[idx] == 'u', "invalid escape sequences must have already been caught by Utf8JsonReader.Read()"); // The source is known to be valid JSON, and hence if we see a \u, it is guaranteed to have 4 hex digits following it // Otherwise, the Utf8JsonReader would have already thrown an exception. Debug.Assert(source.Length >= idx + 5); bool result = Utf8Parser.TryParse(source.Slice(idx + 1, 4), out int scalar, out int bytesConsumed, 'x'); Debug.Assert(result); Debug.Assert(bytesConsumed == 4); idx += 4; if (JsonHelpers.IsInRangeInclusive((uint)scalar, JsonConstants.HighSurrogateStartValue, JsonConstants.LowSurrogateEndValue)) { // The first hex value cannot be a low surrogate. if (scalar >= JsonConstants.LowSurrogateStartValue) { ThrowHelper.ThrowInvalidOperationException_ReadInvalidUTF16(scalar); } Debug.Assert(JsonHelpers.IsInRangeInclusive((uint)scalar, JsonConstants.HighSurrogateStartValue, JsonConstants.HighSurrogateEndValue)); // We must have a low surrogate following a high surrogate. if (source.Length < idx + 7 || source[idx + 1] != '\\' || source[idx + 2] != 'u') { ThrowHelper.ThrowInvalidOperationException_ReadIncompleteUTF16(); } // The source is known to be valid JSON, and hence if we see a \u, it is guaranteed to have 4 hex digits following it // Otherwise, the Utf8JsonReader would have already thrown an exception. result = Utf8Parser.TryParse(source.Slice(idx + 3, 4), out int lowSurrogate, out bytesConsumed, 'x'); Debug.Assert(result); Debug.Assert(bytesConsumed == 4); idx += 6; // If the first hex value is a high surrogate, the next one must be a low surrogate. if (!JsonHelpers.IsInRangeInclusive((uint)lowSurrogate, JsonConstants.LowSurrogateStartValue, JsonConstants.LowSurrogateEndValue)) { ThrowHelper.ThrowInvalidOperationException_ReadInvalidUTF16(lowSurrogate); } // To find the unicode scalar: // (0x400 * (High surrogate - 0xD800)) + Low surrogate - 0xDC00 + 0x10000 scalar = (JsonConstants.BitShiftBy10 * (scalar - JsonConstants.HighSurrogateStartValue)) + (lowSurrogate - JsonConstants.LowSurrogateStartValue) + JsonConstants.UnicodePlane01StartValue; } #if NETCOREAPP var rune = new Rune(scalar); bool success = rune.TryEncodeToUtf8(destination.Slice(written), out int bytesWritten); #else bool success = TryEncodeToUtf8Bytes((uint)scalar, destination.Slice(written), out int bytesWritten); #endif if (!success) { goto DestinationTooShort; } Debug.Assert(bytesWritten <= 4); written += bytesWritten; break; } if (++idx == source.Length) { goto Success; } if (source[idx] != JsonConstants.BackSlash) { ReadOnlySpan <byte> remaining = source.Slice(idx); int nextUnescapedSegmentLength = remaining.IndexOf(JsonConstants.BackSlash); if (nextUnescapedSegmentLength < 0) { nextUnescapedSegmentLength = remaining.Length; } if ((uint)(written + nextUnescapedSegmentLength) >= (uint)destination.Length) { goto DestinationTooShort; } Debug.Assert(nextUnescapedSegmentLength > 0); switch (nextUnescapedSegmentLength) { case 1: destination[written++] = source[idx++]; break; case 2: destination[written++] = source[idx++]; destination[written++] = source[idx++]; break; case 3: destination[written++] = source[idx++]; destination[written++] = source[idx++]; destination[written++] = source[idx++]; break; default: remaining.Slice(0, nextUnescapedSegmentLength).CopyTo(destination.Slice(written)); written += nextUnescapedSegmentLength; idx += nextUnescapedSegmentLength; break; } Debug.Assert(idx == source.Length || source[idx] == JsonConstants.BackSlash); if (idx == source.Length) { goto Success; } } } Success: return(true); DestinationTooShort: return(false); }
public JsonPropertyInfo GetProperty(ReadOnlySpan <byte> propertyName, ref ReadStackFrame frame) { JsonPropertyInfo info = null; // If we're not trying to build the cache locally, and there is an existing cache, then use it. if (_propertyRefsSorted != null) { ulong key = GetKey(propertyName); // First try sorted lookup. int propertyIndex = frame.PropertyIndex; // This .Length is consistent no matter what json data intialized _propertyRefsSorted. int count = _propertyRefsSorted.Length; if (count != 0) { int iForward = Math.Min(propertyIndex, count); int iBackward = iForward - 1; while (iForward < count || iBackward >= 0) { if (iForward < count) { if (TryIsPropertyRefEqual(_propertyRefsSorted[iForward], propertyName, key, ref info)) { return(info); } ++iForward; } if (iBackward >= 0) { if (TryIsPropertyRefEqual(_propertyRefsSorted[iBackward], propertyName, key, ref info)) { return(info); } --iBackward; } } } } // Try the main list which has all of the properties in a consistent order. // We could get here even when hasPropertyCache==true if there is a race condition with different json // property ordering and _propertyRefsSorted is re-assigned while in the loop above. string stringPropertyName = JsonHelpers.Utf8GetString(propertyName); if (PropertyCache.TryGetValue(stringPropertyName, out info)) { // For performance, only add to cache up to a threshold and then just use the dictionary. int count; if (_propertyRefsSorted != null) { count = _propertyRefsSorted.Length; } else { count = 0; } // Do a quick check for the stable (after warm-up) case. if (count < PropertyNameCountCacheThreshold) { if (frame.PropertyRefCache != null) { count += frame.PropertyRefCache.Count; } // Check again to fill up to the limit. if (count < PropertyNameCountCacheThreshold) { if (frame.PropertyRefCache == null) { frame.PropertyRefCache = new List <PropertyRef>(); } ulong key = info.PropertyNameKey; PropertyRef propertyRef = new PropertyRef(key, info); frame.PropertyRefCache.Add(propertyRef); } } } return(info); }
/// <summary> /// Copies the UTF-8 code unit representation of this scalar to an output buffer. /// The buffer must be large enough to hold the required number of <see cref="byte"/>s. /// </summary> private static bool TryEncodeToUtf8Bytes(uint scalar, Span <byte> utf8Destination, out int bytesWritten) { Debug.Assert(JsonHelpers.IsValidUnicodeScalar(scalar)); if (scalar < 0x80U) { // Single UTF-8 code unit if ((uint)utf8Destination.Length < 1u) { bytesWritten = 0; return(false); } utf8Destination[0] = (byte)scalar; bytesWritten = 1; } else if (scalar < 0x800U) { // Two UTF-8 code units if ((uint)utf8Destination.Length < 2u) { bytesWritten = 0; return(false); } utf8Destination[0] = (byte)(0xC0U | (scalar >> 6)); utf8Destination[1] = (byte)(0x80U | (scalar & 0x3FU)); bytesWritten = 2; } else if (scalar < 0x10000U) { // Three UTF-8 code units if ((uint)utf8Destination.Length < 3u) { bytesWritten = 0; return(false); } utf8Destination[0] = (byte)(0xE0U | (scalar >> 12)); utf8Destination[1] = (byte)(0x80U | ((scalar >> 6) & 0x3FU)); utf8Destination[2] = (byte)(0x80U | (scalar & 0x3FU)); bytesWritten = 3; } else { // Four UTF-8 code units if ((uint)utf8Destination.Length < 4u) { bytesWritten = 0; return(false); } utf8Destination[0] = (byte)(0xF0U | (scalar >> 18)); utf8Destination[1] = (byte)(0x80U | ((scalar >> 12) & 0x3FU)); utf8Destination[2] = (byte)(0x80U | ((scalar >> 6) & 0x3FU)); utf8Destination[3] = (byte)(0x80U | (scalar & 0x3FU)); bytesWritten = 4; } return(true); }
public JsonClassInfo(Type type, JsonSerializerOptions options) { Type = type; Options = options; ClassType = GetClassType( type, parentClassType: type, propertyInfo: null, out Type runtimeType, out Type elementType, out Type nullableUnderlyingType, out MethodInfo addMethod, out JsonConverter converter, checkForAddMethod: true, options); // Ignore properties on enumerable. switch (ClassType) { case ClassType.Object: { CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Dictionary <string, JsonPropertyInfo> cache = CreatePropertyCache(properties.Length); foreach (PropertyInfo propertyInfo in properties) { // Ignore indexers if (propertyInfo.GetIndexParameters().Length > 0) { continue; } // For now we only support public getters\setters if (propertyInfo.GetMethod?.IsPublic == true || propertyInfo.SetMethod?.IsPublic == true) { JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options); Debug.Assert(jsonPropertyInfo != null); // If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception. if (!JsonHelpers.TryAdd(cache, jsonPropertyInfo.NameAsString, jsonPropertyInfo)) { JsonPropertyInfo other = cache[jsonPropertyInfo.NameAsString]; if (other.ShouldDeserialize == false && other.ShouldSerialize == false) { // Overwrite the one just added since it has [JsonIgnore]. cache[jsonPropertyInfo.NameAsString] = jsonPropertyInfo; } else if (jsonPropertyInfo.ShouldDeserialize == true || jsonPropertyInfo.ShouldSerialize == true) { ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(this, jsonPropertyInfo); } // else ignore jsonPropertyInfo since it has [JsonIgnore]. } } } JsonPropertyInfo[] cacheArray; if (DetermineExtensionDataProperty(cache)) { // Remove from cache since it is handled independently. cache.Remove(DataExtensionProperty.NameAsString); cacheArray = new JsonPropertyInfo[cache.Count + 1]; // Set the last element to the extension property. cacheArray[cache.Count] = DataExtensionProperty; } else { cacheArray = new JsonPropertyInfo[cache.Count]; } // Set fields when finished to avoid concurrency issues. PropertyCache = cache; cache.Values.CopyTo(cacheArray, 0); PropertyCacheArray = cacheArray; } break; case ClassType.Enumerable: case ClassType.Dictionary: { // Add a single property that maps to the class type so we can have policies applied. ElementType = elementType; AddItemToObject = addMethod; // A policy property is not a real property on a type; instead it leverages the existing converter // logic and generic support to avoid boxing. It is used with values types, elements from collections and // dictionaries, and collections themselves. Typically it would represent a CLR type such as System.String. PolicyProperty = CreateProperty( declaredPropertyType: type, runtimePropertyType: runtimeType, propertyInfo: null, // Not a real property so this is null. parentClassType: typeof(object), collectionElementType: elementType, nullableUnderlyingType, converter: null, ClassType, options); CreateObject = options.MemberAccessorStrategy.CreateConstructor(PolicyProperty.RuntimePropertyType); } break; case ClassType.Value: { CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); // Add a single property that maps to the class type so we can have policies applied. //AddPolicyPropertyForValue(type, options); PolicyProperty = CreateProperty( declaredPropertyType: type, runtimePropertyType: runtimeType, propertyInfo: null, // Not a real property so this is null. parentClassType: typeof(object), collectionElementType: null, nullableUnderlyingType, converter, ClassType, options); } break; case ClassType.Unknown: { CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); // Add a single property that maps to the class type so we can have policies applied. //AddPolicyPropertyForValue(type, options); PolicyProperty = CreateProperty( declaredPropertyType: type, runtimePropertyType: runtimeType, propertyInfo: null, // Not a real property so this is null. parentClassType: typeof(object), collectionElementType: null, nullableUnderlyingType, converter, ClassType, options); PropertyCache = new Dictionary <string, JsonPropertyInfo>(); PropertyCacheArray = Array.Empty <JsonPropertyInfo>(); } break; default: Debug.Fail($"Unexpected class type: {ClassType}"); break; } }
public static bool IsValidDateTimeOffsetParseLength(long length) { return(JsonHelpers.IsInRangeInclusive(length, JsonConstants.MinimumDateTimeParseLength, JsonConstants.MaximumEscapedDateTimeOffsetParseLength)); }
internal JsonPropertyInfo GetProperty(JsonSerializerOptions options, ReadOnlySpan <byte> propertyName, ref ReadStackFrame frame) { // If we should compare with case-insensitive, normalize to an uppercase format since that is what is cached on the propertyInfo. if (options.PropertyNameCaseInsensitive) { string utf16PropertyName = JsonHelpers.Utf8GetString(propertyName); string upper = utf16PropertyName.ToUpperInvariant(); propertyName = Encoding.UTF8.GetBytes(upper); } ulong key = GetKey(propertyName); JsonPropertyInfo info = null; // First try sorted lookup. int propertyIndex = frame.PropertyIndex; // If we're not trying to build the cache locally, and there is an existing cache, then use it. bool hasPropertyCache = frame.PropertyRefCache == null && _propertyRefsSorted != null; if (hasPropertyCache) { // This .Length is consistent no matter what json data intialized _propertyRefsSorted. int count = _propertyRefsSorted.Length; if (count != 0) { int iForward = propertyIndex; int iBackward = propertyIndex - 1; while (iForward < count || iBackward >= 0) { if (iForward < count) { if (TryIsPropertyRefEqual(ref _propertyRefsSorted[iForward], propertyName, key, ref info)) { return(info); } ++iForward; } if (iBackward >= 0) { if (TryIsPropertyRefEqual(ref _propertyRefsSorted[iBackward], propertyName, key, ref info)) { return(info); } --iBackward; } } } } // Try the main list which has all of the properties in a consistent order. // We could get here even when hasPropertyCache==true if there is a race condition with different json // property ordering and _propertyRefsSorted is re-assigned while in the loop above. for (int i = 0; i < _propertyRefs.Count; i++) { PropertyRef propertyRef = _propertyRefs[i]; if (TryIsPropertyRefEqual(ref propertyRef, propertyName, key, ref info)) { break; } } if (!hasPropertyCache) { if (propertyIndex == 0 && frame.PropertyRefCache == null) { // Create the temporary list on first property access to prevent a partially filled List. frame.PropertyRefCache = new List <PropertyRef>(); } if (info != null) { Debug.Assert(frame.PropertyRefCache != null); frame.PropertyRefCache.Add(new PropertyRef(key, info)); } } return(info); }
private static void HandlePropertyName( JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state) { if (state.Current.Drain) { return; } Debug.Assert(state.Current.ReturnValue != null || state.Current.TempDictionaryValues != null); Debug.Assert(state.Current.JsonClassInfo != null); bool isProcessingDictObject = state.Current.IsProcessingObject(ClassType.Dictionary); if ((isProcessingDictObject || state.Current.IsProcessingProperty(ClassType.Dictionary)) && state.Current.JsonClassInfo.DataExtensionProperty != state.Current.JsonPropertyInfo) { if (isProcessingDictObject) { state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.PolicyProperty; } state.Current.KeyName = reader.GetString(); } else { Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Object); state.Current.EndProperty(); ReadOnlySpan <byte> propertyName = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; if (reader._stringHasEscaping) { int idx = propertyName.IndexOf(JsonConstants.BackSlash); Debug.Assert(idx != -1); propertyName = GetUnescapedString(propertyName, idx); } JsonPropertyInfo jsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(propertyName, ref state.Current); if (jsonPropertyInfo == JsonPropertyInfo.s_missingProperty) { JsonPropertyInfo?dataExtProperty = state.Current.JsonClassInfo !.DataExtensionProperty; if (dataExtProperty == null) { state.Current.JsonPropertyInfo = JsonPropertyInfo.s_missingProperty; } else { state.Current.JsonPropertyInfo = dataExtProperty; state.Current.JsonPropertyName = propertyName.ToArray(); state.Current.KeyName = JsonHelpers.Utf8GetString(propertyName); state.Current.CollectionPropertyInitialized = true; CreateDataExtensionProperty(dataExtProperty, ref state); } } else { // Support JsonException.Path. Debug.Assert( jsonPropertyInfo.JsonPropertyName == null || options.PropertyNameCaseInsensitive || propertyName.SequenceEqual(jsonPropertyInfo.JsonPropertyName)); state.Current.JsonPropertyInfo = jsonPropertyInfo; if (jsonPropertyInfo.JsonPropertyName == null) { byte[] propertyNameArray = propertyName.ToArray(); if (options.PropertyNameCaseInsensitive) { // Each payload can have a different name here; remember the value on the temporary stack. state.Current.JsonPropertyName = propertyNameArray; } else { // Prevent future allocs by caching globally on the JsonPropertyInfo which is specific to a Type+PropertyName // so it will match the incoming payload except when case insensitivity is enabled (which is handled above). state.Current.JsonPropertyInfo.JsonPropertyName = propertyNameArray; } } } // Increment the PropertyIndex so JsonClassInfo.GetProperty() starts with the next property. state.Current.PropertyIndex++; } }
public JsonClassInfo(Type type, JsonSerializerOptions options) { Type = type; Options = options; ClassType = GetClassType( type, parentClassType: type, propertyInfo: null, out Type? runtimeType, out Type? elementType, out JsonConverter? converter, options); switch (ClassType) { case ClassType.Object: { CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); Dictionary <string, JsonPropertyInfo> cache = CreatePropertyCache(properties.Length); foreach (PropertyInfo propertyInfo in properties) { // Ignore indexers if (propertyInfo.GetIndexParameters().Length > 0) { continue; } // For now we only support public getters\setters if (propertyInfo.GetMethod?.IsPublic == true || propertyInfo.SetMethod?.IsPublic == true) { JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options); Debug.Assert(jsonPropertyInfo != null && jsonPropertyInfo.NameAsString != null); // If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception. if (!JsonHelpers.TryAdd(cache, jsonPropertyInfo.NameAsString, jsonPropertyInfo)) { JsonPropertyInfo other = cache[jsonPropertyInfo.NameAsString]; if (other.ShouldDeserialize == false && other.ShouldSerialize == false) { // Overwrite the one just added since it has [JsonIgnore]. cache[jsonPropertyInfo.NameAsString] = jsonPropertyInfo; } else if (jsonPropertyInfo.ShouldDeserialize == true || jsonPropertyInfo.ShouldSerialize == true) { ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(this, jsonPropertyInfo); } // else ignore jsonPropertyInfo since it has [JsonIgnore]. } } } JsonPropertyInfo[] cacheArray; if (DetermineExtensionDataProperty(cache)) { // Remove from cache since it is handled independently. cache.Remove(DataExtensionProperty !.NameAsString !); cacheArray = new JsonPropertyInfo[cache.Count + 1]; // Set the last element to the extension property. cacheArray[cache.Count] = DataExtensionProperty; } else { cacheArray = new JsonPropertyInfo[cache.Count]; } // Set fields when finished to avoid concurrency issues. PropertyCache = cache; cache.Values.CopyTo(cacheArray, 0); PropertyCacheArray = cacheArray; // Create the policy property. PolicyProperty = CreatePolicyProperty(type, runtimeType, converter !, ClassType, options); } break; case ClassType.Enumerable: case ClassType.Dictionary: { ElementType = elementType; PolicyProperty = CreatePolicyProperty(type, runtimeType, converter !, ClassType, options); CreateObject = options.MemberAccessorStrategy.CreateConstructor(PolicyProperty.RuntimePropertyType !); } break; case ClassType.Value: case ClassType.NewValue: { CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); PolicyProperty = CreatePolicyProperty(type, runtimeType, converter !, ClassType, options); } break; case ClassType.Invalid: { ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type); } break; default: Debug.Fail($"Unexpected class type: {ClassType}"); break; } }
public JsonClassInfo(Type type, JsonSerializerOptions options) { Type = type; Options = options; JsonConverter converter = GetConverter( Type, parentClassType: null, // A ClassInfo never has a "parent" class. propertyInfo: null, // A ClassInfo never has a "parent" property. out Type runtimeType, Options); ClassType = converter.ClassType; PropertyInfoForClassInfo = CreatePropertyInfoForClassInfo(Type, runtimeType, converter, Options); switch (ClassType) { case ClassType.Object: { CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); Dictionary <string, JsonPropertyInfo> cache = new Dictionary <string, JsonPropertyInfo>( Options.PropertyNameCaseInsensitive ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); Dictionary <string, PropertyInfo>?ignoredProperties = null; // We start from the most derived type. for (Type?currentType = type; currentType != null; currentType = currentType.BaseType) { foreach (PropertyInfo propertyInfo in currentType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)) { // Ignore indexers and virtual properties that have overrides that were [JsonIgnore]d. if (propertyInfo.GetIndexParameters().Length > 0 || PropertyIsOverridenAndIgnored(propertyInfo, ignoredProperties)) { continue; } // For now we only support public properties (i.e. setter and/or getter is public). if (propertyInfo.GetMethod?.IsPublic == true || propertyInfo.SetMethod?.IsPublic == true) { JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo, currentType, options); Debug.Assert(jsonPropertyInfo != null && jsonPropertyInfo.NameAsString != null); string propertyName = propertyInfo.Name; // The JsonPropertyNameAttribute or naming policy resulted in a collision. if (!JsonHelpers.TryAdd(cache, jsonPropertyInfo.NameAsString, jsonPropertyInfo)) { JsonPropertyInfo other = cache[jsonPropertyInfo.NameAsString]; if (other.IsIgnored) { // Overwrite previously cached property since it has [JsonIgnore]. cache[jsonPropertyInfo.NameAsString] = jsonPropertyInfo; } else if ( // Does the current property have `JsonIgnoreAttribute`? !jsonPropertyInfo.IsIgnored && // Is the current property hidden by the previously cached property // (with `new` keyword, or by overriding)? other.PropertyInfo !.Name != propertyName && // Was a property with the same CLR name ignored? That property hid the current property, // thus, if it was ignored, the current property should be ignored too. ignoredProperties?.ContainsKey(propertyName) != true ) { // Throw if we have two public properties with the same JSON property name, // neither overrides or hides the other, and neither have been ignored. ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(Type, jsonPropertyInfo); } // Ignore the current property. } if (jsonPropertyInfo.IsIgnored) { (ignoredProperties ??= new Dictionary <string, PropertyInfo>())[propertyName] = propertyInfo;
private static void ReadCore( JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack readStack) { try { JsonReaderState initialState = default; while (true) { if (readStack.ReadAhead) { // When we're reading ahead we always have to save the state // as we don't know if the next token is an opening object or // array brace. initialState = reader.CurrentState; } if (!reader.Read()) { // Need more data break; } JsonTokenType tokenType = reader.TokenType; if (JsonHelpers.IsInRangeInclusive(tokenType, JsonTokenType.String, JsonTokenType.False)) { Debug.Assert(tokenType == JsonTokenType.String || tokenType == JsonTokenType.Number || tokenType == JsonTokenType.True || tokenType == JsonTokenType.False); HandleValue(tokenType, options, ref reader, ref readStack); } else if (tokenType == JsonTokenType.PropertyName) { HandlePropertyName(options, ref reader, ref readStack); } else if (tokenType == JsonTokenType.StartObject) { if (readStack.Current.SkipProperty) { readStack.Push(); readStack.Current.Drain = true; } else if (readStack.Current.IsProcessingValue) { if (!HandleObjectAsValue(tokenType, options, ref reader, ref readStack, ref initialState)) { // Need more data break; } } else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingImmutableDictionary) { HandleStartDictionary(options, ref reader, ref readStack); } else { HandleStartObject(options, ref reader, ref readStack); } } else if (tokenType == JsonTokenType.EndObject) { if (readStack.Current.Drain) { readStack.Pop(); } else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingImmutableDictionary) { HandleEndDictionary(options, ref reader, ref readStack); } else { HandleEndObject(options, ref reader, ref readStack); } } else if (tokenType == JsonTokenType.StartArray) { if (!readStack.Current.IsProcessingValue) { HandleStartArray(options, ref reader, ref readStack); } else if (!HandleObjectAsValue(tokenType, options, ref reader, ref readStack, ref initialState)) { // Need more data break; } } else if (tokenType == JsonTokenType.EndArray) { HandleEndArray(options, ref reader, ref readStack); } else if (tokenType == JsonTokenType.Null) { HandleNull(ref reader, ref readStack, options); } } } catch (JsonReaderException e) { // Re-throw with Path information. ThrowHelper.ReThrowWithPath(e, readStack.JsonPath); } readStack.BytesConsumed += reader.BytesConsumed; return; }
internal static void ValidateNumber(ReadOnlySpan <byte> utf8FormattedNumber) { // This is a simplified version of the number reader from Utf8JsonReader.TryGetNumber, // because it doesn't need to deal with "NeedsMoreData", or remembering the format. // // The Debug.Asserts in this method should change to validated ArgumentExceptions if/when // writing a formatted number becomes public API. Debug.Assert(!utf8FormattedNumber.IsEmpty); int i = 0; if (utf8FormattedNumber[i] == '-') { i++; if (utf8FormattedNumber.Length <= i) { throw new ArgumentException(SR.RequiredDigitNotFoundEndOfData, nameof(utf8FormattedNumber)); } } if (utf8FormattedNumber[i] == '0') { i++; } else { while (i < utf8FormattedNumber.Length && JsonHelpers.IsDigit(utf8FormattedNumber[i])) { i++; } } if (i == utf8FormattedNumber.Length) { return; } // The non digit character inside the number byte val = utf8FormattedNumber[i]; if (val == '.') { i++; if (utf8FormattedNumber.Length <= i) { throw new ArgumentException(SR.RequiredDigitNotFoundEndOfData, nameof(utf8FormattedNumber)); } while (i < utf8FormattedNumber.Length && JsonHelpers.IsDigit(utf8FormattedNumber[i])) { i++; } if (i == utf8FormattedNumber.Length) { return; } Debug.Assert(i < utf8FormattedNumber.Length); val = utf8FormattedNumber[i]; } if (val == 'e' || val == 'E') { i++; if (utf8FormattedNumber.Length <= i) { throw new ArgumentException(SR.RequiredDigitNotFoundEndOfData, nameof(utf8FormattedNumber)); } val = utf8FormattedNumber[i]; if (val == '+' || val == '-') { i++; } } else { throw new ArgumentException( SR.Format(SR.ExpectedEndOfDigitNotFound, ThrowHelper.GetPrintableString(val)), nameof(utf8FormattedNumber)); } if (utf8FormattedNumber.Length <= i) { throw new ArgumentException(SR.RequiredDigitNotFoundEndOfData, nameof(utf8FormattedNumber)); } while (i < utf8FormattedNumber.Length && JsonHelpers.IsDigit(utf8FormattedNumber[i])) { i++; } if (i != utf8FormattedNumber.Length) { throw new ArgumentException( SR.Format(SR.ExpectedEndOfDigitNotFound, ThrowHelper.GetPrintableString(utf8FormattedNumber[i])), nameof(utf8FormattedNumber)); } }