Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4ce6b17
Add support for Dictionary<non-string,> using TypeConverter.
jozkee Feb 14, 2020
a863dc4
Extend test to check dictionary as a property
jozkee Feb 14, 2020
9f8c14c
Merge branch 'master' of https://github.com/dotnet/runtime into TKey_…
jozkee Feb 14, 2020
46e0b32
Add support for non-string Tkey on Dictionary using custom KeyConverter.
jozkee Feb 15, 2020
e3cda2e
Fix object reference when TKey is string.
jozkee Feb 15, 2020
2830584
Improve performance on KeyConverter.
jozkee Feb 19, 2020
7b127ee
Extend KeyConverter support to DictionaryOfString
jozkee Feb 20, 2020
3bc38ec
Add tests for complex TValues and extension data (string)
jozkee Feb 20, 2020
eba5269
Add GetKeyConverter methods
jozkee Feb 20, 2020
0bac2b3
Do not call ReadKey on TryRead string key to avoid allocation.
jozkee Feb 20, 2020
820dc8c
Add support for object TKey and enum (TODO make enum actually work)
jozkee Feb 21, 2020
344a219
Add naive implementation for Enum. Add test file.
jozkee Feb 22, 2020
9caa700
Clean-up directories.
jozkee Feb 22, 2020
fd601af
Remove DictionaryOfStringTValue converter and add support for continu…
jozkee Feb 24, 2020
a76df85
Implement JsonConverter<T> fon KeyConverter<TKey>.
jozkee Feb 25, 2020
84378a0
Refactor code and add tests.
jozkee Feb 26, 2020
54e8ed3
Merge branch 'master' of https://github.com/dotnet/runtime into TKey_…
jozkee Feb 26, 2020
584ab67
Change NSE message to display the dintionary type instead of TKey type.
jozkee Feb 26, 2020
7cdfca9
Add TODO to broken test.
jozkee Feb 26, 2020
012008b
Code clean-up.
jozkee Feb 26, 2020
0a525d0
Unescape keys before parsing them on async deserialization.
jozkee Feb 27, 2020
a58baf2
Move WritePropertyName methods for int and Guid to their respective f…
jozkee Feb 27, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DocumentationFile>$(OutputPath)$(MSBuildProjectName).xml</DocumentationFile>
Expand Down Expand Up @@ -59,7 +59,7 @@
<Compile Include="System\Text\Json\Serialization\Converters\Collection\ConcurrentQueueOfTConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\ConcurrentStackOfTConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\DictionaryDefaultConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\DictionaryOfStringTValueConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\DictionaryOfTKeyTValue.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\ICollectionOfTConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\IDictionaryConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\IDictionaryOfStringTValueConverter.cs" />
Expand All @@ -78,6 +78,12 @@
<Compile Include="System\Text\Json\Serialization\Converters\Collection\ListOfTConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\QueueOfTConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\StackOfTConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\KeyConverters\EnumKeyConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\KeyConverters\GuidKeyConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\KeyConverters\Int32KeyConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\KeyConverters\KeyConverterOfTKey.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\KeyConverters\ObjectKeyConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\KeyConverters\StringKeyConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Object\ObjectConverterFactory.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Object\ObjectDefaultConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\BooleanConverter.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,33 @@ public static string GetUnescapedString(ReadOnlySpan<byte> utf8Source, int idx)
return utf8String;
}

// TODO: Borrowing this from https://github.com/dotnet/runtime/pull/32669/files#diff-82b934371fff193b37c85635239bb6ebR70
// Remove when that PR is in or remove from #32669 if this gets in first.
public static ReadOnlySpan<byte> GetUnescapedSpan(ReadOnlySpan<byte> utf8Source, int idx)
{
// The escaped name is always >= than the unescaped, so it is safe to use escaped name for the buffer length.
int length = utf8Source.Length;
byte[]? pooledName = null;

Span<byte> utf8Unescaped = length <= JsonConstants.StackallocThreshold ?
stackalloc byte[length] :
(pooledName = ArrayPool<byte>.Shared.Rent(length));

Unescape(utf8Source, utf8Unescaped, idx, out int written);
Debug.Assert(written > 0);

ReadOnlySpan<byte> propertyName = utf8Unescaped.Slice(0, written).ToArray();
Debug.Assert(!propertyName.IsEmpty);

if (pooledName != null)
{
new Span<byte>(pooledName, 0, written).Clear();
ArrayPool<byte>.Shared.Return(pooledName);
}

return propertyName;
}

public static bool UnescapeAndCompare(ReadOnlySpan<byte> utf8Source, ReadOnlySpan<byte> other)
{
Debug.Assert(utf8Source.Length >= other.Length && utf8Source.Length / JsonConstants.MaxExpansionFactorWhileEscaping <= other.Length);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,11 @@ public bool TryGetInt32(out int value)
throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType);
}

return TryGetInt32AfterValidation(out value);
}

