Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/GitVersionCore.Tests/JsonVersionBuilderTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using NUnit.Framework;
using Shouldly;
using GitVersion.OutputFormatters;
using GitVersion.OutputVariables;
using GitVersion;
using GitVersionCore.Tests.Helpers;
Expand Down Expand Up @@ -38,7 +37,7 @@ public void Json()

var variableProvider = sp.GetService<IVariableProvider>();
var variables = variableProvider.GetVariablesFor(semanticVersion, config, false);
var json = JsonOutputFormatter.ToJson(variables);
var json = variables.ToString();
json.ShouldMatchApproved(c => c.SubFolder("Approved"));
}
}
Expand Down
17 changes: 8 additions & 9 deletions src/GitVersionCore.Tests/VariableProviderTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using GitVersion;
using GitVersion.Logging;
using GitVersion.OutputFormatters;
using GitVersion.OutputVariables;
using GitVersion.VersioningModes;
using GitVersionCore.Tests.Helpers;
Expand Down Expand Up @@ -74,7 +73,7 @@ public void ProvidesVariablesInContinuousDeliveryModeForPreRelease()

var vars = variableProvider.GetVariablesFor(semVer, config, false);

JsonOutputFormatter.ToJson(vars).ShouldMatchApproved(c => c.SubFolder("Approved"));
vars.ToString().ShouldMatchApproved(c => c.SubFolder("Approved"));
}

[Test]
Expand All @@ -101,7 +100,7 @@ public void ProvidesVariablesInContinuousDeliveryModeForPreReleaseWithPadding()

var vars = variableProvider.GetVariablesFor(semVer, config, false);

JsonOutputFormatter.ToJson(vars).ShouldMatchApproved(c => c.SubFolder("Approved"));
vars.ToString().ShouldMatchApproved(c => c.SubFolder("Approved"));
}

[Test]
Expand All @@ -127,7 +126,7 @@ public void ProvidesVariablesInContinuousDeploymentModeForPreRelease()

var vars = variableProvider.GetVariablesFor(semVer, config, false);

JsonOutputFormatter.ToJson(vars).ShouldMatchApproved(c => c.SubFolder("Approved"));
vars.ToString().ShouldMatchApproved(c => c.SubFolder("Approved"));
}

[Test]
Expand All @@ -152,7 +151,7 @@ public void ProvidesVariablesInContinuousDeliveryModeForStable()

var vars = variableProvider.GetVariablesFor(semVer, config, false);

JsonOutputFormatter.ToJson(vars).ShouldMatchApproved(c => c.SubFolder("Approved"));
vars.ToString().ShouldMatchApproved(c => c.SubFolder("Approved"));
}

[Test]
Expand All @@ -177,7 +176,7 @@ public void ProvidesVariablesInContinuousDeploymentModeForStable()

var vars = variableProvider.GetVariablesFor(semVer, config, false);

JsonOutputFormatter.ToJson(vars).ShouldMatchApproved(c => c.SubFolder("Approved"));
vars.ToString().ShouldMatchApproved(c => c.SubFolder("Approved"));
}

[Test]
Expand Down Expand Up @@ -205,7 +204,7 @@ public void ProvidesVariablesInContinuousDeploymentModeForStableWhenCurrentCommi

var vars = variableProvider.GetVariablesFor(semVer, config, true);

JsonOutputFormatter.ToJson(vars).ShouldMatchApproved(c => c.SubFolder("Approved"));
vars.ToString().ShouldMatchApproved(c => c.SubFolder("Approved"));
}

[Test]
Expand Down Expand Up @@ -277,7 +276,7 @@ public void ProvidesVariablesInContinuousDeliveryModeForFeatureBranch()

var vars = variableProvider.GetVariablesFor(semVer, config, false);

JsonOutputFormatter.ToJson(vars).ShouldMatchApproved(c => c.SubFolder("Approved"));
vars.ToString().ShouldMatchApproved(c => c.SubFolder("Approved"));
}

[Test]
Expand All @@ -304,7 +303,7 @@ public void ProvidesVariablesInContinuousDeliveryModeForFeatureBranchWithCustomA

var vars = variableProvider.GetVariablesFor(semVer, config, false);

JsonOutputFormatter.ToJson(vars).ShouldMatchApproved(c => c.SubFolder("Approved"));
vars.ToString().ShouldMatchApproved(c => c.SubFolder("Approved"));
}
}
}
274 changes: 274 additions & 0 deletions src/GitVersionCore/Helpers/JsonSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

