NJsonSchema icon indicating copy to clipboard operation
NJsonSchema copied to clipboard

NewtonSoft.Json.Required.DisallowNull set on non-required field

Open bstordrup opened this issue 1 year ago • 6 comments

I have a schema that contains a MasterData like this:

            "MasterDataDto": {
                "required": ["companyId", "deleted", "masterDataTypeName", "message", "publicId", "synchronizedVersion", "version"],
                "type": "object",
                "properties": {
                    "masterDataTypeName": {
                        "type": "string"
                    },
                    "publicId": {
                        "type": "string"
                    },
                    "companyId": {
                        "type": "integer",
                        "format": "int64"
                    },
                    "version": {
                        "type": "integer",
                        "format": "int64"
                    },
                    "synchronizedVersion": {
                        "type": "integer",
                        "format": "int64"
                    },
                    "correlationId": {
                        "type": "string"
                    },
                    "deleted": {
                        "type": "boolean"
                    },
                    "message": {
                        "type": "string"
                    },
                    "createdDate": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "updatedDate": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "lastOperationId": {
                        "type": "string"
                    }
                }
            },

Only the properties mentioned in the "required" property are required.

But in the Dto class generated, all properties are generated with the NewtonSoft.Json.Required.DisallowNull value for the JsonProperty.Required property in the JsonProperty attribute:

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.20.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v13.0.0.0))")]
    public partial class MasterDataDto
    {
        [Newtonsoft.Json.JsonProperty("masterDataTypeName", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
        public string MasterDataTypeName { get; set; }

        [Newtonsoft.Json.JsonProperty("publicId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
        public string PublicId { get; set; }

        [Newtonsoft.Json.JsonProperty("companyId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public long CompanyId { get; set; }

        [Newtonsoft.Json.JsonProperty("version", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public long Version { get; set; }

        [Newtonsoft.Json.JsonProperty("synchronizedVersion", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public long SynchronizedVersion { get; set; }

        [Newtonsoft.Json.JsonProperty("correlationId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public string CorrelationId { get; set; }

        [Newtonsoft.Json.JsonProperty("deleted", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public bool Deleted { get; set; }

        [Newtonsoft.Json.JsonProperty("message", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
        public string Message { get; set; }

        [Newtonsoft.Json.JsonProperty("createdDate", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public System.DateTimeOffset? CreatedDate { get; set; }

        [Newtonsoft.Json.JsonProperty("updatedDate", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public System.DateTimeOffset? UpdatedDate { get; set; }

        [Newtonsoft.Json.JsonProperty("lastOperationId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public string LastOperationId { get; set; }

        private System.Collections.Generic.IDictionary<string, object> _additionalProperties;

        [Newtonsoft.Json.JsonExtensionData]
        public System.Collections.Generic.IDictionary<string, object> AdditionalProperties
        {
            get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary<string, object>()); }
            set { _additionalProperties = value; }
        }

    }

What do I need to set to get the generator to respect the "required" property list? My code generators settings are:

  "codeGenerators": {
    "openApiToCSharpClient": {
      "clientBaseClass": null,
      "configurationClass": null,
      "generateClientClasses": true,
      "generateClientInterfaces": true,
      "clientBaseInterface": null,
      "injectHttpClient": true,
      "disposeHttpClient": true,
      "protectedMethods": [],
      "generateExceptionClasses": true,
      "exceptionClass": "MdmRestApiException",
      "wrapDtoExceptions": true,
      "useHttpClientCreationMethod": false,
      "httpClientType": "System.Net.Http.HttpClient",
      "useHttpRequestMessageCreationMethod": false,
      "useBaseUrl": true,
      "generateBaseUrlProperty": true,
      "generateSyncMethods": false,
      "generatePrepareRequestAndProcessResponseAsAsyncMethods": false,
      "exposeJsonSerializerSettings": true,
      "clientClassAccessModifier": "public",
      "typeAccessModifier": "public",
      "generateContractsOutput": true,
      "contractsNamespace": "Visma.Business.MdmRestClient",
      "contractsOutputFilePath": "IMdmRestApi.cs",
      "parameterDateTimeFormat": "s",
      "parameterDateFormat": "yyyy-MM-dd",
      "generateUpdateJsonSerializerSettingsMethod": true,
      "useRequestAndResponseSerializationSettings": false,
      "serializeTypeInformation": false,
      "queryNullValue": "",
      "className": "{controller}MdmRestApiClient",
      "operationGenerationMode": "SingleClientFromOperationId",
      "additionalNamespaceUsages": [],
      "additionalContractNamespaceUsages": [],
      "generateOptionalParameters": false,
      "generateJsonMethods": false,
      "enforceFlagEnums": false,
      "parameterArrayType": "System.Collections.Generic.IEnumerable",
      "parameterDictionaryType": "System.Collections.Generic.IDictionary",
      "responseArrayType": "System.Collections.Generic.ICollection",
      "responseDictionaryType": "System.Collections.Generic.IDictionary",
      "wrapResponses": false,
      "wrapResponseMethods": [],
      "generateResponseClasses": true,
      "responseClass": "SwaggerResponse",
      "namespace": "Visma.Business.MdmRestClient",
      "requiredPropertiesMustBeDefined": false,
      "dateType": "System.DateTimeOffset",
      "jsonConverters": null,
      "anyType": "object",
      "dateTimeType": "System.DateTimeOffset",
      "timeType": "System.TimeSpan",
      "timeSpanType": "System.TimeSpan",
      "arrayType": "System.Collections.Generic.ICollection",
      "arrayInstanceType": "System.Collections.ObjectModel.Collection",
      "dictionaryType": "System.Collections.Generic.IDictionary",
      "dictionaryInstanceType": "System.Collections.Generic.Dictionary",
      "arrayBaseType": "System.Collections.ObjectModel.Collection",
      "dictionaryBaseType": "System.Collections.Generic.Dictionary",
      "classStyle": "Poco",
      "jsonLibrary": "NewtonsoftJson",
      "generateDefaultValues": true,
      "generateDataAnnotations": true,
      "excludedTypeNames": [],
      "excludedParameterNames": [],
      "handleReferences": false,
      "generateImmutableArrayProperties": false,
      "generateImmutableDictionaryProperties": false,
      "jsonSerializerSettingsTransformationMethod": null,
      "inlineNamedArrays": false,
      "inlineNamedDictionaries": false,
      "inlineNamedTuples": true,
      "inlineNamedAny": false,
      "generateDtoTypes": true,
      "generateOptionalPropertiesAsNullable": true,
      "generateNullableReferenceTypes": false,
      "templateDirectory": null,
      "typeNameGeneratorType": null,
      "propertyNameGeneratorType": null,
      "enumNameGeneratorType": null,
      "serviceHost": null,
      "serviceSchemes": null,
      "output": "MdmRestApi.cs",
      "newLineBehavior": "Auto"
    }
  }

It seems like the generator does not handle a string based property as a property that can contain null.

bstordrup avatar Oct 02 '23 10:10 bstordrup

For generating a nullable contract you need to set "generateNullableReferenceTypes": true, if I am not wrong

trejjam avatar Oct 05 '23 10:10 trejjam

Doing so will mark the property as string? (and also other properties as int?).

But even without this, the string type can be null and is in cases also containing null in the response from the API in question. And that fails because the generated property is decorated with Required = NewtonSoft.Json.Required.DisallowNull

Might be a corner case that a non-required string field in the response should not be marked with DisallowNull, but with Default instead.

bstordrup avatar Oct 05 '23 10:10 bstordrup

The generated code is correct as none of your properties allow null, you'd need to set the type to ["string", "null"] to make it nullable

RicoSuter avatar Oct 30 '23 15:10 RicoSuter

So, the definition for corelationId should be

                    "correlationId": {
                        "type": ["string", "null"]
                    },

in the Json behind the Swagger definition?

But that means that if I do not have control over the API, I'm not able to generate a correct class definition. Because the string can have the value "null" even though the API Json definition does not have the "null" part of the definition. As it it not referenced in the "required" list at the top of the definition.

bstordrup avatar Oct 30 '23 16:10 bstordrup

Since you do not have necessary information in source document you can't do much in NSwag.

You can set Nullability or you can specify Required properties. One generates nullable types directly, the other works with generateNullableReferenceTypes.

When the source document is not correct you can write your preprocessor which by some rules fix the source document.

trejjam avatar Oct 30 '23 22:10 trejjam

Ah this is not JSON Schema.. in OpenAPI you can set “nullable” to true on the property

RicoSuter avatar Oct 30 '23 23:10 RicoSuter