Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Newtonsoft Json converter for nullable types #346

Merged
merged 5 commits into from
Jan 30, 2023

Conversation

Blackclaws
Copy link
Contributor

@Blackclaws Blackclaws commented Jan 26, 2023

This is an attempt at fixing the behavior of the Newtonsoft.Json converter when confronted with serializing nullable fields of ValueTypes of reference types.

Its still failing the Snapshot tests as I have no clue how to update all the snapshots and haven't been able to find any documentation regarding this.

Its also failing some other tests for reasons I don't really understand as they are also failing in a clean pull of the main branch ( for example all tests dealing with date time offset fail because they expect the offset to timezone to be 0:00 while my local timezone is +1:00 (these seem to be a bit brittle).

Other tests that fail are indeed Newtonsoft.Json tests, however those test failures are a bit weird as they fail in the serialization step which now serializes some value objects to a null string. However I didn't touch that code at all and this result also seems to hold for a clean pull of the main branch. Its a bit weird, we'll see how the CI test runners handle this one.

I might add that I'm developing on Linux and the tooling around the project seems to mostly assume a Windows environment.

Also the tests are missing a test for non ValueType user types as ValueObject types.

Fixes #344

@SteveDunn
Copy link
Owner

Thanks for the PR! Which tests are failing due to time zones? I thought I'd fixed those, but I wasn't 100% sure that I got all of them.

I've updated the Wiki page on what to do with different snapshots: https://github.com/SteveDunn/Vogen/wiki/Tests

Thanks again, I'll take a look to see if I spot this weirdness!

@Blackclaws
Copy link
Contributor Author

Thanks for the PR! Which tests are failing due to time zones? I thought I'd fixed those, but I wasn't 100% sure that I got all of them.

I've updated the Wiki page on what to do with different snapshots: https://github.com/SteveDunn/Vogen/wiki/Tests

Thanks again, I'll take a look to see if I spot this weirdness!

Tests like these are failing:

Failed Vogen.Tests.InstanceGenerationTests+With_underlying_DateTime_and_DateTimeOffset_instances+Which_are_created_with_valid_ISO8601_strings.It_generates_a_valid_item_when_given_a_valid_DateTimeOffset_input [63 ms]
  Error Message:
   Expected r.Value to be "global::System.DateTimeOffset.Parse("2020-12-13T00:00:00.0000000+00:00", null, global::System.Globalization.DateTimeStyles.RoundtripKind)", but "global::System.DateTimeOffset.Parse("2020-12-13T00:00:00.0000000+01:00", null, global::System.Globalization.DateTimeStyles.RoundtripKind)" differs near "1:0" (index 66).

Basically all related to RoundtripKind.

I've updated the snapshots.

@Blackclaws
Copy link
Contributor Author

@SteveDunn I wonder right now why there are separate implementations in the templates for all the standard primitive types and then an "Any" type. The way I see it the main differences should be between Value and Reference types. I wasn't able at a cursory glance at least to find any significant differences between the implementations for the different value types.

Since I needed to differentiate between Value AnyTypes and Reference AnyTypes I wonder whether this isn't just a distinction that should be made in general. This would massively reduce the number of templates needed and likewise would reduce the test surface quite a bit as now the only real tests that would need to be performed are ValueTypes vs ReferenceTypes and maybe Records as well but there'd be no real need to test against all different basic ValueTypes if the implementation was identical.

@jeffward01
Copy link

jeffward01 commented Jan 27, 2023

I wrote 'Cli version of Vogen' this because Rider has a real pain with Intellisense, and I got tired of restarting rider due to compiler errors because of source generators. If its helpful to add to Vogen, i have no problem with that, i just need to clean up the code a little. I think SourceGenerators are much much better than CLI, but I just got so tried of the "the IComparable<T> must be implemented error" due to Intellisense and Rider.

If its helpful, here is a list of the Newtonsoft implementations by type.