internal bool TryGetInt32AfterValidation(out int value)
{
ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
if (Utf8Parser.TryParse(span, out int tmp, out int bytesConsumed)
&& span.Length == bytesConsumed)
Expand Down Expand Up @@ -945,6 +950,11 @@ public bool TryGetGuid(out Guid value)
throw ThrowHelper.GetInvalidOperationException_ExpectedString(TokenType);
}

return TryGetGuidAfterValidation(out value);
}

internal bool TryGetGuidAfterValidation(out Guid value)
{
ReadOnlySpan<byte> span = stackalloc byte[0];

if (HasValueSequence)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Buffers.Text;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;

Expand All @@ -10,13 +13,13 @@ namespace System.Text.Json.Serialization.Converters
/// <summary>
/// Default base class implementation of <cref>JsonDictionaryConverter{TCollection}</cref> .
/// </summary>
internal abstract class DictionaryDefaultConverter<TCollection, TValue>
: JsonDictionaryConverter<TCollection>
internal abstract class DictionaryDefaultConverter<TCollection, TKey, TValue>
: JsonDictionaryConverter<TCollection> where TKey : notnull
{
/// <summary>
/// When overridden, adds the value to the collection.
/// </summary>
protected abstract void Add(TValue value, JsonSerializerOptions options, ref ReadStack state);
protected abstract void Add(TKey key, TValue value, JsonSerializerOptions options, ref ReadStack state);

/// <summary>
/// When overridden, converts the temporary collection held in state.Current.ReturnValue to the final collection.
Expand All @@ -31,6 +34,8 @@ protected virtual void CreateCollection(ref ReadStack state) { }

internal override Type ElementType => typeof(TValue);

internal override Type KeyType => typeof(TKey);

protected static JsonConverter<TValue> GetElementConverter(ref ReadStack state)
{
JsonConverter<TValue> converter = (JsonConverter<TValue>)state.Current.JsonClassInfo.ElementClassInfo!.PolicyProperty!.ConverterBase;
Expand Down Expand Up @@ -62,13 +67,19 @@ protected static JsonConverter<TValue> GetValueConverter(ref WriteStack state)
return converter;
}

protected static KeyConverter<TKey> GetKeyConverter(JsonClassInfo classInfo) => (KeyConverter<TKey>)classInfo.KeyConverter;

internal sealed override bool OnTryRead(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options,
ref ReadStack state,
[MaybeNullWhen(false)] out TCollection value)
{
// Get the key converter at the very beginning; will throw NSE if there is no converter for TKey.
// This is performed at the very beginning to avoid processing unsupported types.
KeyConverter<TKey> keyConverter = GetKeyConverter(state.Current.JsonClassInfo);

bool shouldReadPreservedReferences = options.ReferenceHandling.ShouldReadPreservedReferences();

if (!state.SupportContinuation && !shouldReadPreservedReferences)
Expand Down Expand Up @@ -102,11 +113,12 @@ internal sealed override bool OnTryRead(
}

state.Current.JsonPropertyNameAsString = reader.GetString();
keyConverter.OnTryRead(ref reader, keyConverter.TypeToConvert, options, ref state, out TKey key);

// Read the value and add.
reader.ReadWithVerify();
TValue element = elementConverter.Read(ref reader, typeof(TValue), options);
Add(element, options, ref state);
Add(key, element, options, ref state);
}
}
else
Expand All @@ -127,13 +139,14 @@ internal sealed override bool OnTryRead(
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
}

state.Current.JsonPropertyNameAsString = reader.GetString();
state.Current.JsonPropertyNameAsString = reader.GetString()!;
keyConverter.OnTryRead(ref reader, keyConverter.TypeToConvert, options, ref state, out TKey key);

reader.ReadWithVerify();

// Get the value from the converter and add it.
elementConverter.TryRead(ref reader, typeof(TValue), options, ref state, out TValue element);
Add(element, options, ref state);
Add(key, element, options, ref state);
}
}
}
Expand Down Expand Up @@ -219,16 +232,16 @@ internal sealed override bool OnTryRead(

state.Current.PropertyState = StackFramePropertyState.Name;

ReadOnlySpan<byte> propertyName = reader.GetSpan();
// Verify property doesn't contain metadata.
if (shouldReadPreservedReferences)
{
ReadOnlySpan<byte> propertyName = reader.GetSpan();
if (propertyName.Length > 0 && propertyName[0] == '$')
{
ThrowHelper.ThrowUnexpectedMetadataException(propertyName, ref reader, ref state);
}
}

state.Current.DictionaryKeyName = propertyName.ToArray();
state.Current.JsonPropertyNameAsString = reader.GetString();
}

