Skip to content

Commit

Permalink
Improve property name escaping (#1638)
Browse files Browse the repository at this point in the history
* don't allow "init", "fromJS" or "toJSON" for TypeScript member name
* improve performance by not doing string.Replace unless necessary
  • Loading branch information
lahma authored Oct 30, 2023
1 parent 855dab7 commit 28611ab
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,21 @@
namespace NJsonSchema.CodeGeneration.CSharp
{
/// <summary>Generates the property name for a given CSharp <see cref="JsonSchemaProperty"/>.</summary>
public class CSharpPropertyNameGenerator : IPropertyNameGenerator
public sealed class CSharpPropertyNameGenerator : IPropertyNameGenerator
{
private static readonly char[] _reservedFirstPassChars = { '"', '\'', '@', '?', '!', '$', '[', ']', '(', ')', '.', '=', '+' };
private static readonly char[] _reservedSecondPassChars = { '*', ':', '-', '#', '&' };

/// <summary>Generates the property name.</summary>
/// <param name="property">The property.</param>
/// <returns>The new name.</returns>
public virtual string Generate(JsonSchemaProperty property)
public string Generate(JsonSchemaProperty property)
{
return ConversionUtilities.ConvertToUpperCamelCase(property.Name
.Replace("\"", string.Empty)
var name = property.Name;

if (name.IndexOfAny(_reservedFirstPassChars) != -1)
{
name = name.Replace("\"", string.Empty)
.Replace("'", string.Empty)
.Replace("@", string.Empty)
.Replace("?", string.Empty)
Expand All @@ -29,12 +35,22 @@ public virtual string Generate(JsonSchemaProperty property)
.Replace(")", string.Empty)
.Replace(".", "-")
.Replace("=", "-")
.Replace("+", "plus"), true)
.Replace("*", "Star")
.Replace(":", "_")
.Replace("-", "_")
.Replace("#", "_")
.Replace("&", "And");
.Replace("+", "plus");
}

name = ConversionUtilities.ConvertToUpperCamelCase(name, true);

if (name.IndexOfAny(_reservedSecondPassChars) != -1)
{
name = name
.Replace("*", "Star")
.Replace(":", "_")
.Replace("-", "_")
.Replace("#", "_")
.Replace("&", "And");
}

return name;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Threading.Tasks;
using NJsonSchema.Annotations;
using NJsonSchema.NewtonsoftJson.Generation;
using VerifyXunit;
using Xunit;

using static NJsonSchema.CodeGeneration.TypeScript.Tests.VerifyHelper;

namespace NJsonSchema.CodeGeneration.TypeScript.Tests;

[UsesVerify]
public class PropertyNameTests
{
private class TypeWithRestrictedProperties
{
public string Constructor { get; set; }
public string Init { get; set; }
public string FromJS { get; set; }
public string ToJSON { get; set; }
}

[Fact]
public async Task When_class_has_restricted_properties_they_are_escaped()
{
var schema = NewtonsoftJsonSchemaGenerator.FromType<TypeWithRestrictedProperties>();

var generator = new TypeScriptGenerator(schema, new TypeScriptGeneratorSettings { TypeScriptVersion = 4.3m });
var output = generator.GenerateFile(nameof(TypeWithRestrictedProperties));

await Verify(output);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//----------------------
// <auto-generated>
// </auto-generated>
//----------------------







export class TypeWithRestrictedProperties implements ITypeWithRestrictedProperties {
constructor_!: string | undefined;
init_!: string | undefined;
fromJS_!: string | undefined;
toJSON_!: string | undefined;

constructor(data?: ITypeWithRestrictedProperties) {
if (data) {
for (var property in data) {
if (data.hasOwnProperty(property))
(<any>this)[property] = (<any>data)[property];
}
}
}

init(_data?: any) {
if (_data) {
this.constructor_ = _data["Constructor"];
this.init_ = _data["Init"];
this.fromJS_ = _data["FromJS"];
this.toJSON_ = _data["ToJSON"];
}
}

static fromJS(data: any): TypeWithRestrictedProperties {
data = typeof data === 'object' ? data : {};
let result = new TypeWithRestrictedProperties();
result.init(data);
return result;
}

toJSON(data?: any) {
data = typeof data === 'object' ? data : {};
data["Constructor"] = this.constructor_;
data["Init"] = this.init_;
data["FromJS"] = this.fromJS_;
data["ToJSON"] = this.toJSON_;
return data;
}
}

export interface ITypeWithRestrictedProperties {
constructor_: string | undefined;
init_: string | undefined;
fromJS_: string | undefined;
toJSON_: string | undefined;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,43 @@
// <author>Rico Suter, mail@rsuter.com</author>
//-----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;

namespace NJsonSchema.CodeGeneration.TypeScript
{
/// <summary>Generates the property name for a given TypeScript <see cref="JsonSchemaProperty"/>.</summary>
public class TypeScriptPropertyNameGenerator : IPropertyNameGenerator
public sealed class TypeScriptPropertyNameGenerator : IPropertyNameGenerator
{
private static readonly char[] _reservedFirstPassChars = { '"', '@', '?', '.', '=', '+' };
private static readonly char[] _reservedSecondPassChars = { '*', ':', '-' };

/// <summary>Gets or sets the reserved names.</summary>
public IEnumerable<string> ReservedPropertyNames { get; set; } = new List<string> { "constructor" };
public HashSet<string> ReservedPropertyNames { get; set; } = new(StringComparer.Ordinal) { "constructor", "init", "fromJS", "toJSON" };

/// <summary>Generates the property name.</summary>
/// <param name="property">The property.</param>
/// <returns>The new name.</returns>
public virtual string Generate(JsonSchemaProperty property)
/// <inheritdoc />
public string Generate(JsonSchemaProperty property)
{
var name = ConversionUtilities.ConvertToLowerCamelCase(property.Name
.Replace("\"", string.Empty)
var name = property.Name;

if (name.IndexOfAny(_reservedFirstPassChars) != -1)
{
name = name.Replace("\"", string.Empty)
.Replace("@", string.Empty)
.Replace("?", string.Empty)
.Replace(".", "-")
.Replace("=", "-")
.Replace("+", "plus"), true)
.Replace("*", "Star")
.Replace(":", "_")
.Replace("-", "_");
.Replace("+", "plus");
}

name = ConversionUtilities.ConvertToLowerCamelCase(name, true);

if (name.IndexOfAny(_reservedSecondPassChars) != -1)
{
name = name.Replace("*", "Star")
.Replace(":", "_")
.Replace("-", "_");
}

if (ReservedPropertyNames.Contains(name))
{
Expand Down
12 changes: 5 additions & 7 deletions src/NJsonSchema/Generation/SampleJsonDataGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@
//-----------------------------------------------------------------------

using Newtonsoft.Json.Linq;
using NJsonSchema;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

namespace NJsonSchema.Generation
Expand Down Expand Up @@ -133,19 +131,19 @@ private JToken Generate(JsonSchema schema, Stack<JsonSchema> schemaStack)
}
}

private JToken HandleNumberType(JsonSchema schema)
private static JToken HandleNumberType(JsonSchema schema)
{
if (schema.ExclusiveMinimumRaw?.Equals(true) == true && schema.Minimum != null)
{
return JToken.FromObject(decimal.Parse(schema.Minimum.Value.ToString(CultureInfo.InvariantCulture)) + 0.1m);
return JToken.FromObject(schema.Minimum.Value + 0.1m);
}
else if (schema.ExclusiveMinimum != null)
{
return JToken.FromObject(decimal.Parse(schema.ExclusiveMinimum.Value.ToString(CultureInfo.InvariantCulture)));
return JToken.FromObject(schema.ExclusiveMinimum.Value);
}
else if (schema.Minimum.HasValue)
{
return decimal.Parse(schema.Minimum.ToString()!);
return schema.Minimum.Value;
}
return JToken.FromObject(0.0);
}
Expand All @@ -167,7 +165,7 @@ private JToken HandleIntegerType(JsonSchema schema)
return JToken.FromObject(0);
}

private JToken HandleStringType(JsonSchema schema, JsonSchemaProperty? property)
private static JToken HandleStringType(JsonSchema schema, JsonSchemaProperty? property)
{
if (schema.Format == JsonFormatStrings.Date)
{
Expand Down

0 comments on commit 28611ab

Please sign in to comment.