andrewlock / StronglyTypedId

A Rosyln-powered generator for strongly-typed IDs
MIT License
1.52k stars 80 forks source link

Exception is thrown when deserializing nullable strongly-typed id backed by string #83

Closed hankovich closed 1 year ago

hankovich commented 1 year ago
using Newtonsoft.Json;
using StronglyTypedIds;

var json = JsonConvert.SerializeObject(new User(groupId: null)); // "{\"GroupId\":null}"
var deserialize = JsonConvert.DeserializeObject<User>(json); // <- exception is thrown here

[StronglyTypedId(StronglyTypedIdBackingType.String)]
public partial struct GroupId
{
}

public class User
{
    public User(GroupId? groupId)
    {
        GroupId = groupId;
    }

    public GroupId? GroupId { get; }
}

Version of StronglyTypeId: 1.0.0-beta06 Version of Newtonsoft.Json: 13.0.1

Similar to #36

 System.ArgumentNullException: Value cannot be null. (Parameter 'value')
   at GroupId..ctor(String value) in C:\Users\Hello\source\repos\common\src\Host\StronglyTypedIds\StronglyTypedIds.StronglyTypedIdGenerator\GroupId.g.cs:line 20
   at GroupId.GroupIdNewtonsoftJsonConverter.ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer) in C:\Users\Hello\source\repos\common\src\Host\StronglyTypedIds\StronglyTypedIds.StronglyTypedIdGenerator\GroupId.g.cs:line 109
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.DeserializeConvertable(JsonConverter converter, JsonReader reader, Type objectType, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ResolvePropertyAndCreatorValues(JsonObjectContract contract, JsonProperty containerProperty, JsonReader reader, Type objectType)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObjectUsingCreatorWithParameters(JsonReader reader, JsonObjectContract contract, JsonProperty containerProperty, ObjectConstructor`1 creator, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(JsonReader reader, JsonObjectContract objectContract, JsonProperty containerMember, JsonProperty containerProperty, String id, Boolean& createdFromNonDefaultCreator)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
   at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
   at Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader reader, Type objectType)
   at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
   at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings)
   at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value)
   at Program.<Main>$(String[] args) in C:\Users\Hello\source\repos\common\src\Host\Program.cs:line 5
hankovich commented 1 year ago

For now as a workaround I use the following hand-written converter

[StronglyTypedId(StronglyTypedIdBackingType.String, converters: StronglyTypedIdConverter.TypeConverter)]
[JsonConverter(typeof(GroupIdNewtonsoftJsonConverter))]
public partial struct GroupId
{
    // workaround for https://github.com/andrewlock/StronglyTypedId/issues/83
    private sealed class GroupIdNewtonsoftJsonConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(GroupId);
        }

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

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (objectType == typeof(GroupId?))
            {
                var nullableString = serializer.Deserialize<string?>(reader);

                if (nullableString == null)
                {
                    return null!;
                }

                return new GroupId(nullableString);
            }

            return new GroupId(serializer.Deserialize<string>(reader));
        }
    }
}
andrewlock commented 1 year ago

Thanks for flagging this @hankovich, fixed in #94 using your suggestion 👍

hankovich commented 1 year ago

@andrewlock are there any plans to release a new version that includes this fix?