diff --git a/src/Dahomey.Json/JsonSerializerOptionsExtensions.cs b/src/Dahomey.Json/JsonSerializerOptionsExtensions.cs index 8301241..4ed663d 100644 --- a/src/Dahomey.Json/JsonSerializerOptionsExtensions.cs +++ b/src/Dahomey.Json/JsonSerializerOptionsExtensions.cs @@ -65,11 +65,6 @@ public static void SetReadOnlyPropertyHandling(this JsonSerializerOptions option options.GetState().ReadOnlyPropertyHandling = referenceHandling; } - private static JsonSerializerOptionsState GetState(this JsonSerializerOptions options) - { - return (JsonSerializerOptionsState)options.GetConverter(); - } - public static MissingMemberHandling GetMissingMemberHandling(this JsonSerializerOptions options) { return options.GetState().MissingMemberHandling; @@ -79,5 +74,10 @@ public static void SetMissingMemberHandling(this JsonSerializerOptions options, { options.GetState().MissingMemberHandling = missingMemberHandling; } + + internal static JsonSerializerOptionsState GetState(this JsonSerializerOptions options) + { + return (JsonSerializerOptionsState)options.GetConverter(); + } } } diff --git a/src/Dahomey.Json/JsonSerializerOptionsState.cs b/src/Dahomey.Json/JsonSerializerOptionsState.cs index 9703068..96228eb 100644 --- a/src/Dahomey.Json/JsonSerializerOptionsState.cs +++ b/src/Dahomey.Json/JsonSerializerOptionsState.cs @@ -1,13 +1,19 @@ -using Dahomey.Json.Serialization.Conventions; +using Dahomey.Json.Serialization; +using Dahomey.Json.Serialization.Conventions; using Dahomey.Json.Serialization.Converters.DictionaryKeys; using Dahomey.Json.Serialization.Converters.Mappings; +using Dahomey.Json.Util; using System; +using System.Collections.Concurrent; +using System.Reflection; +using System.Text; using System.Text.Json; using System.Text.Json.Serialization; +using System.Threading; namespace Dahomey.Json { - public class JsonSerializerOptionsState : JsonConverter + internal class JsonSerializerOptionsState : JsonConverter { public ObjectMappingRegistry ObjectMappingRegistry { get; } public ObjectMappingConventionRegistry ObjectMappingConventionRegistry { get; } @@ -16,6 +22,12 @@ public class JsonSerializerOptionsState : JsonConverter DefaultValues { get; } + public readonly byte[] ID_MEMBER_NAME = Encoding.ASCII.GetBytes("$id"); + public readonly byte[] REF_MEMBER_NAME = Encoding.ASCII.GetBytes("$ref"); + public readonly byte[] VALUES_MEMBER_NAME = Encoding.ASCII.GetBytes("$values"); + public AsyncLocal CurrentReferenceContext { get; } = new AsyncLocal(); public JsonSerializerOptionsState(JsonSerializerOptions options) { @@ -23,6 +35,24 @@ public JsonSerializerOptionsState(JsonSerializerOptions options) ObjectMappingConventionRegistry = new ObjectMappingConventionRegistry(); DiscriminatorConventionRegistry = new DiscriminatorConventionRegistry(options); DictionaryKeyConverterRegistry = new DictionaryKeyConverterRegistry(options); + + MethodInfo? method = typeof(Utf8JsonWriter).GetMethod( + nameof(Utf8JsonWriter.WriteNumberValue), + BindingFlags.Instance | BindingFlags.NonPublic, + null, + new[] { typeof(ReadOnlySpan) }, + null); + + if (method == null) + { + throw new JsonException("Unexpected"); + } + + WriteFormatedNmberValueFunc = + (Utf8JsonWriterExtensions.WriteFormatedNumberValueFuncDelegate)method + .CreateDelegate(typeof(Utf8JsonWriterExtensions.WriteFormatedNumberValueFuncDelegate)); + + DefaultValues = new ConcurrentDictionary(); } public override JsonSerializerOptionsState Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) diff --git a/src/Dahomey.Json/Serialization/Converters/AbstractCollectionConverter.cs b/src/Dahomey.Json/Serialization/Converters/AbstractCollectionConverter.cs index 4708118..139bf1a 100644 --- a/src/Dahomey.Json/Serialization/Converters/AbstractCollectionConverter.cs +++ b/src/Dahomey.Json/Serialization/Converters/AbstractCollectionConverter.cs @@ -44,7 +44,7 @@ public override TC Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSeria public override void Read(ref Utf8JsonReader reader, ref TC obj, JsonSerializerOptions options) { - using (new DepthHandler(options)) + using (ReferenceHandler referenceHandler = new ReferenceHandler(options)) { string? id = null; @@ -57,21 +57,22 @@ public override void Read(ref Utf8JsonReader reader, ref TC obj, JsonSerializerO reader.Read(); ReadOnlySpan memberName = reader.GetRawString(); + JsonSerializerOptionsState state = options.GetState(); - if (memberName.SequenceEqual(ReferenceHandler.ID_MEMBER_NAME)) + if (memberName.SequenceEqual(state.ID_MEMBER_NAME)) { reader.Read(); id = reader.GetString(); reader.Read(); memberName = reader.GetRawString(); - if (!memberName.SequenceEqual(ReferenceHandler.VALUES_MEMBER_NAME)) + if (!memberName.SequenceEqual(state.VALUES_MEMBER_NAME)) { throw new JsonException("Expected $values member name"); } reader.Read(); } - else if (memberName.SequenceEqual(ReferenceHandler.REF_MEMBER_NAME)) + else if (memberName.SequenceEqual(state.REF_MEMBER_NAME)) { reader.Read(); string? @ref = reader.GetString(); @@ -81,7 +82,7 @@ public override void Read(ref Utf8JsonReader reader, ref TC obj, JsonSerializerO throw new JsonException($"Cannot resolve null reference"); } - object? @object = SerializationContext.Current.ReferenceHandler.ResolveReference(@ref); + object? @object = referenceHandler.ResolveReference(@ref); if (@object == null) { @@ -129,7 +130,7 @@ public override void Read(ref Utf8JsonReader reader, ref TC obj, JsonSerializerO obj = InstantiateCollection(workingCollection); if (!string.IsNullOrEmpty(id)) { - SerializationContext.Current.ReferenceHandler.AddReference(obj, id); + referenceHandler.AddReference(obj, id); } } else if (obj is ICollection collection) @@ -154,28 +155,23 @@ public override void Write(Utf8JsonWriter writer, TC value, JsonSerializerOption return; } - using (new DepthHandler(options)) + using (ReferenceHandler referenceHandler = new ReferenceHandler(options)) { - ReferenceHandler? referenceResolver = null; - if (_referenceHandling == ReferenceHandling.Ignore) - { - referenceResolver = SerializationContext.Current.ReferenceHandler; - } - else if (_referenceHandling == ReferenceHandling.Preserve) + if (_referenceHandling == ReferenceHandling.Preserve) { writer.WriteStartObject(); - referenceResolver = SerializationContext.Current.ReferenceHandler; - string reference = referenceResolver.GetReference(value, out bool firstReference); + string reference = referenceHandler.GetReference(value, out bool firstReference); + JsonSerializerOptionsState state = options.GetState(); if (firstReference) { - writer.WriteString(ReferenceHandler.ID_MEMBER_NAME, reference); - writer.WritePropertyName(ReferenceHandler.VALUES_MEMBER_NAME); + writer.WriteString(state.ID_MEMBER_NAME, reference); + writer.WritePropertyName(state.VALUES_MEMBER_NAME); } else { - writer.WriteString(ReferenceHandler.REF_MEMBER_NAME, reference); + writer.WriteString(state.REF_MEMBER_NAME, reference); writer.WriteEndObject(); return; } @@ -187,13 +183,13 @@ public override void Write(Utf8JsonWriter writer, TC value, JsonSerializerOption { if (_referenceHandling == ReferenceHandling.Ignore) { - if (referenceResolver!.IsReferenced(item!)) + if (referenceHandler!.IsReferenced(item!)) { continue; } else { - referenceResolver.AddReference(item!); + referenceHandler.AddReference(item!); } } diff --git a/src/Dahomey.Json/Serialization/Converters/AbstractDictionaryConverter.cs b/src/Dahomey.Json/Serialization/Converters/AbstractDictionaryConverter.cs index 7f25d8b..8398e75 100644 --- a/src/Dahomey.Json/Serialization/Converters/AbstractDictionaryConverter.cs +++ b/src/Dahomey.Json/Serialization/Converters/AbstractDictionaryConverter.cs @@ -44,7 +44,7 @@ public override TC Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSeria public override void Read(ref Utf8JsonReader reader, ref TC obj, JsonSerializerOptions options) { - using (new DepthHandler(options)) + using (new ReferenceHandler(options)) { if (reader.TokenType != JsonTokenType.StartObject) { @@ -94,7 +94,7 @@ public override void Write(Utf8JsonWriter writer, TC value, JsonSerializerOption return; } - using (new DepthHandler(options)) + using (new ReferenceHandler(options)) { writer.WriteStartObject(); diff --git a/src/Dahomey.Json/Serialization/Converters/JsonNodeConverter.cs b/src/Dahomey.Json/Serialization/Converters/JsonNodeConverter.cs index 7460105..527f593 100644 --- a/src/Dahomey.Json/Serialization/Converters/JsonNodeConverter.cs +++ b/src/Dahomey.Json/Serialization/Converters/JsonNodeConverter.cs @@ -14,13 +14,13 @@ public override JsonNode Read(ref Utf8JsonReader reader, Type typeToConvert, Jso switch (reader.TokenType) { case JsonTokenType.StartObject: - using (new DepthHandler(options)) + using (new ReferenceHandler(options)) { return ReadObject(ref reader, options); } case JsonTokenType.StartArray: - using (new DepthHandler(options)) + using (new ReferenceHandler(options)) { return ReadArray(ref reader, options); } @@ -96,14 +96,14 @@ public override void Write(Utf8JsonWriter writer, JsonNode value, JsonSerializer switch (value.ValueKind) { case JsonValueKind.Object: - using (new DepthHandler(options)) + using (new ReferenceHandler(options)) { WriteObject(writer, (JsonObject)value, options); } break; case JsonValueKind.Array: - using (new DepthHandler(options)) + using (new ReferenceHandler(options)) { WriteArray(writer, (JsonArray)value, options); } @@ -114,7 +114,7 @@ public override void Write(Utf8JsonWriter writer, JsonNode value, JsonSerializer break; case JsonValueKind.Number: - writer.WriteNumberValue(Encoding.ASCII.GetBytes(value.ToString())); + writer.WriteNumberValue(options, Encoding.ASCII.GetBytes(value.ToString())); break; case JsonValueKind.True: diff --git a/src/Dahomey.Json/Serialization/Converters/Mappings/AnonymousObjectMappingConventionProvider.cs b/src/Dahomey.Json/Serialization/Converters/Mappings/AnonymousObjectMappingConventionProvider.cs index 90fad72..f52dec0 100644 --- a/src/Dahomey.Json/Serialization/Converters/Mappings/AnonymousObjectMappingConventionProvider.cs +++ b/src/Dahomey.Json/Serialization/Converters/Mappings/AnonymousObjectMappingConventionProvider.cs @@ -5,7 +5,7 @@ namespace Dahomey.Json.Serialization.Converters.Mappings { public class AnonymousObjectMappingConventionProvider : IObjectMappingConventionProvider { - private static readonly AnonymousObjectMappingConvention _anonymousObjectMappingConvention + private readonly AnonymousObjectMappingConvention _anonymousObjectMappingConvention = new AnonymousObjectMappingConvention(); public IObjectMappingConvention? GetConvention(Type type) diff --git a/src/Dahomey.Json/Serialization/Converters/Mappings/DefaultObjectMappingConventionProvider.cs b/src/Dahomey.Json/Serialization/Converters/Mappings/DefaultObjectMappingConventionProvider.cs index 1552325..f693ea8 100644 --- a/src/Dahomey.Json/Serialization/Converters/Mappings/DefaultObjectMappingConventionProvider.cs +++ b/src/Dahomey.Json/Serialization/Converters/Mappings/DefaultObjectMappingConventionProvider.cs @@ -4,7 +4,7 @@ namespace Dahomey.Json.Serialization.Converters.Mappings { public class DefaultObjectMappingConventionProvider : IObjectMappingConventionProvider { - private static readonly DefaultObjectMappingConvention _defaultObjectMappingConvention = new DefaultObjectMappingConvention(); + private readonly DefaultObjectMappingConvention _defaultObjectMappingConvention = new DefaultObjectMappingConvention(); public IObjectMappingConvention GetConvention(Type type) { diff --git a/src/Dahomey.Json/Serialization/Converters/Mappings/MemberMapping.cs b/src/Dahomey.Json/Serialization/Converters/Mappings/MemberMapping.cs index 371ec55..602028b 100644 --- a/src/Dahomey.Json/Serialization/Converters/Mappings/MemberMapping.cs +++ b/src/Dahomey.Json/Serialization/Converters/Mappings/MemberMapping.cs @@ -31,7 +31,7 @@ public MemberMapping(JsonSerializerOptions options, _options = options; MemberInfo = memberInfo; MemberType = memberType; - DefaultValue = Default.Value(memberType); + DefaultValue = Default.Value(options, memberType); } public MemberMapping SetMemberName(string memberName) diff --git a/src/Dahomey.Json/Serialization/Converters/Mappings/StandardObjectMappingConventionProvider.cs b/src/Dahomey.Json/Serialization/Converters/Mappings/StandardObjectMappingConventionProvider.cs index 266e2b2..ca51964 100644 --- a/src/Dahomey.Json/Serialization/Converters/Mappings/StandardObjectMappingConventionProvider.cs +++ b/src/Dahomey.Json/Serialization/Converters/Mappings/StandardObjectMappingConventionProvider.cs @@ -5,7 +5,7 @@ namespace Dahomey.Json.Serialization.Converters.Mappings { public class StandardObjectMappingConventionProvider : IObjectMappingConventionProvider { - private static readonly StandardObjectMappingConvention _standardObjectMappingConvention = new StandardObjectMappingConvention(); + private readonly StandardObjectMappingConvention _standardObjectMappingConvention = new StandardObjectMappingConvention(); public IObjectMappingConvention? GetConvention(Type type) { diff --git a/src/Dahomey.Json/Serialization/Converters/ObjectConverter.cs b/src/Dahomey.Json/Serialization/Converters/ObjectConverter.cs index 31e9a85..eda1e41 100644 --- a/src/Dahomey.Json/Serialization/Converters/ObjectConverter.cs +++ b/src/Dahomey.Json/Serialization/Converters/ObjectConverter.cs @@ -78,6 +78,7 @@ public static MemberConverters Create(JsonSerializerOptions options, IObjectMapp private readonly IDiscriminatorConvention? _discriminatorConvention; private readonly ReferenceHandling _referenceHandling; private readonly MissingMemberHandling _missingMemberHandling; + private readonly ReadOnlyMemoryEqualityComparer _readOnlyMemoryEqualityComparer = new ReadOnlyMemoryEqualityComparer(); public IReadOnlyList MemberConvertersForWrite => _memberConverters.Value.ForWrite; public IReadOnlyList RequiredMemberConvertersForRead => _memberConverters.Value.RequiredForRead; @@ -137,7 +138,7 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial public override void Read(ref Utf8JsonReader reader, ref T obj, JsonSerializerOptions options) { - using (new DepthHandler(options)) + using (ReferenceHandler referenceHandler = new ReferenceHandler(options)) { if (reader.TokenType != JsonTokenType.StartObject) { @@ -150,13 +151,14 @@ public override void Read(ref Utf8JsonReader reader, ref T obj, JsonSerializerOp { reader.Read(); ReadOnlySpan memberName = reader.GetRawString(); + JsonSerializerOptionsState state = options.GetState(); - if (memberName.SequenceEqual(ReferenceHandler.ID_MEMBER_NAME)) + if (memberName.SequenceEqual(state.ID_MEMBER_NAME)) { reader.Read(); id = reader.GetString(); } - else if (memberName.SequenceEqual(ReferenceHandler.REF_MEMBER_NAME)) + else if (memberName.SequenceEqual(state.REF_MEMBER_NAME)) { reader.Read(); string? @ref = reader.GetString(); @@ -166,7 +168,7 @@ public override void Read(ref Utf8JsonReader reader, ref T obj, JsonSerializerOp throw new JsonException($"Cannot resolve null reference"); } - object? @object = SerializationContext.Current.ReferenceHandler.ResolveReference(@ref); + object? @object = referenceHandler.ResolveReference(@ref); if (@object == null) { @@ -206,7 +208,7 @@ public override void Read(ref Utf8JsonReader reader, ref T obj, JsonSerializerOp throw new JsonException(); } - ReadMember(ref reader, ref obj, ref converter, options, ref creatorValues, ref regularValues, readMembers, id); + ReadMember(ref reader, ref obj, ref converter, options, ref creatorValues, ref regularValues, readMembers, id, referenceHandler); } if (creatorValues != null && converter != null) @@ -215,7 +217,7 @@ public override void Read(ref Utf8JsonReader reader, ref T obj, JsonSerializerOp if (!string.IsNullOrEmpty(id)) { - SerializationContext.Current.ReferenceHandler.AddReference(obj, id); + referenceHandler.AddReference(obj, id); } if (converter.ObjectMapping.OnDeserializingMethod != null) @@ -262,7 +264,7 @@ public override void Read(ref Utf8JsonReader reader, ref T obj, JsonSerializerOp if (!string.IsNullOrEmpty(id)) { - SerializationContext.Current.ReferenceHandler.AddReference(obj, id); + referenceHandler.AddReference(obj, id); } } @@ -276,7 +278,7 @@ public override void Read(ref Utf8JsonReader reader, ref T obj, JsonSerializerOp private void ReadMember(ref Utf8JsonReader reader, ref T obj, ref IObjectConverter? converter, JsonSerializerOptions options, ref Dictionary, object>? creatorValues, ref Dictionary, object>? regularValues, - HashSet? readMembers, string? id) + HashSet? readMembers, string? id, ReferenceHandler referenceHandler) { if (obj == null || converter == null) { @@ -311,8 +313,8 @@ private void ReadMember(ref Utf8JsonReader reader, ref T obj, ref IObjectConvert if (converter.ObjectMapping.CreatorMapping != null) { - creatorValues = new Dictionary, object>(ReadOnlyMemoryEqualityComparer.Instance); - regularValues = new Dictionary, object>(ReadOnlyMemoryEqualityComparer.Instance); + creatorValues = new Dictionary, object>(_readOnlyMemoryEqualityComparer); + regularValues = new Dictionary, object>(_readOnlyMemoryEqualityComparer); } } @@ -324,7 +326,7 @@ private void ReadMember(ref Utf8JsonReader reader, ref T obj, ref IObjectConvert if (!string.IsNullOrEmpty(id)) { - SerializationContext.Current.ReferenceHandler.AddReference(obj, id); + referenceHandler.AddReference(obj, id); } } @@ -478,7 +480,7 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions return; } - using (new DepthHandler(options)) + using (ReferenceHandler referenceHandler = new ReferenceHandler(options)) { if (_objectMapping.OnSerializingMethod != null) { @@ -489,30 +491,28 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions if (_referenceHandling == ReferenceHandling.Ignore) { - ReferenceHandler referenceResolver = SerializationContext.Current.ReferenceHandler; - - if (referenceResolver.IsReferenced(value)) + if (referenceHandler.IsReferenced(value)) { writer.WriteNullValue(); return; } else { - referenceResolver.AddReference(value); + referenceHandler.AddReference(value); } } else if (_referenceHandling == ReferenceHandling.Preserve) { - ReferenceHandler referenceResolver = SerializationContext.Current.ReferenceHandler; - string reference = referenceResolver.GetReference(value, out bool firstReference); + string reference = referenceHandler.GetReference(value, out bool firstReference); + JsonSerializerOptionsState state = options.GetState(); if (firstReference) { - writer.WriteString(ReferenceHandler.ID_MEMBER_NAME, reference); + writer.WriteString(state.ID_MEMBER_NAME, reference); } else { - writer.WriteString(ReferenceHandler.REF_MEMBER_NAME, reference); + writer.WriteString(state.REF_MEMBER_NAME, reference); writer.WriteEndObject(); return; } diff --git a/src/Dahomey.Json/Serialization/DepthHandler.cs b/src/Dahomey.Json/Serialization/DepthHandler.cs deleted file mode 100644 index e578ecf..0000000 --- a/src/Dahomey.Json/Serialization/DepthHandler.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Text.Json; - -namespace Dahomey.Json.Serialization -{ - public struct DepthHandler : IDisposable - { - private readonly SerializationContext _context; - - public DepthHandler(JsonSerializerOptions options) - { - _context = SerializationContext.Current; - int maxDepth = options.MaxDepth == 0 ? 64 : options.MaxDepth; - if (++_context.Depth > maxDepth) - { - throw new JsonException($"MaxDepth of {maxDepth} has been exceeded."); - } - } - - public void Dispose() - { - if (--_context.Depth == 0) - { - _context.Reset(); - } - } - } -} diff --git a/src/Dahomey.Json/Serialization/ReferenceContext.cs b/src/Dahomey.Json/Serialization/ReferenceContext.cs new file mode 100644 index 0000000..74f3025 --- /dev/null +++ b/src/Dahomey.Json/Serialization/ReferenceContext.cs @@ -0,0 +1,60 @@ +using System.Collections.Concurrent; +using System.Text; + +namespace Dahomey.Json.Serialization +{ + public class ReferenceContext + { + public static readonly byte[] ID_MEMBER_NAME = Encoding.ASCII.GetBytes("$id"); + public static readonly byte[] REF_MEMBER_NAME = Encoding.ASCII.GetBytes("$ref"); + public static readonly byte[] VALUES_MEMBER_NAME = Encoding.ASCII.GetBytes("$values"); + + private readonly ConcurrentDictionary _refs2Objects = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _objects2Refs = new ConcurrentDictionary(); + private int _nextReference = 1; + public int Depth { get; set; } + + public bool IsReferenced(object obj) + { + return _objects2Refs.ContainsKey(obj); + } + + public void AddReference(object obj, string? reference = null) + { + if (reference == null) + { + reference = (_nextReference++).ToString(); + } + _refs2Objects.TryAdd(reference, obj); + _objects2Refs.TryAdd(obj, reference); + } + + public string GetReference(object obj, out bool firstReference) + { + bool firstRef = false; + string @ref = _objects2Refs.GetOrAdd(obj, newObj => + { + string reference = (_nextReference++).ToString(); + _refs2Objects.TryAdd(reference, newObj); + firstRef = true; + return reference; + }); + + firstReference = firstRef; + return @ref; + } + + public object? ResolveReference(string @ref) + { + _refs2Objects.TryGetValue(@ref, out object? @object); + return @object; + } + + public void Reset() + { + _objects2Refs.Clear(); + _refs2Objects.Clear(); + _nextReference = 1; + } + } +} diff --git a/src/Dahomey.Json/Serialization/ReferenceHandler.cs b/src/Dahomey.Json/Serialization/ReferenceHandler.cs index 04e88d2..5d19b80 100644 --- a/src/Dahomey.Json/Serialization/ReferenceHandler.cs +++ b/src/Dahomey.Json/Serialization/ReferenceHandler.cs @@ -1,59 +1,60 @@ -using System.Collections.Concurrent; -using System.Text; +using System; +using System.Text.Json; +using System.Threading; namespace Dahomey.Json.Serialization { - public class ReferenceHandler + public struct ReferenceHandler : IDisposable { - public static readonly byte[] ID_MEMBER_NAME = Encoding.ASCII.GetBytes("$id"); - public static readonly byte[] REF_MEMBER_NAME = Encoding.ASCII.GetBytes("$ref"); - public static readonly byte[] VALUES_MEMBER_NAME = Encoding.ASCII.GetBytes("$values"); + private readonly ReferenceContext _context; - private readonly ConcurrentDictionary _refs2Objects = new ConcurrentDictionary(); - private readonly ConcurrentDictionary _objects2Refs = new ConcurrentDictionary(); - private int _nextReference = 1; + public ReferenceHandler(JsonSerializerOptions options) + { + AsyncLocal currentReferenceContext = options.GetState().CurrentReferenceContext; + ReferenceContext? context = currentReferenceContext.Value; + if (context == null) + { + _context = new ReferenceContext(); + currentReferenceContext.Value = _context; + } + else + { + _context = context; + } + + int maxDepth = options.MaxDepth == 0 ? 64 : options.MaxDepth; + if (++_context.Depth > maxDepth) + { + throw new JsonException($"MaxDepth of {maxDepth} has been exceeded."); + } + } public bool IsReferenced(object obj) { - return _objects2Refs.ContainsKey(obj); + return _context.IsReferenced(obj); } public void AddReference(object obj, string? reference = null) { - if (reference == null) - { - reference = (_nextReference++).ToString(); - } - _refs2Objects.TryAdd(reference, obj); - _objects2Refs.TryAdd(obj, reference); + _context.AddReference(obj, reference); } public string GetReference(object obj, out bool firstReference) { - bool firstRef = false; - string @ref = _objects2Refs.GetOrAdd(obj, newObj => - { - string reference = (_nextReference++).ToString(); - _refs2Objects.TryAdd(reference, newObj); - firstRef = true; - return reference; - }); - - firstReference = firstRef; - return @ref; + return _context.GetReference(obj, out firstReference); } - public object? ResolveReference(string @ref) + public object? ResolveReference(string reference) { - _refs2Objects.TryGetValue(@ref, out object? @object); - return @object; + return _context.ResolveReference(reference); } - public void Reset() + public void Dispose() { - _objects2Refs.Clear(); - _refs2Objects.Clear(); - _nextReference = 1; + if (--_context.Depth == 0) + { + _context.Reset(); + } } } } diff --git a/src/Dahomey.Json/Serialization/SerializationContext.cs b/src/Dahomey.Json/Serialization/SerializationContext.cs deleted file mode 100644 index 6ce9c41..0000000 --- a/src/Dahomey.Json/Serialization/SerializationContext.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Threading; - -namespace Dahomey.Json.Serialization -{ - public class SerializationContext - { - private readonly static AsyncLocal _current - = new AsyncLocal(); - - private Lazy _referenceHandler = new Lazy(() => new ReferenceHandler()); - - public static SerializationContext Current - { - get - { - SerializationContext? context = _current.Value; - if (context == null) - { - context = new SerializationContext(); - _current.Value = context; - } - - return context; - } - } - - public int Depth { get; set; } - public ReferenceHandler ReferenceHandler => _referenceHandler.Value; - - public void Reset() - { - Depth = 0; - if (_referenceHandler.IsValueCreated) - { - _referenceHandler.Value.Reset(); - } - } - } -} diff --git a/src/Dahomey.Json/Util/Default.cs b/src/Dahomey.Json/Util/Default.cs index d3979c7..e76276b 100644 --- a/src/Dahomey.Json/Util/Default.cs +++ b/src/Dahomey.Json/Util/Default.cs @@ -1,15 +1,13 @@ using System; -using System.Collections.Concurrent; using System.Linq; +using System.Text.Json; namespace Dahomey.Json.Util { public class Default { - private static readonly ConcurrentDictionary _values = new ConcurrentDictionary(); - public static T Value() => default!; - public static object? Value(Type type) => _values.GetOrAdd(type, GenerateValue); + public static object? Value(JsonSerializerOptions options, Type type) => options.GetState().DefaultValues.GetOrAdd(type, GenerateValue); private static object? GenerateValue(Type type) { diff --git a/src/Dahomey.Json/Util/ReadOnlyMemoryEqualityComparer.cs b/src/Dahomey.Json/Util/ReadOnlyMemoryEqualityComparer.cs index 7d57471..fa48da9 100644 --- a/src/Dahomey.Json/Util/ReadOnlyMemoryEqualityComparer.cs +++ b/src/Dahomey.Json/Util/ReadOnlyMemoryEqualityComparer.cs @@ -6,8 +6,6 @@ namespace Dahomey.Json.Util public class ReadOnlyMemoryEqualityComparer : IEqualityComparer> where T : IEquatable { - public static ReadOnlyMemoryEqualityComparer Instance { get; } = new ReadOnlyMemoryEqualityComparer(); - public bool Equals(ReadOnlyMemory x, ReadOnlyMemory y) { return x.Span.SequenceEqual(y.Span); diff --git a/src/Dahomey.Json/Util/Utf8JsonWriterExtensions.cs b/src/Dahomey.Json/Util/Utf8JsonWriterExtensions.cs index 15cdbc2..0200aa1 100644 --- a/src/Dahomey.Json/Util/Utf8JsonWriterExtensions.cs +++ b/src/Dahomey.Json/Util/Utf8JsonWriterExtensions.cs @@ -1,36 +1,15 @@ using System; -using System.Reflection; using System.Text.Json; namespace Dahomey.Json.Util { public static class Utf8JsonWriterExtensions { - private delegate void WriteFormatedNumberValueFuncDelegate(Utf8JsonWriter writer, ReadOnlySpan utf8FormattedNumber); - private static readonly WriteFormatedNumberValueFuncDelegate s_writeFormatedNmberValueFunc; + internal delegate void WriteFormatedNumberValueFuncDelegate(Utf8JsonWriter writer, ReadOnlySpan utf8FormattedNumber); - static Utf8JsonWriterExtensions() + public static void WriteNumberValue(this Utf8JsonWriter writer, JsonSerializerOptions options, ReadOnlySpan utf8FormattedNumber) { - MethodInfo? method = typeof(Utf8JsonWriter).GetMethod( - nameof(Utf8JsonWriter.WriteNumberValue), - BindingFlags.Instance | BindingFlags.NonPublic, - null, - new[] { typeof(ReadOnlySpan) }, - null); - - if (method == null) - { - throw new JsonException("Unexpected"); - } - - s_writeFormatedNmberValueFunc = - (WriteFormatedNumberValueFuncDelegate)method - .CreateDelegate(typeof(WriteFormatedNumberValueFuncDelegate)); - } - - public static void WriteNumberValue(this Utf8JsonWriter writer, ReadOnlySpan utf8FormattedNumber) - { - s_writeFormatedNmberValueFunc(writer, utf8FormattedNumber); + options.GetState().WriteFormatedNmberValueFunc(writer, utf8FormattedNumber); } } }