0

I am using NSwag Studio to generate a C# client for an external Swagger API that I need to interact with. One of the properties on an API call that I am attempting to execute is a typed array.

From the specification.json:

 "segmentValues": {
          "description": "The table >",
          "type": "array",
          "items": {
            "$ref": "#/definitions/DtoSegmentValue"
          }
        },

The generated code correctly recognizes this as a typed ICollection. From the Client.cs:

/// <summary>
/// The table &amp;gt;
/// </summary>
[Newtonsoft.Json.JsonProperty("segmentValues", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public System.Collections.Generic.ICollection<DtoSegmentValue> SegmentValues { get; set; }

However, in the generated code for this API call, NSwag attempts to convert this array to a <string,string> Dictionary and throws an Exception:

var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(update, _settings.Value);
var dictionary_ = Newtonsoft.Json.JsonConvert.DeserializeObject<System.Collections.Generic.Dictionary<string, string>>(json_, _settings.Value);       <-- this line throws the Exception
var content_ = new System.Net.Http.FormUrlEncodedContent(dictionary_);

The Exception:

Newtonsoft.Json.JsonReaderException: Unexpected character encountered while parsing value: [. Path 'segmentValues', line 1, position 18.
   at Newtonsoft.Json.JsonTextReader.ReadStringValue(ReadType readType)
   at Newtonsoft.Json.JsonTextReader.ReadAsString()
   at Newtonsoft.Json.JsonReader.ReadForType(JsonContract contract, Boolean hasConverter)

The Exception clearly states that it is the array ( '[' ) that cannot be deserialized. But I cannot find any settings in NSwag studio that effect this line of generated code. So how do I reconfigure it to correctly handle an array in a body? I am assuming this is possible because it seems like very standard API behaviour to me.

The current configuration in the nswag.json file:

{
  "runtime": "Net60",
  "defaultVariables": null,
  "documentGenerator": {
    "fromDocument": {
      "url": null,
      "output": null,
      "newLineBehavior": "Auto"
    }
  },
  "codeGenerators": {
    "openApiToCSharpClient": {
      "clientBaseClass": null,
      "configurationClass": null,
      "generateClientClasses": true,
      "generateClientInterfaces": true,
      "clientBaseInterface": null,
      "injectHttpClient": true,
      "disposeHttpClient": true,
      "protectedMethods": [],
      "generateExceptionClasses": true,
      "exceptionClass": "ApiException",
      "wrapDtoExceptions": true,
      "useHttpClientCreationMethod": false,
      "httpClientType": "System.Net.Http.HttpClient",
      "useHttpRequestMessageCreationMethod": false,
      "useBaseUrl": true,
      "generateBaseUrlProperty": true,
      "generateSyncMethods": false,
      "generatePrepareRequestAndProcessResponseAsAsyncMethods": false,
      "exposeJsonSerializerSettings": false,
      "clientClassAccessModifier": "public",
      "typeAccessModifier": "public",
      "generateContractsOutput": false,
      "contractsNamespace": null,
      "contractsOutputFilePath": null,
      "parameterDateTimeFormat": "s",
      "parameterDateFormat": "yyyy-MM-dd",
      "generateUpdateJsonSerializerSettingsMethod": true,
      "useRequestAndResponseSerializationSettings": false,
      "serializeTypeInformation": false,
      "queryNullValue": "",
      "className": "ApiClient",
      "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": true,
      "wrapResponseMethods": [],
      "generateResponseClasses": true,
      "responseClass": "Response",
      "namespace": "My.Api",
      "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": "Client.cs",
      "newLineBehavior": "Auto"
    }
  }
}

I generated the C# client using NSwag studio and various different configurations in the client generation settings. But nothing effects the line of code that requires deserialization to a Dictionary<string, string>. Changing the data contract on the API that I am generating code for is not an option, since it is an external system that I have no influence over. It should also not be necessary since this seems like a standard implementation to me.

XouDo
  • 945
  • 10
  • 19
  • Use an online json checker. Search on web for "json checker". You json is not valid. – jdweng May 09 '23 at 09:35
  • that would imply that the external party has made a mistake in their API. I did not generate this json. – R. Reinders May 09 '23 at 10:57
  • I have captured the json object and put it through a json checker. It is valid JSON: "{\"segmentValues\":[{\"operation\":\"Update\",\"value\":\"00\",\"description\":{\"value\":\"none\"},\"active\":{\"value\":true}}]}" – R. Reinders May 09 '23 at 11:06
  • The e3xception says "line 1, position 18". What character is at position 18? Is it the comma or the return at the end of the line? – jdweng May 09 '23 at 11:48
  • it was the [ (the array opener), but as you can see by my answer below, that was not the issue. There is a bug in NSwag when handling "application/x-www-form-urlencoded". – R. Reinders May 09 '23 at 12:00

1 Answers1

0

This issue has been resolved (in a work-aroundy kind of way). The exact issue has already been reported on the NSwag Github page but is still open: https://github.com/RicoSuter/NSwag/issues/3414#issuecomment-826479920

The issue is that when NSWAG is generating code using a specification file generated by a Swashbuckle Swagger API (instead of an NSwag Swagger API) it gives the "application/x-www-form-urlencoded" property precedence over "application/json". Apparently there is a bug in NSwag that "application/x-www-form-urlencoded" generated code cannot handle arrays.

As a workaround I have removed all "application/x-www-form-urlencoded" lines from the specification file before generating code. This now generates correctly working API calls.