Skip to content

Latest commit

 

History

History
337 lines (273 loc) · 9.25 KB

type-to-string-mapping.md

File metadata and controls

337 lines (273 loc) · 9.25 KB

Type to string mapping

Certain types, when passed directly to Verify(), are written directly without going through json serialization.

The API for controlling this behavior is TreatAsString()

Example of passing directly to Verify():

[Fact]
public Task Example() =>
    Verify(new DateOnly(2020, 10, 4));

Default type mapping

The default mapping is:

{
    typeof(StringBuilder), (target, _) => ((StringBuilder) target).ToString()
},
{
    typeof(StringWriter), (target, _) => ((StringWriter) target).ToString()
},
{
    typeof(bool), (target, _) => ((bool) target).ToString(Culture.InvariantCulture)
},
{
    typeof(short), (target, _) => ((short) target).ToString(Culture.InvariantCulture)
},
{
    typeof(ushort), (target, _) => ((ushort) target).ToString(Culture.InvariantCulture)
},
{
    typeof(int), (target, _) => ((int) target).ToString(Culture.InvariantCulture)
},
{
    typeof(uint), (target, _) => ((uint) target).ToString(Culture.InvariantCulture)
},
{
    typeof(long), (target, _) => ((long) target).ToString(Culture.InvariantCulture)
},
{
    typeof(ulong), (target, _) => ((ulong) target).ToString(Culture.InvariantCulture)
},
{
    typeof(decimal), (target, _) => ((decimal) target).ToString(Culture.InvariantCulture)
},
{
    typeof(BigInteger), (target, _) => ((BigInteger) target).ToString(Culture.InvariantCulture)
},
#if NET6_0_OR_GREATER
{
    typeof(Half), (target, _) => ((Half) target).ToString(Culture.InvariantCulture)
},
#endif
#if NET6_0_OR_GREATER
{
    typeof(Date), (target, _) =>
    {
        var date = (Date) target;
        return date.ToString("yyyy-MM-dd", Culture.InvariantCulture);
    }
},
{
    typeof(Time), (target, _) =>
    {
        var time = (Time) target;
        return time.ToString("h:mm tt", Culture.InvariantCulture);
    }
},
#endif
{
    typeof(float), (target, _) => ((float) target).ToString(Culture.InvariantCulture)
},
{
    typeof(double), (target, _) => ((double) target).ToString(Culture.InvariantCulture)
},
{
    typeof(Guid), (target, _) => ((Guid) target).ToString()
},
{
    typeof(DateTime), (target, _) => DateFormatter.ToJsonString((DateTime) target)
},
{
    typeof(DateTimeOffset), (target, _) => DateFormatter.ToJsonString((DateTimeOffset) target)
},
{
    typeof(XmlNode), (target, _) =>
    {
        var converted = (XmlNode) target;
        var document = XDocument.Parse(converted.OuterXml);
        return new(document.ToString(), "xml");
    }
},
{
    typeof(XElement), (target, settings) =>
    {
        var converted = (XElement) target;
        return new(converted.ToString(), "xml");
    }
},

snippet source | anchor

Scrubbing is bypassed

This approach bypasses the Guid and DateTime scrubbing.

DateTime formatting

How DateTimes are converted to a string:

static partial class DateFormatter
{
    public static string ToJsonString(DateTime value)
    {
        var result = GetJsonDatePart(value);

        if (value.Kind != DateTimeKind.Unspecified)
        {
            result += $" {value.Kind}";
        }

        return result;
    }

    static string GetJsonDatePart(DateTime value)
    {
        if (value.TimeOfDay == TimeSpan.Zero)
        {
            return value.ToString("yyyy-MM-dd", Culture.InvariantCulture);
        }

        if (value is {Second: 0, Millisecond: 0})
        {
            return value.ToString("yyyy-MM-dd HH:mm", Culture.InvariantCulture);
        }

        if (value.Millisecond == 0)
        {
            return value.ToString("yyyy-MM-dd HH:mm:ss", Culture.InvariantCulture);
        }

        return value.ToString("yyyy-MM-dd HH:mm:ss.FFFFFFF", Culture.InvariantCulture);
    }

