Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow use of System.Text.Json in Request/Response examples. #149

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ trim_trailing_whitespace = true
[NuGet.Config]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
trim_trailing_whitespace = true
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ _ReSharper.*
# Visual Studio 2015 cache/options directory
.vs/
*.nupkg

# Rider settings directory
.idea
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,13 @@ namespace Swashbuckle.AspNetCore.Filters
{
internal class ExamplesConverter
{
private readonly JsonFormatter jsonFormatter;
private readonly IJsonFormatter jsonFormatter;
private readonly MvcOutputFormatter mvcOutputFormatter;
private readonly JsonSerializerSettings serializerSettings;

public ExamplesConverter(JsonFormatter jsonFormatter, MvcOutputFormatter mvcOutputFormatter, JsonSerializerSettings serializerSettings)
public ExamplesConverter(IJsonFormatter jsonFormatter, MvcOutputFormatter mvcOutputFormatter)
{
this.jsonFormatter = jsonFormatter;
this.mvcOutputFormatter = mvcOutputFormatter;
this.serializerSettings = serializerSettings;
}

public IOpenApiAny SerializeExampleXml(object exampleValue)
Expand All @@ -28,7 +26,7 @@ public IOpenApiAny SerializeExampleXml(object exampleValue)

public IOpenApiAny SerializeExampleJson(object exampleValue)
{
return new OpenApiRawString(jsonFormatter.FormatJson(exampleValue, serializerSettings));
return new OpenApiRawString(jsonFormatter.FormatJson(exampleValue));
}

private static IDictionary<string, OpenApiExample> ToOpenApiExamplesDictionary(
Expand Down
7 changes: 7 additions & 0 deletions src/Swashbuckle.AspNetCore.Filters/Examples/IJsonFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Swashbuckle.AspNetCore.Filters
{
internal interface IJsonFormatter
{
string FormatJson(object examples);
}
}
13 changes: 0 additions & 13 deletions src/Swashbuckle.AspNetCore.Filters/Examples/JsonFormatter.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace Swashbuckle.AspNetCore.Filters
{
internal class JsonFormatterProvider
{
private readonly SerializerSettingsDuplicator serializerSettingsDuplicator;
private readonly SerializerOptionsDuplicator serializerOptionsDuplicator;
private readonly SerializationMode defaultSerializationMode;

public JsonFormatterProvider(SerializerSettingsDuplicator serializerSettingsDuplicator, SerializerOptionsDuplicator serializerOptionsDuplicator, IOptions<MvcOptions> mvcOptions)
{
this.serializerSettingsDuplicator = serializerSettingsDuplicator;
this.serializerOptionsDuplicator = serializerOptionsDuplicator;

#if NETCOREAPP3_0
// NewtonsoftJsonFormatter will be present if consumer has called UseNewtonsoftJson in Startup.cs
defaultSerializationMode = mvcOptions.Value.OutputFormatters.Any(x => x.GetType() == typeof(NewtonsoftJsonOutputFormatter))
? SerializationMode.Newtonsoft
: SerializationMode.SystemTextJson;
#else
defaultSerializationMode = SerializationMode.Newtonsoft;
#endif
}

public IJsonFormatter GetFormatter(IContractResolver contractResolver = null, JsonConverter jsonConverter = null)
{
if (UseNewtonsoft(contractResolver, jsonConverter))
return new NewtonsoftJsonFormatter(serializerSettingsDuplicator.SerializerSettings(contractResolver, jsonConverter));

return new SystemTextJsonFormatter(serializerOptionsDuplicator.SerializerOptions());
}

private bool UseNewtonsoft(IContractResolver contractResolver = null, JsonConverter jsonConverter = null) =>
defaultSerializationMode == SerializationMode.Newtonsoft || contractResolver != null || jsonConverter != null;
}

public enum SerializationMode
{
Newtonsoft,
SystemTextJson
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Newtonsoft.Json;

namespace Swashbuckle.AspNetCore.Filters
{
internal class NewtonsoftJsonFormatter : IJsonFormatter
{
private readonly JsonSerializerSettings serializerSettings;

public NewtonsoftJsonFormatter(JsonSerializerSettings serializerSettings)
{
serializerSettings.Formatting = Formatting.Indented;
this.serializerSettings = serializerSettings;
}

public string FormatJson(object examples)
{
return JsonConvert.SerializeObject(examples, serializerSettings);
}
}
}
15 changes: 6 additions & 9 deletions src/Swashbuckle.AspNetCore.Filters/Examples/RequestExample.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,16 @@ namespace Swashbuckle.AspNetCore.Filters
{
internal class RequestExample
{
private readonly JsonFormatter jsonFormatter;
private readonly SerializerSettingsDuplicator serializerSettingsDuplicator;
private readonly JsonFormatterProvider jsonFormatterProvider;
private readonly MvcOutputFormatter mvcOutputFormatter;
private readonly SwaggerOptions swaggerOptions;

public RequestExample(
JsonFormatter jsonFormatter,
SerializerSettingsDuplicator serializerSettingsDuplicator,
JsonFormatterProvider jsonFormatterProvider,
MvcOutputFormatter mvcOutputFormatter,
IOptions<SwaggerOptions> options)
{
this.jsonFormatter = jsonFormatter;
this.serializerSettingsDuplicator = serializerSettingsDuplicator;
this.jsonFormatterProvider = jsonFormatterProvider;
this.mvcOutputFormatter = mvcOutputFormatter;
this.swaggerOptions = options?.Value;
}
Expand All @@ -49,9 +46,9 @@ public void SetRequestExampleForOperation(
return;
}

var serializerSettings = serializerSettingsDuplicator.SerializerSettings(contractResolver, jsonConverter);
var jsonFormatter = jsonFormatterProvider.GetFormatter(contractResolver, jsonConverter);

var examplesConverter = new ExamplesConverter(jsonFormatter, mvcOutputFormatter, serializerSettings);
var examplesConverter = new ExamplesConverter(jsonFormatter, mvcOutputFormatter);

IOpenApiAny firstOpenApiExample;
var multiple = example as IEnumerable<ISwaggerExample<object>>;
Expand Down Expand Up @@ -140,4 +137,4 @@ private IOpenApiAny SetMultipleRequestExamplesForOperation(
return operation.RequestBody.Content.FirstOrDefault().Value?.Examples?.FirstOrDefault().Value?.Value;
}
}
}
}
15 changes: 6 additions & 9 deletions src/Swashbuckle.AspNetCore.Filters/Examples/ResponseExample.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,14 @@ namespace Swashbuckle.AspNetCore.Filters
{
internal class ResponseExample
{
private readonly JsonFormatter jsonFormatter;
private readonly SerializerSettingsDuplicator serializerSettingsDuplicator;
private readonly JsonFormatterProvider jsonFormatterProvider;
private readonly MvcOutputFormatter mvcOutputFormatter;

public ResponseExample(
JsonFormatter jsonFormatter,
SerializerSettingsDuplicator serializerSettingsDuplicator,
JsonFormatterProvider jsonFormatterProvider,
MvcOutputFormatter mvcOutputFormatter)
{
this.jsonFormatter = jsonFormatter;
this.serializerSettingsDuplicator = serializerSettingsDuplicator;
this.jsonFormatterProvider = jsonFormatterProvider;
this.mvcOutputFormatter = mvcOutputFormatter;
}

Expand All @@ -42,9 +39,9 @@ public void SetResponseExampleForStatusCode(
return;
}

var serializerSettings = serializerSettingsDuplicator.SerializerSettings(contractResolver, jsonConverter);
var jsonFormatter = jsonFormatterProvider.GetFormatter(contractResolver, jsonConverter);

var examplesConverter = new ExamplesConverter(jsonFormatter, mvcOutputFormatter, serializerSettings);
var examplesConverter = new ExamplesConverter(jsonFormatter, mvcOutputFormatter);

var multiple = example as IEnumerable<ISwaggerExample<object>>;
if (multiple == null)
Expand All @@ -56,7 +53,7 @@ public void SetResponseExampleForStatusCode(
SetMultipleResponseExampleForStatusCode(response, multiple, examplesConverter);
}
}

private void SetSingleResponseExampleForStatusCode(
KeyValuePair<string, OpenApiResponse> response,
object example,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace Swashbuckle.AspNetCore.Filters
{
internal class SerializerOptionsDuplicator
{
private readonly JsonSerializerOptions jsonSerializerOptions;
private readonly SchemaGeneratorOptions schemaGeneratorOptions;


#if NETCOREAPP3_0
public SerializerOptionsDuplicator(IOptions<JsonOptions> jsonOptions, IOptions<SchemaGeneratorOptions> schemaGeneratorOptions)
{
this.jsonSerializerOptions = jsonOptions.Value.JsonSerializerOptions;
this.schemaGeneratorOptions = schemaGeneratorOptions.Value;
}
#else
public SerializerOptionsDuplicator(IOptions<SchemaGeneratorOptions> schemaGeneratorOptions)
{
this.jsonSerializerOptions = new JsonSerializerOptions();
this.schemaGeneratorOptions = schemaGeneratorOptions.Value;
}
#endif

public JsonSerializerOptions SerializerOptions()
{
var serializerSettings = DuplicateSerializerSettings(jsonSerializerOptions);

if (schemaGeneratorOptions.IgnoreObsoleteProperties)
{
// TODO THIS
}

return serializerSettings;
}

private static JsonSerializerOptions DuplicateSerializerSettings(JsonSerializerOptions controllerSerializerOptions)
{
if (controllerSerializerOptions == null)
{
return new JsonSerializerOptions();
}

var options = new JsonSerializerOptions
{
Encoder = controllerSerializerOptions.Encoder,
MaxDepth = controllerSerializerOptions.MaxDepth,
WriteIndented = controllerSerializerOptions.WriteIndented,
AllowTrailingCommas = controllerSerializerOptions.AllowTrailingCommas,
DefaultBufferSize = controllerSerializerOptions.DefaultBufferSize,
DictionaryKeyPolicy = controllerSerializerOptions.DictionaryKeyPolicy,
IgnoreNullValues = controllerSerializerOptions.IgnoreNullValues,
PropertyNamingPolicy = controllerSerializerOptions.PropertyNamingPolicy,
ReadCommentHandling = controllerSerializerOptions.ReadCommentHandling,
IgnoreReadOnlyProperties = controllerSerializerOptions.IgnoreReadOnlyProperties,
PropertyNameCaseInsensitive = controllerSerializerOptions.PropertyNameCaseInsensitive
};

foreach (var converter in controllerSerializerOptions.Converters)
{
options.Converters.Add(converter);
}

return options;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Text.Json;

namespace Swashbuckle.AspNetCore.Filters
{
internal class SystemTextJsonFormatter : IJsonFormatter
{
private readonly JsonSerializerOptions serializerOptions;

public SystemTextJsonFormatter(JsonSerializerOptions serializerOptions)
{
serializerOptions.WriteIndented = true;
this.serializerOptions = serializerOptions;
}

public string FormatJson(object examples)
{
return JsonSerializer.Serialize(examples, serializerOptions);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,13 @@ public static IServiceCollection AddSwaggerExamplesFromAssemblies(this IServiceC
private static void AddRequiredServices(IServiceCollection services)
{
services.AddSingleton<SerializerSettingsDuplicator>();
services.AddSingleton<JsonFormatter>();
services.AddSingleton<SerializerOptionsDuplicator>();
services.AddSingleton<JsonFormatterProvider>();
services.AddSingleton<RequestExample>();
services.AddSingleton<ResponseExample>();
services.AddSingleton<ExamplesOperationFilter>();
services.AddSingleton<ServiceProviderExamplesOperationFilter>();
services.AddSingleton<MvcOutputFormatter>();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ public class ExamplesOperationFilterTests : BaseOperationFilterTests

public ExamplesOperationFilterTests()
{
var mvcJsonOptions = Options.Create(new MvcJsonOptions());
schemaGeneratorOptions = new SchemaGeneratorOptions();
var mvcJsonOptions = Options.Create(new MvcJsonOptions());
var mvcOptions = Options.Create(new MvcOptions());
var serializerSettingsDuplicator = new SerializerSettingsDuplicator(mvcJsonOptions, Options.Create(schemaGeneratorOptions));
var serializerOptionsDuplicator = new SerializerOptionsDuplicator(Options.Create(schemaGeneratorOptions));

var jsonFormatter = new JsonFormatter();
var jsonFormatter = new JsonFormatterProvider(serializerSettingsDuplicator, serializerOptionsDuplicator, mvcOptions);
var mvcOutputFormatter = new MvcOutputFormatter(FormatterOptions.WithoutFormatters, new FakeLoggerFactory());

var serviceProvider = Substitute.For<IServiceProvider>();
Expand All @@ -39,8 +41,8 @@ public ExamplesOperationFilterTests()
serviceProvider.GetService(typeof(PersonRequestMultipleExamples)).Returns(new PersonRequestMultipleExamples());
serviceProvider.GetService(typeof(DictionaryRequestExample)).Returns(new DictionaryRequestExample());

var requestExample = new RequestExample(jsonFormatter, serializerSettingsDuplicator, mvcOutputFormatter, Options.Create(swaggerOptions));
var responseExample = new ResponseExample(jsonFormatter, serializerSettingsDuplicator, mvcOutputFormatter);
var requestExample = new RequestExample(jsonFormatter, mvcOutputFormatter, Options.Create(swaggerOptions));
var responseExample = new ResponseExample(jsonFormatter, mvcOutputFormatter);

sut = new ExamplesOperationFilter(serviceProvider, requestExample, responseExample);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,18 @@ public class ServiceProviderExamplesOperationFilterTests : BaseOperationFilterTe

public ServiceProviderExamplesOperationFilterTests()
{
var mvcJsonOptions = Options.Create(new MvcJsonOptions());
schemaGeneratorOptions = new SchemaGeneratorOptions();

var mvcJsonOptions = Options.Create(new MvcJsonOptions());
var mvcOptions = Options.Create(new MvcOptions());
var serializerSettingsDuplicator = new SerializerSettingsDuplicator(mvcJsonOptions, Options.Create(schemaGeneratorOptions));
var serializerOptionsDuplicator = new SerializerOptionsDuplicator(Options.Create(schemaGeneratorOptions));

var jsonFormatter = new JsonFormatter();
var jsonFormatter = new JsonFormatterProvider(serializerSettingsDuplicator, serializerOptionsDuplicator, mvcOptions);
var mvcOutputFormatter = new MvcOutputFormatter(FormatterOptions.WithoutFormatters, new FakeLoggerFactory());

var requestExample = new RequestExample(jsonFormatter, serializerSettingsDuplicator, mvcOutputFormatter, Options.Create(swaggerOptions));
var responseExample = new ResponseExample(jsonFormatter, serializerSettingsDuplicator, mvcOutputFormatter);
var requestExample = new RequestExample(jsonFormatter, mvcOutputFormatter, Options.Create(swaggerOptions));
var responseExample = new ResponseExample(jsonFormatter, mvcOutputFormatter);

serviceProvider = Substitute.For<IServiceProvider>();
serviceProvider.GetService(typeof(IExamplesProvider<PersonResponse>)).Returns(new PersonResponseAutoExample());
Expand Down
Loading