Expand All @@ -245,6 +258,8 @@ internal sealed override bool OnTryRead(

if (state.Current.PropertyState < StackFramePropertyState.TryRead)
{
keyConverter.OnTryRead(ref reader, keyConverter.TypeToConvert, options, ref state, out TKey key);

// Get the value from the converter and add it.
bool success = elementConverter.TryRead(ref reader, typeof(TValue), options, ref state, out TValue element);
if (!success)
Expand All @@ -253,7 +268,7 @@ internal sealed override bool OnTryRead(
return false;
}

Add(element, options, ref state);
Add(key, element, options, ref state);
state.Current.EndElement();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,20 @@
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Diagnostics;

namespace System.Text.Json.Serialization.Converters
{
/// <summary>
/// Converter for Dictionary{string, TValue} that (de)serializes as a JSON object with properties
/// Converter for Dictionary{TKey, TValue} that (de)serializes as a JSON object with properties
/// representing the dictionary element key and value.
/// </summary>
internal sealed class DictionaryOfStringTValueConverter<TCollection, TValue>
: DictionaryDefaultConverter<TCollection, TValue>
where TCollection : Dictionary<string, TValue>
internal sealed class DictionaryOfTKeyTValueConverter<TCollection, TKey, TValue>
: DictionaryDefaultConverter<TCollection, TKey, TValue>
where TCollection : Dictionary<TKey, TValue>
where TKey : notnull
{
protected override void Add(TValue value, JsonSerializerOptions options, ref ReadStack state)
protected override void Add(TKey key, TValue value, JsonSerializerOptions options, ref ReadStack state)
{
Debug.Assert(state.Current.ReturnValue is TCollection);

string key = state.Current.JsonPropertyNameAsString!;
((TCollection)state.Current.ReturnValue!)[key] = value;
}

Expand All @@ -39,7 +36,12 @@ protected internal override bool OnWriteResume(
JsonSerializerOptions options,
ref WriteStack state)
{
Dictionary<string, TValue>.Enumerator enumerator;
// Get the key converter at the very beginning; will throw NSE if there is no converter for TKey.
// This is performed at the very beginning to avoid processing unsupported types.
KeyConverter<TKey> keyConverter = GetKeyConverter(state.Current.JsonClassInfo);

Dictionary<TKey, TValue>.Enumerator enumerator;

if (state.Current.CollectionEnumerator == null)
{
enumerator = value.GetEnumerator();
Expand All @@ -50,20 +52,19 @@ protected internal override bool OnWriteResume(
}
else
{
Debug.Assert(state.Current.CollectionEnumerator is Dictionary<string, TValue>.Enumerator);
enumerator = (Dictionary<string, TValue>.Enumerator)state.Current.CollectionEnumerator;
enumerator = (Dictionary<TKey, TValue>.Enumerator)state.Current.CollectionEnumerator;
}

JsonConverter<TValue> converter = GetValueConverter(ref state);
if (!state.SupportContinuation && converter.CanUseDirectReadOrWrite)
JsonConverter<TValue> valueConverter = GetValueConverter(ref state);

if (!state.SupportContinuation && valueConverter.CanUseDirectReadOrWrite)
{
// Fast path that avoids validation and extra indirection.
do
{
string key = GetKeyName(enumerator.Current.Key, ref state, options);
writer.WritePropertyName(key);
keyConverter.OnTryWrite(writer, enumerator.Current.Key, options, ref state);
// TODO: https://github.com/dotnet/runtime/issues/32523
converter.Write(writer, enumerator.Current.Value!, options);
valueConverter.Write(writer, enumerator.Current.Value!, options);
} while (enumerator.MoveNext());
}
else
Expand All @@ -77,14 +78,14 @@ protected internal override bool OnWriteResume(
}

TValue element = enumerator.Current.Value;

if (state.Current.PropertyState < StackFramePropertyState.Name)
{
state.Current.PropertyState = StackFramePropertyState.Name;
string key = GetKeyName(enumerator.Current.Key, ref state, options);
writer.WritePropertyName(key);
keyConverter.OnTryWrite(writer, enumerator.Current.Key, options, ref state);
}

if (!converter.TryWrite(writer, element, options, ref state))
if (!valueConverter.TryWrite(writer, element, options, ref state))
{
state.Current.CollectionEnumerator = enumerator;
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ namespace System.Text.Json.Serialization.Converters
/// representing the dictionary element key and value.
/// </summary>
internal sealed class IDictionaryConverter<TCollection>
: DictionaryDefaultConverter<TCollection, object?>
: DictionaryDefaultConverter<TCollection, string, object?>
where TCollection : IDictionary
{
protected override void Add(object? value, JsonSerializerOptions options, ref ReadStack state)
protected override void Add(string _, object? value, JsonSerializerOptions options, ref ReadStack state)
{
Debug.Assert(state.Current.ReturnValue is IDictionary);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ namespace System.Text.Json.Serialization.Converters
/// (de)serializes as a JSON object with properties representing the dictionary element key and value.
/// </summary>
internal sealed class IDictionaryOfStringTValueConverter<TCollection, TValue>
: DictionaryDefaultConverter<TCollection, TValue>
: DictionaryDefaultConverter<TCollection, string, TValue>
where TCollection : IDictionary<string, TValue>
{
protected override void Add(TValue value, JsonSerializerOptions options, ref ReadStack state)
protected override void Add(string _, TValue value, JsonSerializerOptions options, ref ReadStack state)
{
Debug.Assert(state.Current.ReturnValue is TCollection);

Expand Down
Loading