Skip to content

Commit

Permalink
deterministically serialize number and string JSON output
Browse files Browse the repository at this point in the history
Ever since the introduction of the JsonOutputFormatter way back
in 2014 [1] all fields where serialized as JSON string. Except
for values looking like an int which where then serialized as JSON
numbers. This had some strange unpredictable side effects like
PreReleaseNumber being an empty JSON string instead of null
or a "2009069" ShortSha being formatted as a JSON number.

This change ensures all fields are serialized as JSON string
except Major, Minor, Patch, PreReleaseNumber,
WeightedPreReleaseNumber, CommitsSinceVersionSource and
UncommittedChanges which are now always serialized as JSON number.

Deserialisation remains the same as before for all fields so we
continue to accept both JSON string and JSON number formatted
values.

Fixes GitTools#1688 and fixes GitTools#2304.

[1] GitTools@f2daf60#diff-fde1a8ff593c6ac2ad558a6f9bb512e0350db91343b185f9b2a00d1d6e848bc3
  • Loading branch information
dieterv committed Jan 10, 2021
1 parent 7e7bccb commit 65d88a7
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"PreReleaseTagWithDash": "",
"PreReleaseLabel": "",
"PreReleaseLabelWithDash": "",
"PreReleaseNumber": "",
"PreReleaseNumber": null,
"WeightedPreReleaseNumber": 0,
"BuildMetaData": 5,
"BuildMetaDataPadded": "0005",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"PreReleaseTagWithDash": "",
"PreReleaseLabel": "",
"PreReleaseLabelWithDash": "",
"PreReleaseNumber": "",
"PreReleaseNumber": null,
"WeightedPreReleaseNumber": 0,
"BuildMetaData": 5,
"BuildMetaDataPadded": "0005",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"PreReleaseTagWithDash": "",
"PreReleaseLabel": "",
"PreReleaseLabelWithDash": "",
"PreReleaseNumber": "",
"PreReleaseNumber": null,
"WeightedPreReleaseNumber": 0,
"BuildMetaData": 5,
"BuildMetaDataPadded": "0005",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"PreReleaseLabelWithDash": "-unstable",
"PreReleaseNumber": 8,
"WeightedPreReleaseNumber": 8,
"BuildMetaData": "",
"BuildMetaData": null,
"BuildMetaDataPadded": "",
"FullBuildMetaData": "Branch.develop.Sha.commitSha",
"MajorMinorPatch": "1.2.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"PreReleaseLabelWithDash": "-ci",
"PreReleaseNumber": 5,
"WeightedPreReleaseNumber": 5,
"BuildMetaData": "",
"BuildMetaData": null,
"BuildMetaDataPadded": "",
"FullBuildMetaData": "Branch.develop.Sha.commitSha",
"MajorMinorPatch": "1.2.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"PreReleaseTagWithDash": "",
"PreReleaseLabel": "",
"PreReleaseLabelWithDash": "",
"PreReleaseNumber": "",
"PreReleaseNumber": null,
"WeightedPreReleaseNumber": 0,
"BuildMetaData": 5,
"BuildMetaDataPadded": "0005",
Expand Down
145 changes: 137 additions & 8 deletions src/GitVersionCore/Model/VersionVariables.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ public class VersionVariables : IEnumerable<KeyValuePair<string, string>>
public string VersionSourceSha { get; }
public string CommitsSinceVersionSource { get; }
public string CommitsSinceVersionSourcePadded { get; }

public string UncommittedChanges { get; }
public string CommitDate { get; set; }

[ReflectionIgnore]
public static IEnumerable<string> AvailableVariables
Expand All @@ -132,8 +132,6 @@ public static IEnumerable<string> AvailableVariables
}
}

public string CommitDate { get; set; }

[ReflectionIgnore]
public string FileName { get; set; }