I simply generated one of each type, then copied out the Newtonsoft code. So this the generation result of each type

namespace Aeonic.CodeGen.Vogen.Infra.Stores;

using SharedCommon.Primitives;

public class NewtonsoftJsonConverterStore
{
    // ReSharper disable once InconsistentNaming
    public readonly ICodeGenTemplate Attribute = CodeGenTemplate.With(
        @"[global::Newtonsoft.Json.JsonConverter(typeof({{strongValueObjectName}}NewtonsoftJsonConverter))]");
    
    private const string StringTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                return {{strongValueObjectName}}.Deserialize(serializer.Deserialize<global::System.String>(reader));
            }
        }
";

    private const string BooleanTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var result = serializer.Deserialize<global::System.Boolean?>(reader);
                return result.HasValue ? {{strongValueObjectName}}.Deserialize(result.Value) : null;
            }
        }";

    private const string DateTimeTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var dt = serializer.Deserialize<global::System.DateTime?>(reader);
                return dt.HasValue ? {{strongValueObjectName}}.Deserialize(dt.Value) : null;
            }
        }";

    private const string DateOnlyTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var dt = serializer.Deserialize<global::System.DateOnly?>(reader);
                return dt.HasValue ? {{strongValueObjectName}}.Deserialize(dt.Value) : null;
            }
        }";

    private const string TimeOnlyTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var dt = serializer.Deserialize<global::System.TimeOnly?>(reader);
                return dt.HasValue ? {{strongValueObjectName}}.Deserialize(dt.Value) : null;
            }
        }";

    private const string DateTimeOffsetTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var dt = serializer.Deserialize<global::System.DateTimeOffset?>(reader);
                return dt.HasValue ? {{strongValueObjectName}}.Deserialize(dt.Value) : null;
            }
        }
";

    private const string IntTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var result = serializer.Deserialize<global::System.Int32?>(reader);
                return result.HasValue ? {{strongValueObjectName}}.Deserialize(result.Value) : null;
            }
        }";

    private const string FloatSingleTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var result = serializer.Deserialize<global::System.Single?>(reader);
                return result.HasValue ? {{strongValueObjectName}}.Deserialize(result.Value) : null;
            }
        }";

    private const string DoubleTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var result = serializer.Deserialize<global::System.Double?>(reader);
                return result.HasValue ? {{strongValueObjectName}}.Deserialize(result.Value) : null;
            }
        }
";

    private const string DecimalTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var result = serializer.Deserialize<global::System.Decimal?>(reader);
                return result.HasValue ? {{strongValueObjectName}}.Deserialize(result.Value) : null;
            }
        }
";

    private const string LongTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var result = serializer.Deserialize<global::System.Int64?>(reader);
                return result.HasValue ? {{strongValueObjectName}}.Deserialize(result.Value) : null;
            }
        }
