Skip to content

Commit

Permalink
Use sorted properties when saving configuration in NSwagStudio
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma committed Nov 8, 2023
1 parent 512eb65 commit 0430295
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 70 deletions.
64 changes: 45 additions & 19 deletions src/NSwag.Commands/NSwagDocumentBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using NJsonSchema.Infrastructure;
using NSwag.Commands.CodeGeneration;
using NSwag.Commands.Generation;

Expand All @@ -31,6 +30,7 @@ public abstract class NSwagDocumentBase : INotifyPropertyChanged
{
private string _path;
private string _latestData;
private bool _latestDataSorted;
private IOutputCommand _selectedSwaggerGenerator;

/// <summary>Initializes a new instance of the <see cref="NSwagDocumentBase"/> class.</summary>
Expand Down Expand Up @@ -78,7 +78,7 @@ internal JObject SelectedSwaggerGeneratorRaw
key[0].ToString().ToLowerInvariant() + key.Substring(1),
SelectedSwaggerGenerator
}
}, JsonSerializer.Create(GetSerializerSettings()));
}, JsonSerializer.Create(GetSerializerSettings(sortProperties: true)));
}
set
{
Expand Down Expand Up @@ -132,7 +132,7 @@ public string Name

/// <summary>Gets a value indicating whether the document is dirty (has any changes).</summary>
[JsonIgnore]
public bool IsDirty => _latestData != JsonConvert.SerializeObject(this, Formatting.Indented, GetSerializerSettings());
public bool IsDirty => _latestData != JsonConvert.SerializeObject(this, Formatting.Indented, GetSerializerSettings(_latestDataSorted));

/// <summary>Gets the selected Swagger generator.</summary>
[JsonIgnore]
Expand Down Expand Up @@ -233,22 +233,24 @@ public static TDocument FromJson<TDocument>(string filePath, string data)
}

/// <summary>Saves the document.</summary>
/// <returns>The task.</returns>
public Task SaveAsync()
/// <param name="sortProperties">Whether to sort the JSON members alphabetically.</param>
public Task SaveAsync(bool sortProperties = false)
{
File.WriteAllText(Path, ToJsonWithRelativePaths());
_latestData = JsonConvert.SerializeObject(this, Formatting.Indented, GetSerializerSettings());
File.WriteAllText(Path, ToJsonWithRelativePaths(sortProperties));
_latestData = JsonConvert.SerializeObject(this, Formatting.Indented, GetSerializerSettings(sortProperties));
_latestDataSorted = sortProperties;
return Task.CompletedTask;
}

/// <summary>Converts the document to JSON with relative paths.</summary>
/// <param name="sortProperties">Whether to sort the JSON members alphabetically.</param>
/// <returns>The JSON data.</returns>
public string ToJsonWithRelativePaths()
public string ToJsonWithRelativePaths(bool sortProperties = false)
{
ConvertToRelativePaths();
try
{
return ToJson();
return ToJson(sortProperties);
}
finally
{
Expand All @@ -257,10 +259,11 @@ public string ToJsonWithRelativePaths()
}

/// <summary>Converts the document to JSON.</summary>
/// <param name="sortProperties">Whether to sort the JSON members alphabetically.</param>
/// <returns>The JSON data.</returns>
public string ToJson()
public string ToJson(bool sortProperties = false)
{
return JsonConvert.SerializeObject(this, Formatting.Indented, GetSerializerSettings());
return JsonConvert.SerializeObject(this, Formatting.Indented, GetSerializerSettings(sortProperties));
}

/// <summary>Generates the <see cref="OpenApiDocument"/> with the currently selected generator.</summary>
Expand Down Expand Up @@ -296,20 +299,49 @@ private static Dictionary<string, string> ConvertVariables(string variables)
}
}

private static JsonSerializerSettings GetSerializerSettings()
private static JsonSerializerSettings GetSerializerSettings(bool sortProperties = false)
{
return new JsonSerializerSettings
{
DefaultValueHandling = DefaultValueHandling.Include,
NullValueHandling = NullValueHandling.Include,
ContractResolver = new CamelCasePropertyNamesContractResolver(),
ContractResolver = sortProperties ? new OrderedCamelCasePropertyNamesContractResolver() : new CamelCasePropertyNamesContractResolver(),
Converters = new List<JsonConverter>
{
new StringEnumConverter()
}
};
}

private sealed class OrderedCamelCasePropertyNamesContractResolver : CamelCasePropertyNamesContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
// sort simple properties first, then container objects, then sort by name
var properties = base.CreateProperties(type, memberSerialization)
.OrderBy(x =>
{
// document generator before code generators
if (x.PropertyName == "documentGenerator")
{
return 5;
}

if (x.PropertyType?.Namespace?.StartsWith("NSwag") == true && x.PropertyType?.IsEnum != true || x.PropertyType == typeof(JObject))
{
// later than others
return 10;
}

return 1;
})
.ThenBy(x => x.PropertyName, StringComparer.Ordinal)
.ToList();

