0

I'm a beginner, just writing code for an application for my work and I was trying to deserialize a class, but encountered an error. I have a class named Nuget, that looks like this:

public class Nuget
{
    public string? Name { get; set; }
    public string? Id { get; set; }
    public NuGetVersion Version { get; set; }
    public string? PackageSource { get; set; }
    public System.DateTimeOffset BlobCreation { get; set; }
}

Now I'm using .Net 6, with System.Text.Json in the method that tries to deserialize a file. When I try to do it I receive the following error message:

Encountered exception:

System.NotSupportedException: Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'NuGet.Versioning.NuGetVersion'. Path: $.Version | LineNumber: 0 | BytePositionInLine: 65..

Why would NuGetVersion need any constuctor?

The deserializer method is just your usual code.

try
{
    Nuget? deserializedObject = JsonSerializer.Deserialize<Nuget>(DirectoryManager.ReadResourceFromExternal(filePath));
    if (deserializedObject is null) throw new ArgumentNullException("deserializedObject");

    return deserializedObject;
}
catch (Exception ex)
{
    LogManager.CatchLog(ex, new string[] {
    $"Loading the requested file of {filePath } is not possible\n",
    "Please check the filepath"
});
}

The ReadResourceFromExternal method returns the decrypted file in a string format.

public static string ReadResourceFromExternal(string filePath) {
    using Stream stream = new MemoryStream(buffer: DAPIManager.Decrypt(filePath));
    using StreamReader reader = new(stream);
    return reader.ReadToEnd();
}

So far, I'm just trying to understand why NuGet.Version is not acceptable, since it has a {get;set;}, isn't this a parameterless constructor?

I was pondering if I should just have a string instead and convert the NuGet.Version manually.

Or am I misunderstanding the issue here?

dbc
  • 104,963
  • 20
  • 228
  • 340
  • What the ouput you get from DirectoryManager.ReadResourceFromExternal(filePath)? you have to post it. – Serge Feb 11 '23 at 19:09
  • Added it, thanks for mentioning that. – Vincze Szilárd Feb 11 '23 at 19:30
  • Thanks, but we need a text of the file – Serge Feb 11 '23 at 19:36
  • Well, the System.Text.Jso deserialization code somehow has to create the NugetVersion object, but has no idea how to do this without help of a JsonConstructorAttribute. The easiest way out would indeed be to declare it as `string` in your class and call `NugetVersion.Parse` later yourself. – Klaus Gütter Feb 12 '23 at 05:37

1 Answers1

1

Your problem is not with the Nuget.Version property, it is with the NuGetVersion type itself. NuGet.Versioning.NuGetVersion is an immutable type that has multiple parameterized constructors but no parameterless constructor, so System.Text.Json cannot figure out how to construct it when deserializing the value of Nuget.Version.

So, what are your options?

Firstly, Microsoft's own NuGet.Protocol package has a complete set of Newtonsoft.Json converters for serializing and deserializing NuGet types so you could just install that package and use Newtonsoft:

var nuget = Newtonsoft.Json.JsonConvert.DeserializeObject<Nuget>(json, 
    NuGet.Protocol.JsonExtensions.ObjectSerializationSettings);

Or even more simply:

var nuget = NuGet.Protocol.JsonExtensions.FromJson<Nuget>(json);

Secondly, if you would prefer to stick with System.Text.Json, you will need to port any required converters and settings used in NuGet.Protocol.JsonExtensions from Newtonsoft.

Create the following converters:

public class NuGetVersionConverter : System.Text.Json.Serialization.JsonConverter<NuGetVersion>
{
    public override NuGetVersion? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => 
        NuGetVersion.Parse(reader.GetString());
    public override void Write(Utf8JsonWriter writer, NuGetVersion value, JsonSerializerOptions options) => 
        writer.WriteStringValue(value.ToString());
}

public class VersionRangeConverter : System.Text.Json.Serialization.JsonConverter<VersionRange>
{
    public override VersionRange? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => 
        VersionRange.Parse(reader.GetString());
    public override void Write(Utf8JsonWriter writer, VersionRange value, JsonSerializerOptions options) => 
        // ToString()->Parse()->ToString() adapted from https://github.com/NuGet/NuGet.Client/blob/f24bad0668193ce21a1db8cabd1ce95ba509c7f0/src/NuGet.Core/NuGet.Protocol/Converters/VersionRangeConverter.cs#L41
        // Though in all honesty I'm not sure why the round-trip is necessary.
        writer.WriteStringValue((VersionRange.Parse(value.ToString())).ToString());
}

// TODO: 
// VersionInfoConverter, https://github.com/NuGet/NuGet.Client/blob/3c5cea96aa281d0703d7334d16be14fe8a7e0918/src/NuGet.Core/NuGet.Protocol/Converters/VersionInfoConverter.cs
// FingerprintsConverter, https://github.com/NuGet/NuGet.Client/blob/f24bad0668193ce21a1db8cabd1ce95ba509c7f0/src/NuGet.Core/NuGet.Protocol/Converters/FingerprintsConverter.cs

Then serialize and deserialize using the following options:

var options = new JsonSerializerOptions
{
    Converters = { new NuGetVersionConverter(), new VersionRangeConverter(), new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) },
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    // Other options as required
    WriteIndented = true,
};

var nuget = JsonSerializer.Deserialize<Nuget>(json, options);

Notes:

  • The Newtonsoft converters serialize NuGetVersion as a string, so my converter does also:

    "Version": "6.0.3-rc.1",
    
  • The Json.NET settings used by NuGet.Protocol can be seen here:

    public static readonly JsonSerializerSettings ObjectSerializationSettings = new JsonSerializerSettings
    {
        MaxDepth = JsonSerializationMaxDepth,
        NullValueHandling = NullValueHandling.Ignore,
        TypeNameHandling = TypeNameHandling.None,
        Converters = new List<JsonConverter>
        {
            new NuGetVersionConverter(),
            new VersionInfoConverter(),
            new StringEnumConverter { NamingStrategy = new CamelCaseNamingStrategy() },
            new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal },
            new FingerprintsConverter(),
            new VersionRangeConverter()
       },
    };
    

    The Newtonsoft settings use camel case for enums, so the equivalent System.Text.Json options probably should as well.

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340