Search code examples
c#json.net.net-coreazure-cosmosdb

Unable to find a constructor to use for type NuGet.Versioning.VersionRange


I have created a DtoClass and referenced VersionRange class from Nuget.Versioning :

DtoClass:

  public class DependencyDto
{
    public string Name { get; set; }
    
    [JsonProperty("VersionRange")]
    [JsonConverter(typeof(VersionRangeConverter))]
    public VersionRange VersionRange { get; set; }
}

I have implemented simple JsonConverter in order to use Object's value in string representation format on swagger UI.

enter image description here

JsonConverter :

public class VersionRangeConverter : JsonConverter<VersionRange>
{       
    public override void WriteJson(JsonWriter writer, VersionRange value, JsonSerializer serializer)
    {
        writer.WriteValue(value.ToString());
    }
   
    public override VersionRange ReadJson(JsonReader reader, Type objectType, VersionRange existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        Requires.NotNull(serializer, nameof(JsonSerializer));

        VersionRange versionRange = null;

        var versionRangeObject = serializer.Deserialize(reader);

        if (versionRangeObject is null)
        {
            return null;
        }
        else if (versionRangeObject is string)
        {
            if (!VersionRange.TryParse(versionRangeObject.ToString(), out versionRange))
            {
                throw new Exception();
            }
        }
        else
        {
            try
            {
                dynamic ver = JsonConvert.DeserializeObject(versionRangeObject.ToString());
                NuGetVersion maxVersion = versionRange.MaxVersion;
                NuGetVersion minVersion = versionRange.MinVersion;
                bool isMinInclusive = versionRange.IsMinInclusive;
                bool isMaxInclusive = versionRange.IsMaxInclusive;
                string originalString = versionRange.OriginalString;

                versionRange = new VersionRange(minVersion, isMinInclusive, maxVersion, isMaxInclusive, null, originalString);
            }
            catch
            {
                throw;
            }
        }

        return versionRange;
    }
}

I have created one more Dependency class but without JsonConverter attribute over VersionRange property as I want to save whole Object to Cosmos DB. I have used automapper to map these two classes. This works fine and it gets saved in db as expected. see below. Document in Cosmos DB :

"Dependencies": [
        {
            "Name": "string",
            "VersionRange": {
                "IsFloating": false,
                "Float": null,
                "OriginalString": "[1.0.0, 2.0.0)",
                "HasLowerBound": true,
                "HasUpperBound": true,
                "HasLowerAndUpperBounds": true,
                "IsMinInclusive": true,
                "IsMaxInclusive": false,
                "MaxVersion": "2.0.0",
                "MinVersion": "1.0.0"
            }
        }
    ]

Now when i am trying to read response from cosmos db by casting it to a class which does not have JsonConverter attribute over a VersionRange property. I am getting error. Code to write to cosmos and cast repose :

ResourceResponse<Document> response = await this.DocumentDbClient.WriteAsync(doc.Id, this.CollectionName, doc).ConfigureAwait(false);
DependencyDoc dependencyDoc= (DependencyDoc)(dynamic)response.Resource;

Exception:

  "Message": "Unable to find a constructor to use for type NuGet.Versioning.VersionRange. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'Module.Dependencies[0].VersionRange.IsFloating', line 1, position 285.",

Solution

  • You can accomplish this by using the VersionRangeFactory

    public class VersionRangeConverter : JsonConverter<VersionRange>
    {       
        public override void WriteJson(JsonWriter writer, VersionRange value, JsonSerializer serializer)
        {
            writer.WriteValue(value.ToString());
        }
       
        public override VersionRange ReadJson(JsonReader reader, Type objectType, VersionRange existingValue, bool hasExistingValue, JsonSerializer serializer)
        {
            // Deserialize as Dictionary to be able to access the values by key
            var dict = serializer.Deserialize<Dictionary<string, object>>(reader);
    
            // Utilize the `VersionRangeFactory`
    
            // Add your validations here if key exists, value is string etc.
            // This is just for demo purpose
            // ... or use one of the other constructors from the factory to build the full object from the dictionary which is now in your hand.
            var versionRange = VersionRange.Parse(dict["OriginalString"] as string, dict["IsFloating"] == "true");
    
            return versionRange;
        }
    }
    

    Here is a small working example

    Update

    If you have VersionRange from response body as string you can also add a handling to your converter to support full deserialized object and string object.

    public override VersionRange ReadJson(JsonReader reader, Type objectType, VersionRange existingValue, bool hasExistingValue, JsonSerializer serializer)
        {
            VersionRange versionRange = null;
            
            try
            {
                var dict = serializer.Deserialize<Dictionary<string, object>>(reader);
                if(dict != null)
                {
                    versionRange = VersionRange.Parse(dict["OriginalString"] as string, dict["IsFloating"] == "true");
                }
            }
            catch
            {
                // Handling if response body contains only the string version declaration
                var strValue = serializer.Deserialize<string>(reader);
                if(!string.IsNullOrWhiteSpace(strValue))
                {
                    versionRange = VersionRange.Parse(strValue);
                }
            }
            
            return versionRange;
        }
    

    Next working example