";

    private const string ShortTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var result = serializer.Deserialize<global::System.Int16?>(reader);
                return result.HasValue ? {{strongValueObjectName}}.Deserialize(result.Value) : null;
            }
        }";

    private const string GuidTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var guid = serializer.Deserialize<System.Guid?>(reader);
                return guid.HasValue ? {{strongValueObjectName}}.Deserialize(guid.Value) : null;
            }
        }";

    public NewtonsoftJsonConverterStore()
    {
        this.Store.Add(PrimitiveTypeEnum.AsString, CodeGenTemplate.With(StringTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsBoolean, CodeGenTemplate.With(BooleanTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsDateTime, CodeGenTemplate.With(DateTimeTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsDateTimeOffset, CodeGenTemplate.With(DateTimeOffsetTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsDateOnly, CodeGenTemplate.With(DateOnlyTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsTimeOnly, CodeGenTemplate.With(TimeOnlyTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsInt, CodeGenTemplate.With(IntTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsFloat, CodeGenTemplate.With(FloatSingleTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsDouble, CodeGenTemplate.With(DoubleTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsDecimal, CodeGenTemplate.With(DecimalTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsGuid, CodeGenTemplate.With(GuidTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsShort, CodeGenTemplate.With(ShortTemplate));
    }

    private Dictionary<PrimitiveTypeEnum, ICodeGenTemplate> Store { get; } = new();

    public ICodeGenTemplate Get(PrimitiveTypeEnum primitiveType)
    {
        return this.Store.First(_ => _.Key == primitiveType)
            .Value;
    }
}

@Blackclaws
Copy link
Contributor Author

Blackclaws commented Jan 27, 2023

@SteveDunn So the template tests failing is kind of expected, as I did not retain the normal template, it could be added back just as a placeholder but that seems weird. Again the question is should we just use two template files to begin with one for value one for reference types?

I don't know why the snapshot tests are failing. I've ran the tests locally with -p:THOROUGH=true and commited the result.

@SteveDunn
Copy link
Owner

I wonder right now why there are separate implementations in the templates for all the standard primitive types and then an "Any" type. The way I see it the main differences should be between Value and Reference types. I wasn't able at a cursory glance at least to find any significant differences between the implementations for the different value types

@Blackclaws - the templates are different for each type because each type can be coerced from a different range of types. For instance, here's the difference between the Dapper template for byte and bool:

image

  • byte can built from short/int/long/string,
  • bool can be built from long/string (should probably add int to that as well 🤔 ) but only when the long is 0 or 1

Similarly for string:

image

  • string only accepts string as input
  • DateOnly can only be built from DateOnly or a string in a particular format

Looking at a non-Dapper template:

image

This has similar logic to the Dapper templates.

The AnyOtherType templates are just a best guess.

@SteveDunn
Copy link
Owner

@Blackclaws - having a look at the template, I think I see what you mean now! The JsonConverter's are all pretty much identical, even between reference and value types.

The difference between Single and String is:

image

So, perhaps the string implementation needs this null check too?

@SteveDunn
Copy link
Owner

I wrote 'Cli version of Vogen' this because Rider has a real pain with Intellisense, and I got tired of restarting rider due to compiler errors because of source generators. If its helpful to add to Vogen, i have no problem with that, i just need to clean up the code a little. I think SourceGenerators are much much better than CLI, but I just got so tried of the "the IComparable<T> must be implemented error" due to Intellisense and Rider.

If its helpful, here is a list of the Newtonsoft implementations by type.

I simply generated one of each type, then copied out the Newtonsoft code. So this the generation result of each type

namespace Aeonic.CodeGen.Vogen.Infra.Stores;

using SharedCommon.Primitives;

public class NewtonsoftJsonConverterStore
{
    // ReSharper disable once InconsistentNaming
    public readonly ICodeGenTemplate Attribute = CodeGenTemplate.With(
        @"[global::Newtonsoft.Json.JsonConverter(typeof({{strongValueObjectName}}NewtonsoftJsonConverter))]");
    
    private const string StringTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                return {{strongValueObjectName}}.Deserialize(serializer.Deserialize<global::System.String>(reader));
            }
        }
";

    private const string BooleanTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var result = serializer.Deserialize<global::System.Boolean?>(reader);
                return result.HasValue ? {{strongValueObjectName}}.Deserialize(result.Value) : null;
            }
        }";

    private const string DateTimeTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var dt = serializer.Deserialize<global::System.DateTime?>(reader);
                return dt.HasValue ? {{strongValueObjectName}}.Deserialize(dt.Value) : null;
            }
        }";

    private const string DateOnlyTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var dt = serializer.Deserialize<global::System.DateOnly?>(reader);
                return dt.HasValue ? {{strongValueObjectName}}.Deserialize(dt.Value) : null;
            }
        }";

    private const string TimeOnlyTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var dt = serializer.Deserialize<global::System.TimeOnly?>(reader);
                return dt.HasValue ? {{strongValueObjectName}}.Deserialize(dt.Value) : null;
            }
        }";

    private const string DateTimeOffsetTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var dt = serializer.Deserialize<global::System.DateTimeOffset?>(reader);
                return dt.HasValue ? {{strongValueObjectName}}.Deserialize(dt.Value) : null;
            }
        }