Expand Down Expand Up @@ -165,7 +163,7 @@ public static VersionVariables FromDictionary(IEnumerable<KeyValuePair<string, s

public static VersionVariables FromJson(string json)
{
var serializeOptions = JsonSerializerOptions();
var serializeOptions = JsonDeSerializerOptions();
var variablePairs = JsonSerializer.Deserialize<Dictionary<string, string>>(json, serializeOptions);
return FromDictionary(variablePairs);
}
Expand Down Expand Up @@ -199,19 +197,36 @@ private static bool ContainsKey(string variable)

public override string ToString()
{
var variables = this.GetProperties().ToDictionary(x => x.Key, x => x.Value);
var variablesType = typeof(VersionVariablesJsonModel);
var variables = new VersionVariablesJsonModel();

foreach (KeyValuePair<string, string> property in this.GetProperties())
{
variablesType.GetProperty(property.Key).SetValue(variables, property.Value);
}

var serializeOptions = JsonSerializerOptions();

return JsonSerializer.Serialize(variables, serializeOptions);
}

private static JsonSerializerOptions JsonDeSerializerOptions()
{
var serializeOptions = new JsonSerializerOptions
{
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Converters = { new GitVersionNumberConverter() }
};
return serializeOptions;
}

private static JsonSerializerOptions JsonSerializerOptions()
{
var serializeOptions = new JsonSerializerOptions
{
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Converters = { new GitVersionStringConverter() }
};
return serializeOptions;
}
Expand Down Expand Up @@ -242,14 +257,128 @@ public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS
public override void Write(Utf8JsonWriter writer, [CanBeNull] string value, JsonSerializerOptions options)
{
value ??= string.Empty;
if (NotAPaddedNumber(value) && int.TryParse(value, out var number))
writer.WriteStringValue(value);
}

public override bool HandleNull => true;

private static bool NotAPaddedNumber(string value) => value != null && (value == "0" || !value.StartsWith("0"));
}

public class GitVersionNumberConverter : JsonConverter<string>
{
public override bool CanConvert(Type typeToConvert)
=> typeToConvert == typeof(string);

public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.Number && typeToConvert == typeof(string))
return reader.GetString() ?? "";

var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
if (Utf8Parser.TryParse(span, out long number, out var bytesConsumed) && span.Length == bytesConsumed)
return number.ToString();

var data = reader.GetString();

throw new InvalidOperationException($"'{data}' is not a correct expected value!")
{
Source = nameof(GitVersionNumberConverter)
};
}

public override void Write(Utf8JsonWriter writer, [CanBeNull] string value, JsonSerializerOptions options)
{
if (string.IsNullOrWhiteSpace(value))
{
writer.WriteNullValue();
}
else if (int.TryParse(value, out var number))
{
writer.WriteNumberValue(number);
}
else
writer.WriteStringValue(value);
{
throw new InvalidOperationException($"'{value}' is not a correct expected value!")
{
Source = nameof(GitVersionStringConverter)
};
}

}

public override bool HandleNull => true;

private static bool NotAPaddedNumber(string value) => value != null && (value == "0" || !value.StartsWith("0"));
}

public class VersionVariablesJsonModel
{
[JsonConverter(typeof(GitVersionNumberConverter))]
public string Major { get; set; }
[JsonConverter(typeof(GitVersionNumberConverter))]
public string Minor { get; set; }
[JsonConverter(typeof(GitVersionNumberConverter))]
public string Patch { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string PreReleaseTag { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string PreReleaseTagWithDash { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string PreReleaseLabel { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string PreReleaseLabelWithDash { get; set; }
[JsonConverter(typeof(GitVersionNumberConverter))]
public string PreReleaseNumber { get; set; }
[JsonConverter(typeof(GitVersionNumberConverter))]
public string WeightedPreReleaseNumber { get; set; }
[JsonConverter(typeof(GitVersionNumberConverter))]
public string BuildMetaData { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string BuildMetaDataPadded { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string FullBuildMetaData { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string MajorMinorPatch { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string SemVer { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string LegacySemVer { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string LegacySemVerPadded { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string AssemblySemVer { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string AssemblySemFileVer { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string FullSemVer { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string InformationalVersion { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string BranchName { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string EscapedBranchName { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string Sha { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string ShortSha { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string NuGetVersionV2 { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string NuGetVersion { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string NuGetPreReleaseTagV2 { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string NuGetPreReleaseTag { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string VersionSourceSha { get; set; }
[JsonConverter(typeof(GitVersionNumberConverter))]
public string CommitsSinceVersionSource { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string CommitsSinceVersionSourcePadded { get; set; }
[JsonConverter(typeof(GitVersionNumberConverter))]
public string UncommittedChanges { get; set; }
[JsonConverter(typeof(GitVersionStringConverter))]
public string CommitDate { get; set; }
}
}

0 comments on commit 65d88a7

Please sign in to comment.