As of Json.NET version 13 and possibly earlier, Json.NET will now round-trip System.Version
without need for a converter, however its default serialization is inconsistent between .NET Core and .NET Framework.
In .NET Core 6 and later it is automatically round-tripped as a JSON string using its ToString()
representation (here "1.2.3.0"
).
Demo fiddle #1 here.
System.Text.Json has also serialized Version
identically since at least .NET Core 6.
In .NET Framework 4.7.2 it is automatically round-tripped as a JSON object by default (here {"Major":1,"Minor":2,"Build":3,"Revision":0,"MajorRevision":0,"MinorRevision":0}
)
Demo fiddle #2 here.
However, as noted by Manuel in this answer, you can use VersionConverter
to force Version
to be round-tripped as a string.
See: JToken.FromObject(System.Version) behaves differently from dotnet core and framework #2588.
If for some reason you are dealing with JSON that might contain a Version
serialized as either an object or a string, you will need to write your own JsonConverter<Version>
that handles this. The following is one such:
public class VersionObjectConverter : JsonConverter<Version>
{
public VersionObjectConverter() : this(false) { } // Serialize as a string by default
public VersionObjectConverter(bool serializeAsObject) { this.SerializeAsObject = serializeAsObject; }
static readonly Newtonsoft.Json.Converters.VersionConverter InnerConverter = new Newtonsoft.Json.Converters.VersionConverter();
bool SerializeAsObject { get; set; }
class VersionDto
{
public int Major; public int Minor; public int Build = -1; public int Revision = -1; public Int16 MajorRevision = -1; public Int16 MinorRevision = -1;
public Version ToVersion()
{
if (Build == -1)
return new Version(Major, Minor);
else if (Revision == -1)
return new Version(Major, Minor, Build);
else
return new Version(Major, Minor, Build, Revision);
}
}
public override void WriteJson(JsonWriter writer, Version value, JsonSerializer serializer)
{
if (value == null)
writer.WriteNull();
else if (SerializeAsObject)
serializer.Serialize(writer, new VersionDto { Major = value.Major, Minor = value.Minor, Build = value.Build, Revision = value.Revision, MajorRevision = value.MajorRevision, MinorRevision = value.MinorRevision});
else
InnerConverter.WriteJson(writer, value, serializer);
}
public override Version ReadJson(JsonReader reader, Type objectType, Version existingValue, bool hasExistingValue, JsonSerializer serializer)
{
switch (reader.MoveToContentAndAssert().TokenType)
{
case JsonToken.Null: return null;
case JsonToken.String: return (Version)InnerConverter.ReadJson(reader, objectType, hasExistingValue ? existingValue : null, serializer);
case JsonToken.StartObject: return serializer.Deserialize<VersionDto>(reader).ToVersion();
default: throw new JsonSerializationException(string.Format("Unknown token type {0}", reader.TokenType));
}
}
}
public static partial class JsonExtensions
{
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException("reader");
if (reader.TokenType == JsonToken.None) // Skip past beginning of stream.
reader.ReadAndAssert();
while (reader.TokenType == JsonToken.Comment) // Skip past comments.
reader.ReadAndAssert();
return reader;
}
public static JsonReader ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException("reader");
if (!reader.Read())
throw new JsonReaderException("Unexpected end of JSON stream.");
return reader;
}
}
And round-trip your Version
as follows:
var settings = new JsonSerializerSettings
{
Converters = { new VersionObjectConverter(serializeAsObject : true) }, // Use serializeAsObject : false if you don't want Version to be serialized as a JSON object
};
var reportJSON = JsonConvert.SerializeObject(version, settings);
var report2 = JsonConvert.DeserializeObject<Version>(reportJSON, settings);
var reportJSON2 = JsonConvert.SerializeObject(report2, settings);
Assert.That(report2 == version); // Passes
Assert.That(reportJSON == "{\"Major\":1,\"Minor\":2,\"Build\":3,\"Revision\":0,\"MajorRevision\":0,\"MinorRevision\":0}"); // Passes
Notes:
The converter is written in using .NET Framework 4.7.2 / C# 7.3 syntax.
To control whether Version
is re-serialized as a string or an object, pass serializeAsObject : true
or serializeAsObject : false
to the constructor.
When Revision
is -1
, a Version
constructor that does not take the revision must be used. And when Build
is -1
the constructor that takes neither must be used. VersionConverter
from this answer does not handle this correctly.
Demo fiddles #3 for .NET Framework 4.7.2 here and for #4 for .NET Core 6 here.
Version
class is not XML-serializable, but I'm pretty sure same mechanism applies to JSON serializers. – Ilowell