";

    private const string IntTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var result = serializer.Deserialize<global::System.Int32?>(reader);
                return result.HasValue ? {{strongValueObjectName}}.Deserialize(result.Value) : null;
            }
        }";

    private const string FloatSingleTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var result = serializer.Deserialize<global::System.Single?>(reader);
                return result.HasValue ? {{strongValueObjectName}}.Deserialize(result.Value) : null;
            }
        }";

    private const string DoubleTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var result = serializer.Deserialize<global::System.Double?>(reader);
                return result.HasValue ? {{strongValueObjectName}}.Deserialize(result.Value) : null;
            }
        }
";

    private const string DecimalTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var result = serializer.Deserialize<global::System.Decimal?>(reader);
                return result.HasValue ? {{strongValueObjectName}}.Deserialize(result.Value) : null;
            }
        }
";

    private const string LongTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var result = serializer.Deserialize<global::System.Int64?>(reader);
                return result.HasValue ? {{strongValueObjectName}}.Deserialize(result.Value) : null;
            }
        }
";

    private const string ShortTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var result = serializer.Deserialize<global::System.Int16?>(reader);
                return result.HasValue ? {{strongValueObjectName}}.Deserialize(result.Value) : null;
            }
        }";

    private const string GuidTemplate = @"
        class {{strongValueObjectName}}NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
        {
            public override bool CanConvert(System.Type objectType)
            {
                return objectType == typeof({{strongValueObjectName}});
            }

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
            {
                var id = ({{strongValueObjectName}})value;
                serializer.Serialize(writer, id.Value);
            }

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
            {
                var guid = serializer.Deserialize<System.Guid?>(reader);
                return guid.HasValue ? {{strongValueObjectName}}.Deserialize(guid.Value) : null;
            }
        }";

    public NewtonsoftJsonConverterStore()
    {
        this.Store.Add(PrimitiveTypeEnum.AsString, CodeGenTemplate.With(StringTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsBoolean, CodeGenTemplate.With(BooleanTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsDateTime, CodeGenTemplate.With(DateTimeTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsDateTimeOffset, CodeGenTemplate.With(DateTimeOffsetTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsDateOnly, CodeGenTemplate.With(DateOnlyTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsTimeOnly, CodeGenTemplate.With(TimeOnlyTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsInt, CodeGenTemplate.With(IntTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsFloat, CodeGenTemplate.With(FloatSingleTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsDouble, CodeGenTemplate.With(DoubleTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsDecimal, CodeGenTemplate.With(DecimalTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsGuid, CodeGenTemplate.With(GuidTemplate));
        this.Store.Add(PrimitiveTypeEnum.AsShort, CodeGenTemplate.With(ShortTemplate));
    }

    private Dictionary<PrimitiveTypeEnum, ICodeGenTemplate> Store { get; } = new();

    public ICodeGenTemplate Get(PrimitiveTypeEnum primitiveType)
    {
        return this.Store.First(_ => _.Key == primitiveType)
            .Value;
    }
}

Thank you @jeffward01 - so you use those instead of getting Vogen to generate them?

I'll open a bug report in Rider today and share the link. There are similar problems in ReSharper too.

Plus, aside from source generation, Rider isn't very happy with the Vogen solution. I'm assuming it's down to the thousands of snapshot files (even though I have them as 'do not analyze')

@SteveDunn
Copy link
Owner

Thanks for the PR @Blackclaws , much appreciated! LGTM!

Copy link
Owner

@SteveDunn SteveDunn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@SteveDunn SteveDunn merged commit 9718f4f into SteveDunn:main Jan 30, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Netwonsoft.Json serializer fails when deserializing into nullable ValueObjects
3 participants