namespace GitVersion.Helpers
{
// Credit to https://github.com/neuecc
// Inspired by https://gist.github.com/neuecc/7d728cd99d2a1e613362
public static class JsonSerializer
{
private const string INDENT_STRING = " ";
private static readonly Encoding UTF8 = new UTF8Encoding(false);

public static string Serialize(object obj)
{
using var ms = new MemoryStream();
using var sw = new StreamWriter(ms, UTF8);
Serialize(sw, obj);
sw.Flush();
return UTF8.GetString(ms.ToArray());
}

public static void Serialize(TextWriter tw, object obj)
{
SerializeObject(tw, obj);
}

enum JsonType
{
@string,
number,
boolean,
@object,
array,
@null
}

static JsonType GetJsonType(object obj)
{
if (obj == null) return JsonType.@null;

return Type.GetTypeCode(obj.GetType()) switch
{
TypeCode.Boolean => JsonType.boolean,
TypeCode.String => JsonType.@string,
TypeCode.Char => JsonType.@string,
TypeCode.DateTime => JsonType.@string,
TypeCode.Int16 => JsonType.number,
TypeCode.Int32 => JsonType.number,
TypeCode.Int64 => JsonType.number,
TypeCode.UInt16 => JsonType.number,
TypeCode.UInt32 => JsonType.number,
TypeCode.UInt64 => JsonType.number,
TypeCode.Single => JsonType.number,
TypeCode.Double => JsonType.number,
TypeCode.Decimal => JsonType.number,
TypeCode.SByte => JsonType.number,
TypeCode.Byte => JsonType.number,
TypeCode.Object => (obj switch
{
// specialized for well known types
Uri _ => JsonType.@string,
DateTimeOffset _ => JsonType.@string,
Guid _ => JsonType.@string,
StringBuilder _ => JsonType.@string,
IDictionary _ => JsonType.@object,
_ => ((obj is IEnumerable) ? JsonType.array : JsonType.@object)
}),
TypeCode.DBNull => JsonType.@null,
TypeCode.Empty => JsonType.@null,
_ => JsonType.@null
};
}

static void SerializeObject(TextWriter tw, object o)
{
switch (GetJsonType(o))
{
case JsonType.@string:
switch (o)
{
case string s when NotAPaddedNumber(s) && int.TryParse(s, out var n):
WriteNumber(tw, n);
break;
case string s:
WriteString(tw, s);
break;
case DateTime time:
{
var s = time.ToString("o");
WriteString(tw, s);
break;
}
case DateTimeOffset offset:
{
var s = offset.ToString("o");
WriteString(tw, s);
break;
}
default:
WriteString(tw, o.ToString());
break;
}

break;
case JsonType.number:
WriteNumber(tw, o);
break;
case JsonType.boolean:
WriteBoolean(tw, (bool) o);
break;
case JsonType.@object:
WriteObject(tw, o);
break;
case JsonType.array:
WriteArray(tw, (IEnumerable) o);
break;
case JsonType.@null:
WriteNull(tw);
break;
default:
break;
}
}

static void WriteString(TextWriter tw, string o)
{
tw.Write('\"');

foreach (var c in o)
{
switch (c)
{
case '"':
tw.Write("\\\"");
break;
case '\\':
tw.Write("\\\\");
break;
case '\b':
tw.Write("\\b");
break;
case '\f':
tw.Write("\\f");
break;
case '\n':
tw.Write("\\n");
break;
case '\r':
tw.Write("\\r");
break;
case '\t':
tw.Write("\\t");
break;
default:
tw.Write(c);
break;
}
}

tw.Write('\"');
}

static void WriteNumber(TextWriter tw, object o)
{
tw.Write(o.ToString());
}

static void WriteBoolean(TextWriter tw, bool o)
{
tw.Write(o ? "true" : "false");
}

static void WriteObject(TextWriter tw, object o)
{
tw.Write('{');

if (o is IDictionary dict)
{
// Dictionary
var isFirst = true;
foreach (DictionaryEntry item in dict)
{
if (!isFirst) tw.Write(",");
else isFirst = false;

tw.Write('\"');
tw.Write(item.Key);
tw.Write('\"');
tw.Write(":");
SerializeObject(tw, item.Value);
}
}
else
{
// Object
var isFirst = true;
foreach (var item in o.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty))
{
if (!isFirst) tw.Write(",");
else isFirst = false;

var key = item.Name;
var value = item.GetGetMethod().Invoke(o, null); // safe reflection for unity
tw.Write('\"');
tw.Write(key);
tw.Write('\"');
tw.Write(":");
SerializeObject(tw, value);
}

isFirst = true;
foreach (var item in o.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetField))
{
if (!isFirst) tw.Write(",");
else isFirst = false;

var key = item.Name;
var value = item.GetValue(o);
tw.Write('\"');
tw.Write(key);
tw.Write('\"');
tw.Write(":");
SerializeObject(tw, value);
}
}

tw.Write('}');
}

static void WriteArray(TextWriter tw, IEnumerable o)
{
tw.Write("[");
var isFirst = true;
foreach (var item in o)
{
if (!isFirst) tw.Write(",");
else isFirst = false;

SerializeObject(tw, item);
}

tw.Write("]");
}

static void WriteNull(TextWriter tw)
{
tw.Write("null");
}

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

public static string FormatJson(string json)
{
var indentation = 0;
var quoteCount = 0;
var result =
from ch in json
let quotes = ch == '"' ? quoteCount++ : quoteCount
let lineBreak = ch == ',' && quotes % 2 == 0 ? ch + System.Environment.NewLine + string.Concat(Enumerable.Repeat(INDENT_STRING, indentation)) : null
let openChar = ch == '{' || ch == '[' ? ch + System.Environment.NewLine + string.Concat(Enumerable.Repeat(INDENT_STRING, ++indentation)) : ch.ToString()
let closeChar = ch == '}' || ch == ']' ? System.Environment.NewLine + string.Concat(Enumerable.Repeat(INDENT_STRING, --indentation)) + ch : ch.ToString()
select lineBreak ??
(openChar.Length > 1
? openChar
: closeChar);

return string.Concat(result);
}
}
}
Loading