    public static string ToParameterString(DateTime value)
    {
        var result = GetParameterDatePart(value);

        if (value.Kind != DateTimeKind.Unspecified)
        {
            result += value.Kind;
        }

        return result;
    }

    static string GetParameterDatePart(DateTime value)
    {
        if (value.TimeOfDay == TimeSpan.Zero)
        {
            return value.ToString("yyyy-MM-dd", Culture.InvariantCulture);
        }

        if (value is {Second: 0, Millisecond: 0})
        {
            return value.ToString("yyyy-MM-ddTHH-mm", Culture.InvariantCulture);
        }

        if (value.Millisecond == 0)
        {
            return value.ToString("yyyy-MM-ddTHH-mm-ss", Culture.InvariantCulture);
        }

        return value.ToString("yyyy-MM-ddTHH-mm-ss.FFFFFFF", Culture.InvariantCulture);
    }
}

snippet source | anchor

DateTimeOffset formatting

How DateTimeOffset are converted to a string:

static partial class DateFormatter
{
    public static string ToJsonString(DateTimeOffset value)
    {
        var result = GetJsonDatePart(value);
        result += $" {GetDateOffset(value)}";
        return result;
    }

    static string GetJsonDatePart(DateTimeOffset value)
    {
        if (value.TimeOfDay == TimeSpan.Zero)
        {
            return value.ToString("yyyy-MM-dd", Culture.InvariantCulture);
        }

        if (value is {Second: 0, Millisecond: 0})
        {
            return value.ToString("yyyy-MM-dd HH:mm", Culture.InvariantCulture);
        }

        if (value.Millisecond == 0)
        {
            return value.ToString("yyyy-MM-dd HH:mm:ss", Culture.InvariantCulture);
        }

        return value.ToString("yyyy-MM-dd HH:mm:ss.FFFFFFF", Culture.InvariantCulture);
    }

    public static string ToParameterString(DateTimeOffset value)
    {
        var result = GetParameterDatePart(value);
        result += GetDateOffset(value);

        return result;
    }

    static string GetParameterDatePart(DateTimeOffset value)
    {
        if (value.TimeOfDay == TimeSpan.Zero)
        {
            return value.ToString("yyyy-MM-dd", Culture.InvariantCulture);
        }

        if (value is {Second: 0, Millisecond: 0})
        {
            return value.ToString("yyyy-MM-ddTHH-mm", Culture.InvariantCulture);
        }

        if (value.Millisecond == 0)
        {
            return value.ToString("yyyy-MM-ddTHH-mm-ss", Culture.InvariantCulture);
        }

        return value.ToString("yyyy-MM-ddTHH-mm-ss.FFFFFFF", Culture.InvariantCulture);
    }

    static string GetDateOffset(DateTimeOffset value)
    {
        var offset = value.Offset;

        if (offset > TimeSpan.Zero)
        {
            if (offset.Minutes == 0)
            {
                return $"+{offset.TotalHours:0}";
            }

            return $"+{offset.Hours:0}-{offset.Minutes:00}";
        }

        if (offset < TimeSpan.Zero)
        {
            if (offset.Minutes == 0)
            {
                return $"{offset.Hours:0}";
            }

            return $"{offset.Hours:0}{offset.Minutes:00}";
        }

        return "+0";
    }
}

snippet source | anchor

Override TreatAsString defaults

The default TreatAsString behavior can be overridden:

VerifierSettings.TreatAsString<DateTime>(
    (target, settings) => target.ToString("D"));

snippet source | anchor

Extra Types

Extra types can be added to this mapping:

VerifierSettings.TreatAsString<ClassWithToString>(
    (target, settings) => target.Property);

snippet source | anchor

Redundant json serialization settings

Since this approach bypasses json serialization, any json serialization settings are redundant. For example DontScrubDateTimes, UseStrictJson, and DontScrubGuids.

Note that any json serialization settings will still apply to anything amended to the target via Recording or JsonAppenders

See also