diff --git a/src/NJsonSchema/JsonSchema.cs b/src/NJsonSchema/JsonSchema.cs index 29576639e..e80fccc29 100644 --- a/src/NJsonSchema/JsonSchema.cs +++ b/src/NJsonSchema/JsonSchema.cs @@ -25,7 +25,9 @@ namespace NJsonSchema public partial class JsonSchema : IDocumentPathProvider { internal static readonly HashSet JsonSchemaPropertiesCache = - [..typeof(JsonSchema).GetContextualProperties().Select(p => p.Name).ToArray()]; + [ + ..typeof(JsonSchema).GetContextualProperties().Select(p => p.Name) + ]; private const SchemaType SerializationSchemaType = SchemaType.JsonSchema; diff --git a/src/NJsonSchema/Visitors/AsyncJsonReferenceVisitorBase.cs b/src/NJsonSchema/Visitors/AsyncJsonReferenceVisitorBase.cs index 7c2c38499..fd5f315c0 100644 --- a/src/NJsonSchema/Visitors/AsyncJsonReferenceVisitorBase.cs +++ b/src/NJsonSchema/Visitors/AsyncJsonReferenceVisitorBase.cs @@ -63,13 +63,12 @@ public virtual async Task VisitAsync(object obj, CancellationToken cancellationT protected virtual async Task VisitAsync(object obj, string path, string? typeNameHint, ISet checkedObjects, Action replacer, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - if (obj == null || checkedObjects.Contains(obj)) + + if (obj == null || obj is string || !checkedObjects.Add(obj)) { return; } - checkedObjects.Add(obj); - if (obj is IJsonReference reference) { var newReference = await VisitJsonReferenceAsync(reference, path, typeNameHint, cancellationToken).ConfigureAwait(false); @@ -174,21 +173,29 @@ await VisitAsync(p.Value, path + "/definitions/" + p.Key, p.Key, checkedObjects, } } - if (obj is not string && obj is not JToken && obj.GetType() != typeof(JsonSchema)) // Reflection fallback + if (obj is not JToken && obj.GetType() != typeof(JsonSchema)) // Reflection fallback { + var pathPrefix = path + "/"; if (_contractResolver.ResolveContract(obj.GetType()) is JsonObjectContract contract) { - foreach (var property in contract.Properties.Where(p => - { - bool isJsonSchemaProperty = obj is JsonSchema && p.UnderlyingName != null && JsonSchema.JsonSchemaPropertiesCache.Contains(p.UnderlyingName); - return !isJsonSchemaProperty && !p.Ignored && - p.ShouldSerialize?.Invoke(obj) != false; - })) + foreach (var p in contract.Properties) { - var value = property.ValueProvider?.GetValue(obj); + var isJsonSchemaProperty = obj is JsonSchema && p.UnderlyingName != null && JsonSchema.JsonSchemaPropertiesCache.Contains(p.UnderlyingName); + if (isJsonSchemaProperty + || p.Ignored + || p.PropertyType == typeof(string) + || p.PropertyType?.IsPrimitive == true + || p.ShouldSerialize?.Invoke(obj) == false) + { + continue; + } + + var value = p.ValueProvider?.GetValue(obj); if (value != null) { - await VisitAsync(value, path + "/" + property.PropertyName, property.PropertyName, checkedObjects, o => property.ValueProvider?.SetValue(obj, o), cancellationToken).ConfigureAwait(false); + // to avoid closure allocations + var temp = p.ValueProvider; + await VisitAsync(value, pathPrefix + p.PropertyName, p.PropertyName, checkedObjects, o => temp?.SetValue(obj, o), cancellationToken).ConfigureAwait(false); } } } @@ -199,7 +206,7 @@ await VisitAsync(p.Value, path + "/definitions/" + p.Key, p.Key, checkedObjects, var value = dictionary[key]; if (value != null) { - await VisitAsync(value, path + "/" + key, key.ToString(), checkedObjects, o => + await VisitAsync(value, pathPrefix + key, key.ToString(), checkedObjects, o => { if (o != null) { @@ -224,7 +231,7 @@ await VisitAsync(value, path + "/" + key, key.ToString(), checkedObjects, o => var value = property.GetValue(obj); if (value != null) { - await VisitAsync(value, path + "/" + property.Name, property.Name, checkedObjects, o => property.SetValue(obj, o), cancellationToken).ConfigureAwait(false); + await VisitAsync(value, pathPrefix + property.Name, property.Name, checkedObjects, o => property.SetValue(obj, o), cancellationToken).ConfigureAwait(false); } } }