return properties;
}
}

private void ConvertToAbsolutePaths()
{
if (SwaggerGenerators.FromDocumentCommand != null)
Expand Down Expand Up @@ -515,12 +547,6 @@ private static string TransformLegacyDocument(string data, out bool saveFile)
saveFile = true;
}

if (data.Contains("\"noBuild\":") && !data.ToLowerInvariant().Contains("UseDocumentProvider".ToLowerInvariant()))
{
data = data.Replace("\"noBuild\":", "\"useDocumentProvider\": false, \"noBuild\":");
saveFile = true;
}

if (data.Contains("\"noBuild\":") && !data.ToLowerInvariant().Contains("RequireParametersWithoutDefault".ToLowerInvariant()))
{
data = data.Replace("\"noBuild\":", "\"requireParametersWithoutDefault\": true, \"noBuild\":");
Expand Down
60 changes: 12 additions & 48 deletions src/NSwag.Sample.NET80/nswag.json
Original file line number Diff line number Diff line change
@@ -1,58 +1,22 @@
{
"runtime": "Net80",
{
"defaultVariables": null,
"runtime": "Net80",
"documentGenerator": {
"aspNetCoreToOpenApi": {
"project": "NSwag.Sample.NET80.csproj",
"msBuildProjectExtensionsPath": null,
"aspNetCoreEnvironment": null,
"configuration": null,
"documentName": "v1",
"msBuildOutputPath": null,
"msBuildProjectExtensionsPath": "",
"newLineBehavior": "Auto",
"noBuild": false,
"output": "openapi.json",
"project": "NSwag.Sample.NET80.csproj",
"runtime": null,
"targetFramework": null,
"noBuild": false,
"verbose": true,
"workingDirectory": null,
"requireParametersWithoutDefault": false,
"apiGroupNames": null,
"defaultPropertyNameHandling": "Default",
"defaultReferenceTypeNullHandling": "Null",
"defaultDictionaryValueReferenceTypeNullHandling": "NotNull",
"defaultResponseReferenceTypeNullHandling": "NotNull",
"defaultEnumHandling": "Integer",
"flattenInheritanceHierarchy": false,
"generateKnownTypes": true,
"generateEnumMappingDescription": false,
"generateXmlObjects": false,
"generateAbstractProperties": false,
"generateAbstractSchemas": true,
"ignoreObsoleteProperties": false,
"allowReferencesWithProperties": false,
"excludedTypeNames": [],
"serviceHost": null,
"serviceBasePath": null,
"serviceSchemes": [],
"infoTitle": "My Title",
"infoDescription": null,
"infoVersion": "1.0.0",
"documentTemplate": null,
"documentProcessorTypes": [],
"operationProcessorTypes": [],
"typeNameGeneratorType": null,
"schemaNameGeneratorType": null,
"contractResolverType": null,
"serializerSettingsType": null,
"useDocumentProvider": true,
"documentName": "v1",
"aspNetCoreEnvironment": null,
"createWebHostBuilderMethod": null,
"startupType": null,
"allowNullableBodyParameters": true,
"output": "openapi.json",
"outputType": "Swagger2",
"assemblyPaths": [],
"assemblyConfig": null,
"referencePaths": [],
"useNuGetCache": false
"workingDirectory": null
}
},
"codeGenerators": {}
}
}
6 changes: 3 additions & 3 deletions src/NSwagStudio/ViewModels/MainWindowModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ private async Task<bool> SaveDocumentAsync(DocumentModel document)
if (File.Exists(document.Document.Path))
{
FocusManager.SetFocusedElement(Application.Current.MainWindow, null);
await document.Document.SaveAsync();
await document.Document.SaveAsync(sortProperties: true);
MessageBox.Show($"The file {document.Document.Name} has been saved.", "File saved");
return true;
}
Expand All @@ -225,7 +225,7 @@ private async Task<bool> SaveAllDocumentAsync(ObservableCollection<DocumentModel
changeCount++;
if (File.Exists(document.Document.Path))
{
await document.Document.SaveAsync();
await document.Document.SaveAsync(sortProperties: true);
}
else
{
Expand Down Expand Up @@ -253,7 +253,7 @@ private async Task<bool> SaveAsDocumentAsync(DocumentModel document)
{
document.Document.Path = dlg.FileName;
FocusManager.SetFocusedElement(Application.Current.MainWindow, null);
await document.Document.SaveAsync();
await document.Document.SaveAsync(sortProperties: true);
return true;
}
return false;
Expand Down

0 comments on commit 0430295

Please sign in to comment.