Skip to content

Commit

Permalink
Support stream-based NJsonSchema construction (RicoSuter#1467)
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma authored Dec 23, 2021
1 parent 265c5e9 commit 477a92a
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 34 deletions.
23 changes: 22 additions & 1 deletion src/NJsonSchema/Generation/SampleJsonSchemaGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
Expand All @@ -33,6 +34,26 @@ public JsonSchema Generate(string json)
return schema;
}

/// <summary>Generates the JSON Schema for the given JSON data.</summary>
/// <param name="stream">The JSON data stream.</param>
/// <returns>The JSON Schema.</returns>
public JsonSchema Generate(Stream stream)
{
using var reader = new StreamReader(stream);
using var jsonReader = new JsonTextReader(reader);

var serializer = JsonSerializer.Create(new JsonSerializerSettings
{
DateFormatHandling = DateFormatHandling.IsoDateFormat
});

var token = serializer.Deserialize<JToken>(jsonReader);

var schema = new JsonSchema();
Generate(token, schema, schema, "Anonymous");
return schema;
}

private void Generate(JToken token, JsonSchema schema, JsonSchema rootSchema, string typeNameHint)
{
if (schema != rootSchema && token.Type == JTokenType.Object)
Expand All @@ -48,7 +69,7 @@ private void Generate(JToken token, JsonSchema schema, JsonSchema rootSchema, st
s.Type == JsonObjectType.Object &&
properties.All(p => s.Properties.ContainsKey(p.Name)));
}

