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 Feb 3, 2021
1 parent b10adbb commit d1baac1
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 50 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
54 changes: 10 additions & 44 deletions src/GitVersion.Core/Model/VersionVariables.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
using System;
using System.Buffers;
using System.Buffers.Text;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using JetBrains.Annotations;
using YamlDotNet.Serialization;
using static GitVersion.Extensions.ObjectExtensions;

Expand Down Expand Up @@ -116,8 +113,8 @@ public VersionVariables(string major,
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 +129,6 @@ public static IEnumerable<string> AvailableVariables
}
}

public string CommitDate { get; set; }

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

Expand Down Expand Up @@ -199,7 +194,14 @@ 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);
Expand All @@ -211,45 +213,9 @@ private static JsonSerializerOptions JsonSerializerOptions()
{
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Converters = { new GitVersionStringConverter() }
Converters = { new VersionVariablesJsonStringConverter() }
};
return serializeOptions;
}
}

public class GitVersionStringConverter : 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(GitVersionStringConverter)
};
}

public override void Write(Utf8JsonWriter writer, [CanBeNull] string value, JsonSerializerOptions options)
{
value ??= string.Empty;
if (NotAPaddedNumber(value) && int.TryParse(value, out var number))
writer.WriteNumberValue(number);
else
writer.WriteStringValue(value);
}

public override bool HandleNull => true;

private static bool NotAPaddedNumber(string value) => value != null && (value == "0" || !value.StartsWith("0"));
}
}
74 changes: 74 additions & 0 deletions src/GitVersion.Core/Model/VersionVariablesJsonModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.Text.Json.Serialization;

namespace GitVersion.OutputVariables
{
public class VersionVariablesJsonModel
{
[JsonConverter(typeof(VersionVariablesJsonNumberConverter))]
public string Major { get; set; }
[JsonConverter(typeof(VersionVariablesJsonNumberConverter))]
public string Minor { get; set; }
[JsonConverter(typeof(VersionVariablesJsonNumberConverter))]
public string Patch { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string PreReleaseTag { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string PreReleaseTagWithDash { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string PreReleaseLabel { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string PreReleaseLabelWithDash { get; set; }
[JsonConverter(typeof(VersionVariablesJsonNumberConverter))]
public string PreReleaseNumber { get; set; }
[JsonConverter(typeof(VersionVariablesJsonNumberConverter))]
public string WeightedPreReleaseNumber { get; set; }
[JsonConverter(typeof(VersionVariablesJsonNumberConverter))]
public string BuildMetaData { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string BuildMetaDataPadded { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string FullBuildMetaData { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string MajorMinorPatch { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string SemVer { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string LegacySemVer { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string LegacySemVerPadded { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string AssemblySemVer { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string AssemblySemFileVer { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string FullSemVer { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string InformationalVersion { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string BranchName { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string EscapedBranchName { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string Sha { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string ShortSha { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string NuGetVersionV2 { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string NuGetVersion { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string NuGetPreReleaseTagV2 { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string NuGetPreReleaseTag { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string VersionSourceSha { get; set; }
[JsonConverter(typeof(VersionVariablesJsonNumberConverter))]
public string CommitsSinceVersionSource { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string CommitsSinceVersionSourcePadded { get; set; }
[JsonConverter(typeof(VersionVariablesJsonNumberConverter))]
public string UncommittedChanges { get; set; }
[JsonConverter(typeof(VersionVariablesJsonStringConverter))]
public string CommitDate { get; set; }
}
}
57 changes: 57 additions & 0 deletions src/GitVersion.Core/Model/VersionVariablesJsonNumberConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Buffers;
using System.Buffers.Text;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using JetBrains.Annotations;

namespace GitVersion.OutputVariables
{
public class VersionVariablesJsonNumberConverter : 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(VersionVariablesJsonNumberConverter)
};
}

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
{
throw new InvalidOperationException($"'{value}' is not a correct expected value!")
{
Source = nameof(VersionVariablesJsonStringConverter)
};
}

}

public override bool HandleNull => true;

private static bool NotAPaddedNumber(string value) => value != null && (value == "0" || !value.StartsWith("0"));
}
}
43 changes: 43 additions & 0 deletions src/GitVersion.Core/Model/VersionVariablesJsonStringConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.Buffers;
using System.Buffers.Text;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using JetBrains.Annotations;

namespace GitVersion.OutputVariables
{
public class VersionVariablesJsonStringConverter : 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(VersionVariablesJsonStringConverter)
};
}

public override void Write(Utf8JsonWriter writer, [CanBeNull] string value, JsonSerializerOptions options)
{
value ??= string.Empty;
writer.WriteStringValue(value);
}

public override bool HandleNull => true;

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

0 comments on commit d1baac1

Please sign in to comment.