diff --git a/src/Json.Schema.ToDotNet.UnitTests/DataModelGeneratorTests.cs b/src/Json.Schema.ToDotNet.UnitTests/DataModelGeneratorTests.cs index 9a702e0b..b7c4104c 100644 --- a/src/Json.Schema.ToDotNet.UnitTests/DataModelGeneratorTests.cs +++ b/src/Json.Schema.ToDotNet.UnitTests/DataModelGeneratorTests.cs @@ -871,10 +871,43 @@ public void GeneratesCloningCode() ""type"": ""integer"", ""description"": ""An integer property."" }, + ""intPropWithDefault"": { + ""type"": ""integer"", + ""description"": ""An integer property with a default value."", + ""default"": 42 + }, + ""numberProp"": { + ""type"": ""number"", + ""description"": ""A number property."" + }, + ""numberPropWithDefault"": { + ""type"": ""number"", + ""description"": ""A number property with a default value."", + ""default"": 42.1 + }, ""stringProp"": { ""type"": ""string"", ""description"": ""A string property."" }, + ""stringPropWithDefault"": { + ""type"": ""string"", + ""description"": ""A string property with a default value."", + ""default"": ""42"" + }, + ""boolProp"": { + ""type"": ""boolean"", + ""description"": ""A Boolean property."" + }, + ""boolPropWithTrueDefault"": { + ""type"": ""boolean"", + ""description"": ""A Boolean property with a true default value."", + ""default"": true + }, + ""boolPropWithFalseDefault"": { + ""type"": ""boolean"", + ""description"": ""A Boolean property with a false default value."", + ""default"": false + }, ""arrayProp"": { ""type"": ""array"", ""description"": ""An array property."", @@ -1006,6 +1039,7 @@ public void GeneratesCloningCode() @"using System; using System.CodeDom.Compiler; using System.Collections.Generic; +using System.ComponentModel; using System.Runtime.Serialization; namespace N @@ -1031,12 +1065,59 @@ public SNodeKind SNodeKind [DataMember(Name = ""intProp"", IsRequired = false, EmitDefaultValue = false)] public int IntProp { get; set; } + /// + /// An integer property with a default value. + /// + [DataMember(Name = ""intPropWithDefault"", IsRequired = false, EmitDefaultValue = false)] + [DefaultValue(42)] + public int IntPropWithDefault { get; set; } + + /// + /// A number property. + /// + [DataMember(Name = ""numberProp"", IsRequired = false, EmitDefaultValue = false)] + public double NumberProp { get; set; } + + /// + /// A number property with a default value. + /// + [DataMember(Name = ""numberPropWithDefault"", IsRequired = false, EmitDefaultValue = false)] + [DefaultValue(42.1)] + public double NumberPropWithDefault { get; set; } + /// /// A string property. /// [DataMember(Name = ""stringProp"", IsRequired = false, EmitDefaultValue = false)] public string StringProp { get; set; } + /// + /// A string property with a default value. + /// + [DataMember(Name = ""stringPropWithDefault"", IsRequired = false, EmitDefaultValue = false)] + [DefaultValue(""42"")] + public string StringPropWithDefault { get; set; } + + /// + /// A Boolean property. + /// + [DataMember(Name = ""boolProp"", IsRequired = false, EmitDefaultValue = false)] + public bool BoolProp { get; set; } + + /// + /// A Boolean property with a true default value. + /// + [DataMember(Name = ""boolPropWithTrueDefault"", IsRequired = false, EmitDefaultValue = false)] + [DefaultValue(true)] + public bool BoolPropWithTrueDefault { get; set; } + + /// + /// A Boolean property with a false default value. + /// + [DataMember(Name = ""boolPropWithFalseDefault"", IsRequired = false, EmitDefaultValue = false)] + [DefaultValue(false)] + public bool BoolPropWithFalseDefault { get; set; } + /// /// An array property. /// @@ -1110,56 +1191,82 @@ public SNodeKind SNodeKind /// public C() { + IntPropWithDefault = 42; + NumberPropWithDefault = 42.1; + StringPropWithDefault = ""42""; + BoolPropWithTrueDefault = true; + BoolPropWithFalseDefault = false; } /// /// Initializes a new instance of the class from the supplied values. /// /// - /// An initialization value for the property. + /// An initialization value for the property. + /// + /// + /// An initialization value for the property. + /// + /// + /// An initialization value for the property. + /// + /// + /// An initialization value for the property. /// /// - /// An initialization value for the property. + /// An initialization value for the property. + /// + /// + /// An initialization value for the property. + /// + /// + /// An initialization value for the property. + /// + /// + /// An initialization value for the property. + /// + /// + /// An initialization value for the property. /// /// - /// An initialization value for the property. + /// An initialization value for the property. /// /// - /// An initialization value for the property. + /// An initialization value for the property. /// /// - /// An initialization value for the property. + /// An initialization value for the property. /// /// - /// An initialization value for the property. + /// An initialization value for the property. /// /// - /// An initialization value for the property. + /// An initialization value for the property. /// /// - /// An initialization value for the property. + /// An initialization value for the property. /// /// - /// An initialization value for the property. + /// An initialization value for the property. /// /// - /// An initialization value for the property. + /// An initialization value for the property. /// /// - /// An initialization value for the property. + /// An initialization value for the property. /// /// - /// An initialization value for the property. + /// An initialization value for the property. /// /// - /// An initialization value for the property. + /// An initialization value for the property. /// /// - /// An initialization value for the property. + /// An initialization value for the property. /// - public C(int intProp, string stringProp, IEnumerable arrayProp, Uri uriProp, DateTime dateTimeProp, D referencedTypeProp, IEnumerable arrayOfRefProp, IEnumerable> arrayOfArrayProp, IDictionary dictionaryProp, IDictionary dictionaryWithPrimitiveSchemaProp, IDictionary dictionaryWithObjectSchemaProp, IDictionary> dictionaryWithObjectArraySchemaProp, IDictionary dictionaryWithUriKeyProp, IDictionary dictionaryWithHintedValueProp) + public C(int intProp, int intPropWithDefault, double numberProp, double numberPropWithDefault, string stringProp, string stringPropWithDefault, bool boolProp, bool boolPropWithTrueDefault, bool boolPropWithFalseDefault, IEnumerable arrayProp, Uri uriProp, DateTime dateTimeProp, D referencedTypeProp, IEnumerable arrayOfRefProp, IEnumerable> arrayOfArrayProp, IDictionary dictionaryProp, IDictionary dictionaryWithPrimitiveSchemaProp, IDictionary dictionaryWithObjectSchemaProp, IDictionary> dictionaryWithObjectArraySchemaProp, IDictionary dictionaryWithUriKeyProp, IDictionary dictionaryWithHintedValueProp) { - Init(intProp, stringProp, arrayProp, uriProp, dateTimeProp, referencedTypeProp, arrayOfRefProp, arrayOfArrayProp, dictionaryProp, dictionaryWithPrimitiveSchemaProp, dictionaryWithObjectSchemaProp, dictionaryWithObjectArraySchemaProp, dictionaryWithUriKeyProp, dictionaryWithHintedValueProp); + Init(intProp, intPropWithDefault, numberProp, numberPropWithDefault, stringProp, stringPropWithDefault, boolProp, boolPropWithTrueDefault, boolPropWithFalseDefault, arrayProp, uriProp, dateTimeProp, referencedTypeProp, arrayOfRefProp, arrayOfArrayProp, dictionaryProp, dictionaryWithPrimitiveSchemaProp, dictionaryWithObjectSchemaProp, dictionaryWithObjectArraySchemaProp, dictionaryWithUriKeyProp, dictionaryWithHintedValueProp); } /// @@ -1178,7 +1285,7 @@ public C(C other) throw new ArgumentNullException(nameof(other)); } - Init(other.IntProp, other.StringProp, other.ArrayProp, other.UriProp, other.DateTimeProp, other.ReferencedTypeProp, other.ArrayOfRefProp, other.ArrayOfArrayProp, other.DictionaryProp, other.DictionaryWithPrimitiveSchemaProp, other.DictionaryWithObjectSchemaProp, other.DictionaryWithObjectArraySchemaProp, other.DictionaryWithUriKeyProp, other.DictionaryWithHintedValueProp); + Init(other.IntProp, other.IntPropWithDefault, other.NumberProp, other.NumberPropWithDefault, other.StringProp, other.StringPropWithDefault, other.BoolProp, other.BoolPropWithTrueDefault, other.BoolPropWithFalseDefault, other.ArrayProp, other.UriProp, other.DateTimeProp, other.ReferencedTypeProp, other.ArrayOfRefProp, other.ArrayOfArrayProp, other.DictionaryProp, other.DictionaryWithPrimitiveSchemaProp, other.DictionaryWithObjectSchemaProp, other.DictionaryWithObjectArraySchemaProp, other.DictionaryWithUriKeyProp, other.DictionaryWithHintedValueProp); } ISNode ISNode.DeepClone() @@ -1199,10 +1306,17 @@ private ISNode DeepCloneCore() return new C(this); } - private void Init(int intProp, string stringProp, IEnumerable arrayProp, Uri uriProp, DateTime dateTimeProp, D referencedTypeProp, IEnumerable arrayOfRefProp, IEnumerable> arrayOfArrayProp, IDictionary dictionaryProp, IDictionary dictionaryWithPrimitiveSchemaProp, IDictionary dictionaryWithObjectSchemaProp, IDictionary> dictionaryWithObjectArraySchemaProp, IDictionary dictionaryWithUriKeyProp, IDictionary dictionaryWithHintedValueProp) + private void Init(int intProp, int intPropWithDefault, double numberProp, double numberPropWithDefault, string stringProp, string stringPropWithDefault, bool boolProp, bool boolPropWithTrueDefault, bool boolPropWithFalseDefault, IEnumerable arrayProp, Uri uriProp, DateTime dateTimeProp, D referencedTypeProp, IEnumerable arrayOfRefProp, IEnumerable> arrayOfArrayProp, IDictionary dictionaryProp, IDictionary dictionaryWithPrimitiveSchemaProp, IDictionary dictionaryWithObjectSchemaProp, IDictionary> dictionaryWithObjectArraySchemaProp, IDictionary dictionaryWithUriKeyProp, IDictionary dictionaryWithHintedValueProp) { IntProp = intProp; + IntPropWithDefault = intPropWithDefault; + NumberProp = numberProp; + NumberPropWithDefault = numberPropWithDefault; StringProp = stringProp; + StringPropWithDefault = stringPropWithDefault; + BoolProp = boolProp; + BoolPropWithTrueDefault = boolPropWithTrueDefault; + BoolPropWithFalseDefault = boolPropWithFalseDefault; if (arrayProp != null) { var destination_0 = new List(); @@ -2450,7 +2564,7 @@ public C() /// Initializes a new instance of the class from the supplied values. /// /// - /// An initialization value for the property. + /// An initialization value for the property. /// public C(IEnumerable uriFormattedStrings) { diff --git a/src/Json.Schema.ToDotNet/ClassGenerator.cs b/src/Json.Schema.ToDotNet/ClassGenerator.cs index 8d209405..e67590f3 100644 --- a/src/Json.Schema.ToDotNet/ClassGenerator.cs +++ b/src/Json.Schema.ToDotNet/ClassGenerator.cs @@ -27,6 +27,9 @@ public class ClassGenerator : ClassOrInterfaceGenerator // Name used for the parameters of the copy ctor. private const string OtherParameterName = "other"; + private const string DefaultValueAttributeNamespaceName = "System.ComponentModel"; + private const string DefaultValueAttributeName = "DefaultValue"; + private const string DataContractAttributeName = "DataContract"; private const string DataMemberAttributeName = "DataMember"; private const string DataMemberNamePropertyName = "Name"; @@ -241,7 +244,7 @@ private MemberDeclarationSyntax GenerateValueGetHashCodeMethod() .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); } - protected override AttributeSyntax[] GeneratePropertyAttributes(string propertyName, string serializedName, bool isRequired) + protected override AttributeSyntax[] GeneratePropertyAttributes(string propertyName, string serializedName, bool isRequired, object defaultValue) { var attributes = new List(); @@ -279,6 +282,24 @@ protected override AttributeSyntax[] GeneratePropertyAttributes(string propertyN attributes.Add(dataMemberAttribute); + if (defaultValue != null) + { + AddUsing(DefaultValueAttributeNamespaceName); + + var defaultValueArguments = new List + { + SyntaxFactory.AttributeArgument(GetLiteralExpressionForValue(defaultValue)) + }; + + AttributeSyntax defaultValueAttribute = + SyntaxFactory.Attribute( + SyntaxFactory.IdentifierName(DefaultValueAttributeName), + SyntaxFactory.AttributeArgumentList( + SyntaxFactory.SeparatedList(defaultValueArguments))); + + attributes.Add(defaultValueAttribute); + } + string hintDictionaryKey = MakeHintDictionaryKey(propertyName); AttributeHint[] attributeHints = HintDictionary?.GetHints(hintDictionaryKey); if (attributeHints != null) @@ -372,7 +393,7 @@ private ConstructorDeclarationSyntax GenerateDefaultConstructor() { return SyntaxFactory.ConstructorDeclaration(SuffixedTypeName) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)) - .AddBodyStatements() + .AddBodyStatements(GenerateDefaultInitializations()) .WithLeadingTrivia( SyntaxHelper.MakeDocComment( string.Format( @@ -381,6 +402,95 @@ private ConstructorDeclarationSyntax GenerateDefaultConstructor() SuffixedTypeName))); } + /// + /// Generates an initialization statement for each property for which the + /// schema specifies a default value. + /// + /// + /// The resulting statements are inserted into the default constructor. + /// This ensures that the default values are set even if the object is + /// not the result of deserializing a JSON instance document. + /// + /// + /// An array containing one initialization statement for each property + /// for which the schema specifies a default value. + /// + private ExpressionStatementSyntax[] GenerateDefaultInitializations() + { + var initializations = new List(); + + foreach (string propertyName in PropInfoDictionary.GetPropertyNames()) + { + if (IncludeProperty(propertyName)) + { + PropertyInfo propInfo = PropInfoDictionary[propertyName]; + object defaultValue = propInfo.DefaultValue; + + ExpressionStatementSyntax initializationStatement = GenerateDefaultInitialization(propertyName, defaultValue); + if (initializationStatement != null) + { + initializations.Add(initializationStatement); + } + } + } + + return initializations.ToArray(); + } + + private ExpressionStatementSyntax GenerateDefaultInitialization( + string propertyName, + object defaultValue) + { + LiteralExpressionSyntax defaultValueExpression = GetLiteralExpressionForValue(defaultValue); + if (defaultValueExpression == null) { return null; } + + return SyntaxFactory.ExpressionStatement( + SyntaxFactory.AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + SyntaxFactory.IdentifierName(propertyName), + defaultValueExpression)); + } + + private LiteralExpressionSyntax GetLiteralExpressionForValue(object value) + { + LiteralExpressionSyntax literalExpression = null; + + if (value is bool) + { + SyntaxKind literalSyntaxKind = (bool)value == true + ? SyntaxKind.TrueLiteralExpression + : SyntaxKind.FalseLiteralExpression; + + literalExpression = SyntaxFactory.LiteralExpression(literalSyntaxKind); + } + else if (value is long) + { + literalExpression = SyntaxFactory.LiteralExpression( + SyntaxKind.NumericLiteralExpression, + SyntaxFactory.Literal((int)(long)value)); + // Note: the extra cast compensates for a mismatch between our code generation + // and Newtonsoft.Json's deserialization behavior. Newtonsoft deserializes + // integer properties as Int64 (long), but we generate integer properties + // with type int. The extra cast causes Roslyn to emit the literal 42, + // which can be assigned to an int, rather than 42L, which cannot. We should + // consider changing the code generation to emit longs for integer properties. + } + else if (value is double) + { + literalExpression = SyntaxFactory.LiteralExpression( + SyntaxKind.NumericLiteralExpression, + SyntaxFactory.Literal((double)value)); + } + else if (value is string) + { + literalExpression = SyntaxFactory.LiteralExpression( + SyntaxKind.StringLiteralExpression, + SyntaxFactory.Literal((string)value)); + } + + return literalExpression; + } + private ConstructorDeclarationSyntax GeneratePropertyCtor() { // Generate the argument list that will be passed from the copy ctor to the diff --git a/src/Json.Schema.ToDotNet/ClassOrInterfaceGenerator.cs b/src/Json.Schema.ToDotNet/ClassOrInterfaceGenerator.cs index 43733be4..5312eee7 100644 --- a/src/Json.Schema.ToDotNet/ClassOrInterfaceGenerator.cs +++ b/src/Json.Schema.ToDotNet/ClassOrInterfaceGenerator.cs @@ -25,7 +25,7 @@ public ClassOrInterfaceGenerator( PropInfoDictionary = propertyInfoDictionary; } - protected abstract AttributeSyntax[] GeneratePropertyAttributes(string propertyName, string serializedName, bool isRequired); + protected abstract AttributeSyntax[] GeneratePropertyAttributes(string propertyName, string serializedName, bool isRequired, object defaultValue); protected abstract SyntaxToken[] GeneratePropertyModifiers(string propertyName); @@ -103,7 +103,7 @@ private PropertyDeclarationSyntax CreatePropertyDeclaration(string propertyName) .AddModifiers(GeneratePropertyModifiers(propertyName)) .AddAccessorListAccessors(GeneratePropertyAccessors()); - AttributeSyntax[] attributes = GeneratePropertyAttributes(propertyName, info.SerializedName, info.IsRequired); + AttributeSyntax[] attributes = GeneratePropertyAttributes(propertyName, info.SerializedName, info.IsRequired, info.DefaultValue); if (attributes.Length > 0) { propDecl = propDecl.AddAttributeLists(attributes diff --git a/src/Json.Schema.ToDotNet/EqualityComparerGenerator.cs b/src/Json.Schema.ToDotNet/EqualityComparerGenerator.cs index 08253b43..842cb174 100644 --- a/src/Json.Schema.ToDotNet/EqualityComparerGenerator.cs +++ b/src/Json.Schema.ToDotNet/EqualityComparerGenerator.cs @@ -111,16 +111,20 @@ internal string Generate(string className, PropertyInfoDictionary propertyInfoDi GenerateEqualsMethod(), GenerateGetHashCodeMethod()); - var usings = new List + var usings = new HashSet { "System", // For Object. "System.Collections.Generic" // For IEqualityComparer }; - usings.AddRange(_propertyInfoDictionary + IEnumerable namespaceNames = _propertyInfoDictionary .Values .Select(propertyInfo => propertyInfo.NamespaceName) - .Where(namespaceName => !string.IsNullOrWhiteSpace(namespaceName))); + .Where(namespaceName => !string.IsNullOrWhiteSpace(namespaceName)); + foreach (string namespaceName in namespaceNames) + { + usings.Add(namespaceName); + } return classDeclaration.Format( _copyrightNotice, diff --git a/src/Json.Schema.ToDotNet/InterfaceGenerator.cs b/src/Json.Schema.ToDotNet/InterfaceGenerator.cs index 589ffbf4..7a3e5814 100644 --- a/src/Json.Schema.ToDotNet/InterfaceGenerator.cs +++ b/src/Json.Schema.ToDotNet/InterfaceGenerator.cs @@ -41,7 +41,7 @@ public override void AddMembers() .AddMembers(GenerateProperties()); } - protected override AttributeSyntax[] GeneratePropertyAttributes(string propertyName, string serializedName, bool isRequired) + protected override AttributeSyntax[] GeneratePropertyAttributes(string propertyName, string serializedName, bool isRequired, object defaultValue) { return new AttributeSyntax[0]; } diff --git a/src/Json.Schema.ToDotNet/Json.Schema.ToDotNet.csproj b/src/Json.Schema.ToDotNet/Json.Schema.ToDotNet.csproj index 0ff38bee..8d6dc6b8 100644 --- a/src/Json.Schema.ToDotNet/Json.Schema.ToDotNet.csproj +++ b/src/Json.Schema.ToDotNet/Json.Schema.ToDotNet.csproj @@ -16,6 +16,21 @@ + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + diff --git a/src/Json.Schema.ToDotNet/PropertyInfo.cs b/src/Json.Schema.ToDotNet/PropertyInfo.cs index 1ab0e617..e67ccb07 100644 --- a/src/Json.Schema.ToDotNet/PropertyInfo.cs +++ b/src/Json.Schema.ToDotNet/PropertyInfo.cs @@ -42,6 +42,9 @@ public class PropertyInfo /// true if this property is required by the schema; /// otherwise false. /// + /// + /// The default value, if any, specified by the schema; otherwise null. + /// /// /// true if this property is of a type defined by the schema (or an; /// array of a schema-defined type otherwise false. @@ -61,6 +64,7 @@ public PropertyInfo( TypeSyntax type, string namespaceName, bool isRequired, + object defaultValue, bool isOfSchemaDefinedType, int arrayRank, int declarationOrder) @@ -74,6 +78,7 @@ public PropertyInfo( TypeName = type.ToString(); NamespaceName = namespaceName; IsRequired = isRequired; + DefaultValue = defaultValue; IsOfSchemaDefinedType = isOfSchemaDefinedType; ArrayRank = arrayRank; DeclarationOrder = declarationOrder; @@ -131,6 +136,11 @@ public PropertyInfo( /// public bool IsRequired { get; } + /// + /// Gets this property's default value, if the schema specifies one; otherwise null. + /// + public object DefaultValue; + /// /// Gets a value indicating whether this property is of a type defined by the schema. /// diff --git a/src/Json.Schema.ToDotNet/PropertyInfoDictionary.cs b/src/Json.Schema.ToDotNet/PropertyInfoDictionary.cs index aa98e83f..cf74dcf5 100644 --- a/src/Json.Schema.ToDotNet/PropertyInfoDictionary.cs +++ b/src/Json.Schema.ToDotNet/PropertyInfoDictionary.cs @@ -10,7 +10,6 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.Json.Schema.ToDotNet.Hints; -using Newtonsoft.Json.Linq; namespace Microsoft.Json.Schema.ToDotNet { @@ -57,8 +56,8 @@ public class PropertyInfoDictionary : IReadOnlyDictionary /// public delegate void AdditionalTypeRequiredDelegate(AdditionalTypeRequiredInfo additionalTypeRequiredInfo); - private AdditionalTypeRequiredDelegate _additionalTypeRequiredDelegate; - private string _typeNameSuffix; + private readonly AdditionalTypeRequiredDelegate _additionalTypeRequiredDelegate; + private readonly string _typeNameSuffix; /// /// Initializes a new instance of the class. @@ -143,8 +142,7 @@ public static string MakeDictionaryItemKeyName(string propertyName) public static SyntaxKind GetTypeKeywordFromSchemaType(SchemaType type) { - SyntaxKind typeKeyword; - if (!s_SchemaTypeToSyntaxKindDictionary.TryGetValue(type, out typeKeyword)) + if (!s_SchemaTypeToSyntaxKindDictionary.TryGetValue(type, out SyntaxKind typeKeyword)) { typeKeyword = SyntaxKind.ObjectKeyword; } @@ -158,8 +156,7 @@ public PropertyInfo this[string key] { get { - PropertyInfo info; - if (!TryGetValue(key, out info)) + if (!TryGetValue(key, out PropertyInfo info)) { throw new ApplicationException($"The schema does not contain information describing the property or element {key}."); } @@ -229,8 +226,6 @@ private void AddPropertyInfoFromPropertySchema( string referencedEnumTypeName; bool isOfSchemaDefinedType = false; int arrayRank = 0; - EnumHint enumHint; - DictionaryHint dictionaryHint; if (propertySchema.IsDateTime()) { @@ -246,7 +241,7 @@ private void AddPropertyInfoFromPropertySchema( initializationKind = InitializationKind.Uri; type = MakeNamedType("System.Uri", out namespaceName); } - else if (propertySchema.ShouldBeDictionary(_typeName, schemaPropertyName, _hintDictionary, out dictionaryHint)) + else if (propertySchema.ShouldBeDictionary(_typeName, schemaPropertyName, _hintDictionary, out DictionaryHint dictionaryHint)) { comparisonKind = ComparisonKind.Dictionary; hashKind = HashKind.Dictionary; @@ -271,7 +266,7 @@ private void AddPropertyInfoFromPropertySchema( initializationKind = InitializationKind.SimpleAssign; type = MakeNamedType(referencedEnumTypeName, out namespaceName); } - else if (propertySchema.ShouldBeEnum(_typeName, schemaPropertyName, _hintDictionary, out enumHint)) + else if (propertySchema.ShouldBeEnum(_typeName, schemaPropertyName, _hintDictionary, out EnumHint enumHint)) { comparisonKind = ComparisonKind.OperatorEquals; hashKind = HashKind.ScalarValueType; @@ -284,7 +279,7 @@ private void AddPropertyInfoFromPropertySchema( OnAdditionalTypeRequired(enumHint, propertySchema); } else - { + { SchemaType propertyType = propertySchema.SafeGetType(); switch (propertyType) @@ -384,6 +379,7 @@ private void AddPropertyInfoFromPropertySchema( type, namespaceName, isRequired, + propertySchema.Default, isOfSchemaDefinedType, arrayRank, entries.Count))); @@ -477,6 +473,7 @@ private TypeSyntax MakeDictionaryType( type: valueType, namespaceName: dictionaryHint.NamespaceName, isRequired: true, + defaultValue: null, isOfSchemaDefinedType: false, arrayRank: 0, declarationOrder: 0))); diff --git a/src/Json.Schema.ToDotNet/Resources.Designer.cs b/src/Json.Schema.ToDotNet/Resources.Designer.cs index f1f0643c..01bc443b 100644 --- a/src/Json.Schema.ToDotNet/Resources.Designer.cs +++ b/src/Json.Schema.ToDotNet/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace Microsoft.Json.Schema.ToDotNet { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { @@ -214,7 +214,7 @@ internal static string KindEnumNoneDescription { } /// - /// Looks up a localized string similar to An initialization value for the <see cref="P: {0}" /> property.. + /// Looks up a localized string similar to An initialization value for the <see cref="P:{0}" /> property.. /// internal static string PropertyCtorParamDescription { get { diff --git a/src/Json.Schema.ToDotNet/Resources.resx b/src/Json.Schema.ToDotNet/Resources.resx index 44f5acbe..fffa6fac 100644 --- a/src/Json.Schema.ToDotNet/Resources.resx +++ b/src/Json.Schema.ToDotNet/Resources.resx @@ -169,7 +169,7 @@ An uninitialized kind. - An initialization value for the <see cref="P: {0}" /> property. + An initialization value for the <see cref="P:{0}" /> property. Initializes a new instance of the <see cref="{0}" /> class from the supplied values. diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index 99b73d2e..3eb94e56 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -79,7 +79,7 @@ internal string GenerateRewritingVisitor() .AddMembers( GenerateVisitClassMethods()); - var usings = new List { "System", "System.Collections.Generic", "System.Linq" }; + var usings = new HashSet { "System", "System.Collections.Generic", "System.Linq" }; string summaryComment = string.Format( CultureInfo.CurrentCulture, @@ -356,9 +356,7 @@ private StatementSyntax[] GeneratePropertyVisits(string className) continue; } - int arrayRank = 0; - bool isDictionary = false; - string propertyName = propertyNameWithRank.BasePropertyName(out arrayRank, out isDictionary); + string propertyName = propertyNameWithRank.BasePropertyName(out int arrayRank, out bool isDictionary); TypeSyntax collectionType = propertyInfoDictionary.GetConcreteListType(propertyName); TypeSyntax elementType = propertyInfoDictionary[propertyNameWithRank].Type; diff --git a/src/Json.Schema.ToDotNet/SyntaxNodeExtensions.cs b/src/Json.Schema.ToDotNet/SyntaxNodeExtensions.cs index 2176810d..f46481d6 100644 --- a/src/Json.Schema.ToDotNet/SyntaxNodeExtensions.cs +++ b/src/Json.Schema.ToDotNet/SyntaxNodeExtensions.cs @@ -54,7 +54,7 @@ internal static class SyntaxNodeExtensions internal static string Format( this BaseTypeDeclarationSyntax typeDecl, string copyrightNotice, - List usings, + HashSet usings, string namespaceName, string summaryComment) { @@ -74,15 +74,11 @@ internal static string Format( CompilationUnitSyntax compilationUnit = SyntaxFactory.CompilationUnit() .AddMembers(namespaceDecl); - if (usings == null) - { - usings = new List(); - } + usings = usings ?? new HashSet(); usings.Add("System.CodeDom.Compiler"); // For GeneratedCodeAttribute UsingDirectiveSyntax[] usingDirectives = usings - .Distinct() .OrderBy(u => u, UsingComparer.Instance) .Select(u => SyntaxFactory.UsingDirective(MakeQualifiedName(u))) .ToArray(); diff --git a/src/Json.Schema.ToDotNet/TypeGenerator.cs b/src/Json.Schema.ToDotNet/TypeGenerator.cs index af93ece6..4522aff8 100644 --- a/src/Json.Schema.ToDotNet/TypeGenerator.cs +++ b/src/Json.Schema.ToDotNet/TypeGenerator.cs @@ -9,7 +9,7 @@ namespace Microsoft.Json.Schema.ToDotNet { public abstract class TypeGenerator { - private string _typeNameSuffix; + private readonly string _typeNameSuffix; protected TypeGenerator( JsonSchema schema, @@ -37,7 +37,7 @@ public string SuffixedTypeName /// protected BaseTypeDeclarationSyntax TypeDeclaration { get; set; } - protected List Usings { get; private set; } + protected HashSet Usings { get; private set; } public abstract BaseTypeDeclarationSyntax GenerateTypeDeclaration(); @@ -73,10 +73,7 @@ public string Generate(string namespaceName, string typeName, string copyrightNo protected void AddUsing(string namespaceName) { - if (Usings == null) - { - Usings = new List(); - } + Usings = Usings ?? new HashSet(); Usings.Add(namespaceName); } diff --git a/src/Json.Schema.UnitTests/JsonSchemaTests.cs b/src/Json.Schema.UnitTests/JsonSchemaTests.cs index 8e389934..72cb33de 100644 --- a/src/Json.Schema.UnitTests/JsonSchemaTests.cs +++ b/src/Json.Schema.UnitTests/JsonSchemaTests.cs @@ -73,7 +73,8 @@ public class JsonSchemaTests ""uniqueItems"": true, ""format"": ""date-time"", ""maximimum"": 2, - ""exclusiveMaximum"": false + ""exclusiveMaximum"": false, + ""default"": 2, }", @"{ ""id"": ""http://x/y#"", @@ -128,7 +129,8 @@ public class JsonSchemaTests ""uniqueItems"": true, ""format"": ""date-time"", ""maximimum"": 2, - ""exclusiveMaximum"": false + ""exclusiveMaximum"": false, + ""default"": 2 }", true ), @@ -773,6 +775,84 @@ public class JsonSchemaTests }", false ), + + new EqualityTestCase( + "Same integer defaults", + @"{ + ""default"": 2 + }", + @"{ + ""default"": 2 + }", + true), + + new EqualityTestCase( + "Different integer defaults", + @"{ + ""default"": 2 + }", + @"{ + ""default"": 3 + }", + false), + + new EqualityTestCase( + "Same string defaults", + @"{ + ""default"": ""2"" + }", + @"{ + ""default"": ""2"" + }", + true), + + new EqualityTestCase( + "Different string defaults", + @"{ + ""default"": ""2"" + }", + @"{ + ""default"": ""3"" + }", + false), + + new EqualityTestCase( + "Same Boolean defaults", + @"{ + ""default"": true + }", + @"{ + ""default"": true + }", + true), + + new EqualityTestCase( + "Different Boolean defaults", + @"{ + ""default"": false + }", + @"{ + ""default"": true + }", + false), + + new EqualityTestCase( + "Different default types", + @"{ + ""default"": 2 + }", + @"{ + ""default"": ""2"" + }", + false), + + new EqualityTestCase( + "Present and missing defaults", + @"{ + ""default"": 2 + }", + @"{}", + false) }; [Theory(DisplayName = "JsonSchema equality")] diff --git a/src/Json.Schema/JsonSchema.cs b/src/Json.Schema/JsonSchema.cs index 392d9e55..1a47aa5b 100644 --- a/src/Json.Schema/JsonSchema.cs +++ b/src/Json.Schema/JsonSchema.cs @@ -102,6 +102,7 @@ public JsonSchema(JsonSchema other) PatternProperties = new Dictionary(other.PatternProperties); } + Default = other.Default; Pattern = other.Pattern; MaxLength = other.MaxLength; MinLength = other.MinLength; @@ -266,6 +267,12 @@ public JsonSchema(JsonSchema other) /// public int? MinLength { get; set; } + /// + /// Gets or sets the value the property should be taken to have if it is absent + /// from an instance document. + /// + public object Default { get; set; } + /// /// Gets or sets a regular expression which a string schema instance must match. /// @@ -520,8 +527,7 @@ private static JsonSchema Collapse(JsonSchema schema, JsonSchema rootSchema) string definitionName = schema.Reference.GetDefinitionName(); - JsonSchema referencedSchema; - if (rootSchema.Definitions == null || !rootSchema.Definitions.TryGetValue(definitionName, out referencedSchema)) + if (rootSchema.Definitions == null || !rootSchema.Definitions.TryGetValue(definitionName, out JsonSchema referencedSchema)) { throw Error.CreateException( Resources.ErrorDefinitionDoesNotExist, @@ -547,6 +553,7 @@ private static JsonSchema Collapse(JsonSchema schema, JsonSchema rootSchema) collapsedSchema.Items.SingleSchema = referencedSchema.Items.SingleSchema; } + collapsedSchema.Default = referencedSchema.Default; collapsedSchema.Pattern = referencedSchema.Pattern; collapsedSchema.MaxLength = referencedSchema.MaxLength; collapsedSchema.MinLength = referencedSchema.MinLength; @@ -583,6 +590,7 @@ public override int GetHashCode() Required, Definitions, Reference, + Default, Pattern, MaxLength, MinLength, @@ -610,7 +618,7 @@ public override int GetHashCode() public bool Equals(JsonSchema other) { - if ((object)other == null) + if (other is null) { return false; } @@ -646,6 +654,7 @@ public bool Equals(JsonSchema other) && (Reference == null ? other.Reference == null : Reference.Equals(other.Reference)) + && Object.Equals(Default, other.Default) && Pattern == other.Pattern && MaxLength == other.MaxLength && MinLength == other.MinLength @@ -679,7 +688,7 @@ public bool Equals(JsonSchema other) return true; } - if ((object)left == null) + if (left is null) { return false; }