if (referencedSchema == null)
{
referencedSchema = new JsonSchema();
Expand Down
108 changes: 88 additions & 20 deletions src/NJsonSchema/Infrastructure/JsonSchemaSerialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@


using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
Expand Down Expand Up @@ -84,10 +85,10 @@ public static string ToJson(object obj, SchemaType schemaType, IContractResolver
/// <param name="contractResolver">The contract resolver.</param>
/// <returns>The deserialized schema.</returns>
[Obsolete("Use FromJsonAsync with cancellation token instead.")]
public static async Task<T> FromJsonAsync<T>(string json, SchemaType schemaType, string documentPath,
public static Task<T> FromJsonAsync<T>(string json, SchemaType schemaType, string documentPath,
Func<T, JsonReferenceResolver> referenceResolverFactory, IContractResolver contractResolver)
{
return await FromJsonAsync(json, schemaType, documentPath, referenceResolverFactory, contractResolver, CancellationToken.None).ConfigureAwait(false);
return FromJsonAsync(json, schemaType, documentPath, referenceResolverFactory, contractResolver, CancellationToken.None);
}

/// <summary>Deserializes JSON data to a schema with reference handling.</summary>
Expand All @@ -98,29 +99,63 @@ public static async Task<T> FromJsonAsync<T>(string json, SchemaType schemaType,
/// <param name="contractResolver">The contract resolver.</param>
/// <param name="cancellationToken">The cancellation token</param>
/// <returns>The deserialized schema.</returns>
public static async Task<T> FromJsonAsync<T>(string json, SchemaType schemaType, string documentPath,
public static Task<T> FromJsonAsync<T>(string json, SchemaType schemaType, string documentPath,
Func<T, JsonReferenceResolver> referenceResolverFactory, IContractResolver contractResolver, CancellationToken cancellationToken = default)
{
var loader = () => FromJson<T>(json, contractResolver);
return FromJsonWithLoaderAsync(loader, schemaType, documentPath, referenceResolverFactory, contractResolver, cancellationToken);
}

/// <summary>Deserializes JSON data to a schema with reference handling.</summary>
/// <param name="stream">The JSON data stream.</param>
/// <param name="schemaType">The schema type.</param>
/// <param name="documentPath">The document path.</param>
/// <param name="referenceResolverFactory">The reference resolver factory.</param>
/// <param name="contractResolver">The contract resolver.</param>
/// <param name="cancellationToken">The cancellation token</param>
/// <returns>The deserialized schema.</returns>
public static Task<T> FromJsonAsync<T>(Stream stream, SchemaType schemaType, string documentPath,
Func<T, JsonReferenceResolver> referenceResolverFactory, IContractResolver contractResolver, CancellationToken cancellationToken = default)
{
var loader = () => FromJson<T>(stream, contractResolver);
return FromJsonWithLoaderAsync(loader, schemaType, documentPath, referenceResolverFactory, contractResolver, cancellationToken);
}

private static async Task<T> FromJsonWithLoaderAsync<T>(
Func<T> loader,
SchemaType schemaType,
string documentPath,
Func<T, JsonReferenceResolver> referenceResolverFactory,
IContractResolver contractResolver,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
CurrentSchemaType = schemaType;

var schema = FromJson<T>(json, contractResolver);
if (schema is IDocumentPathProvider documentPathProvider)
T schema;
try
{
documentPathProvider.DocumentPath = documentPath;
}
schema = loader();
if (schema is IDocumentPathProvider documentPathProvider)
{
documentPathProvider.DocumentPath = documentPath;
}

var referenceResolver = referenceResolverFactory.Invoke(schema);
if (schema is IJsonReference referenceSchema)
{
if (!string.IsNullOrEmpty(documentPath))
var referenceResolver = referenceResolverFactory.Invoke(schema);
if (schema is IJsonReference referenceSchema)
{
referenceResolver.AddDocumentReference(documentPath, referenceSchema);
if (!string.IsNullOrEmpty(documentPath))
{
referenceResolver.AddDocumentReference(documentPath, referenceSchema);
}
}
}

await JsonSchemaReferenceUtilities.UpdateSchemaReferencesAsync(schema, referenceResolver, contractResolver).ConfigureAwait(false);
CurrentSchemaType = SchemaType.JsonSchema;
await JsonSchemaReferenceUtilities.UpdateSchemaReferencesAsync(schema, referenceResolver, contractResolver, cancellationToken).ConfigureAwait(false);
}
finally
{
CurrentSchemaType = SchemaType.JsonSchema;
}

return schema;
}
Expand All @@ -132,6 +167,44 @@ public static async Task<T> FromJsonAsync<T>(string json, SchemaType schemaType,
public static T FromJson<T>(string json, IContractResolver contractResolver)
{
IsWriting = true;
UpdateCurrentSerializerSettings<T>(contractResolver);

try
{
return JsonConvert.DeserializeObject<T>(json, CurrentSerializerSettings);
}
finally
{
CurrentSerializerSettings = null;
}
}

/// <summary>Deserializes JSON data with the given contract resolver.</summary>
/// <param name="stream">The JSON data stream.</param>
/// <param name="contractResolver">The contract resolver.</param>
/// <returns>The deserialized schema.</returns>
public static T FromJson<T>(Stream stream, IContractResolver contractResolver)
{
IsWriting = true;
UpdateCurrentSerializerSettings<T>(contractResolver);

try
{
using var reader = new StreamReader(stream);
using var jsonReader = new JsonTextReader(reader);

var serializer = JsonSerializer.Create(CurrentSerializerSettings);

return serializer.Deserialize<T>(jsonReader);
}
finally
{
CurrentSerializerSettings = null;
}
}

private static void UpdateCurrentSerializerSettings<T>(IContractResolver contractResolver)
{
CurrentSerializerSettings = new JsonSerializerSettings
{
ContractResolver = contractResolver,
Expand All @@ -140,11 +213,6 @@ public static T FromJson<T>(string json, IContractResolver contractResolver)
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
PreserveReferencesHandling = PreserveReferencesHandling.None
};

var obj = JsonConvert.DeserializeObject<T>(json, CurrentSerializerSettings);
CurrentSerializerSettings = null;

return obj;
}
}
}
62 changes: 49 additions & 13 deletions src/NJsonSchema/JsonSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
Expand Down Expand Up @@ -137,14 +138,22 @@ public static JsonSchema FromSampleJson(string data)
return generator.Generate(data);
}

/// <summary>Creates a <see cref="JsonSchema" /> from sample JSON data.</summary>
/// <returns>The JSON Schema.</returns>
public static JsonSchema FromSampleJson(Stream stream)
{
var generator = new SampleJsonSchemaGenerator();
return generator.Generate(stream);
}

/// <summary>Loads a JSON Schema from a given file path (only available in .NET 4.x).</summary>
/// <param name="filePath">The file path.</param>
/// <param name="cancellationToken">Cancellation token instance</param>
/// <returns>The JSON Schema.</returns>
public static async Task<JsonSchema> FromFileAsync(string filePath, CancellationToken cancellationToken = default)
public static Task<JsonSchema> FromFileAsync(string filePath, CancellationToken cancellationToken = default)
{
var factory = JsonReferenceResolver.CreateJsonReferenceResolverFactory(new DefaultTypeNameGenerator());
return await FromFileAsync(filePath, factory, cancellationToken).ConfigureAwait(false);
return FromFileAsync(filePath, factory, cancellationToken);
}

/// <summary>Loads a JSON Schema from a given file path (only available in .NET 4.x).</summary>
Expand All @@ -153,21 +162,26 @@ public static async Task<JsonSchema> FromFileAsync(string filePath, Cancellation
/// <param name="cancellationToken">The cancellation token</param>
/// <returns>The JSON Schema.</returns>
/// <exception cref="NotSupportedException">The System.IO.File API is not available on this platform.</exception>
public static async Task<JsonSchema> FromFileAsync(string filePath, Func<JsonSchema, JsonReferenceResolver> referenceResolverFactory, CancellationToken cancellationToken = default)
public static Task<JsonSchema> FromFileAsync(string filePath, Func<JsonSchema, JsonReferenceResolver> referenceResolverFactory, CancellationToken cancellationToken = default)
{
var data = DynamicApis.FileReadAllText(filePath);
return await FromJsonAsync(data, filePath, referenceResolverFactory, cancellationToken).ConfigureAwait(false);
#if !NETSTANDARD1_0
using var stream = File.OpenRead(filePath);
return FromJsonAsync(stream, filePath, referenceResolverFactory, cancellationToken);
#else
var json = DynamicApis.FileReadAllText(filePath);
return FromJsonAsync(json, filePath, referenceResolverFactory, cancellationToken);
#endif
}

/// <summary>Loads a JSON Schema from a given URL (only available in .NET 4.x).</summary>
/// <param name="url">The URL to the document.</param>
/// <param name="cancellationToken">The cancellation token</param>
/// <returns>The JSON Schema.</returns>
/// <exception cref="NotSupportedException">The HttpClient.GetAsync API is not available on this platform.</exception>
public static async Task<JsonSchema> FromUrlAsync(string url, CancellationToken cancellationToken = default)
public static Task<JsonSchema> FromUrlAsync(string url, CancellationToken cancellationToken = default)
{
var factory = JsonReferenceResolver.CreateJsonReferenceResolverFactory(new DefaultTypeNameGenerator());
return await FromUrlAsync(url, factory, cancellationToken).ConfigureAwait(false);
return FromUrlAsync(url, factory, cancellationToken);
}

/// <summary>Loads a JSON Schema from a given URL (only available in .NET 4.x).</summary>
Expand All @@ -186,20 +200,30 @@ public static async Task<JsonSchema> FromUrlAsync(string url, Func<JsonSchema, J
/// <param name="data">The JSON string. </param>
/// <param name="cancellationToken">The cancellation token</param>
/// <returns>The JSON Schema.</returns>
public static async Task<JsonSchema> FromJsonAsync(string data, CancellationToken cancellationToken = default)
public static Task<JsonSchema> FromJsonAsync(string data, CancellationToken cancellationToken = default)
{
return await FromJsonAsync(data, null, cancellationToken).ConfigureAwait(false);
return FromJsonAsync(data, null, cancellationToken);
}

/// <summary>Deserializes a JSON stream to a <see cref="JsonSchema"/>. </summary>
/// <param name="stream">The JSON data stream.</param>
/// <param name="cancellationToken">The cancellation token</param>
/// <returns>The JSON Schema.</returns>
public static Task<JsonSchema> FromJsonAsync(Stream stream, CancellationToken cancellationToken = default)
{
var factory = JsonReferenceResolver.CreateJsonReferenceResolverFactory(new DefaultTypeNameGenerator());
return FromJsonAsync(stream, null, factory, cancellationToken);
}

/// <summary>Deserializes a JSON string to a <see cref="JsonSchema"/>. </summary>
/// <param name="data">The JSON string. </param>
/// <param name="documentPath">The document path (URL or file path) for resolving relative document references.</param>
/// <param name="cancellationToken">The cancellation token</param>
/// <returns>The JSON Schema.</returns>
public static async Task<JsonSchema> FromJsonAsync(string data, string documentPath, CancellationToken cancellationToken = default)
public static Task<JsonSchema> FromJsonAsync(string data, string documentPath, CancellationToken cancellationToken = default)
{
var factory = JsonReferenceResolver.CreateJsonReferenceResolverFactory(new DefaultTypeNameGenerator());
return await FromJsonAsync(data, documentPath, factory, cancellationToken).ConfigureAwait(false);
return FromJsonAsync(data, documentPath, factory, cancellationToken);
}

/// <summary>Deserializes a JSON string to a <see cref="JsonSchema" />.</summary>
Expand All @@ -208,10 +232,22 @@ public static async Task<JsonSchema> FromJsonAsync(string data, string documentP
/// <param name="referenceResolverFactory">The JSON reference resolver factory.</param>
/// <param name="cancellationToken">The cancellation token</param>
/// <returns>The JSON Schema.</returns>
public static async Task<JsonSchema> FromJsonAsync(string data, string documentPath, Func<JsonSchema,
public static Task<JsonSchema> FromJsonAsync(string data, string documentPath, Func<JsonSchema,
JsonReferenceResolver> referenceResolverFactory, CancellationToken cancellationToken = default)
{
return JsonSchemaSerialization.FromJsonAsync(data, SerializationSchemaType, documentPath, referenceResolverFactory, ContractResolver.Value, cancellationToken);
}

/// <summary>Deserializes a JSON string to a <see cref="JsonSchema" />.</summary>
/// <param name="stream">The JSON data stream.</param>
/// <param name="documentPath">The document path (URL or file path) for resolving relative document references.</param>
/// <param name="referenceResolverFactory">The JSON reference resolver factory.</param>
/// <param name="cancellationToken">The cancellation token</param>
/// <returns>The JSON Schema.</returns>
public static Task<JsonSchema> FromJsonAsync(Stream stream, string documentPath, Func<JsonSchema,
JsonReferenceResolver> referenceResolverFactory, CancellationToken cancellationToken = default)
{
return await JsonSchemaSerialization.FromJsonAsync(data, SerializationSchemaType, documentPath, referenceResolverFactory, ContractResolver.Value, cancellationToken).ConfigureAwait(false);
return JsonSchemaSerialization.FromJsonAsync(stream, SerializationSchemaType, documentPath, referenceResolverFactory, ContractResolver.Value, cancellationToken);
}

internal static JsonSchema FromJsonWithCurrentSettings(object obj)
Expand Down

0 comments on commit 477a92a

Please sign in to comment.