使用 System.Text.Json 序列化实现接口的对象
Serialize objects implementing interface with System.Text.Json
我有一个母版 class,其中包含一个通用集合。集合中的元素有不同的类型,每个元素都实现了一个接口。
硕士class:
public class MasterClass
{
public ICollection<IElement> ElementCollection { get; set; }
}
合同要素:
public interface IElement
{
string Key { get; set; }
}
元素的两个示例:
public class ElementA : IElement
{
public string Key { get; set; }
public string AValue { get; set; }
}
public class ElementB : IElement
{
public string Key { get; set; }
public string BValue { get; set; }
}
我需要使用 Json 中的新 System.Text.Json
库序列化 MasterClass
对象的一个实例。使用以下代码,
public string Serialize(MasterClass masterClass)
{
var options = new JsonSerializerOptions
{
WriteIndented = true,
};
return JsonSerializer.Serialize(masterClass, options);
}
我得到以下 JSON:
{
"ElementCollection":
[
{
"Key": "myElementAKey1"
},
{
"Key": "myElementAKey2"
},
{
"Key": "myElementBKey1"
}
]
}
而不是:
{
"ElementCollection":
[
{
"Key": "myElementAKey1",
"AValue": "MyValueA-1"
},
{
"Key": "myElementAKey2",
"AValue": "MyValueA-2"
},
{
"Key": "myElementBKey1",
"AValue": "MyValueB-1"
}
]
}
我应该实施哪个 class(转换器、写入器...)以获得完整的 JSON?
在此先感谢您的帮助。
解决方案是实现通用转换器 (System.Text.Json.Serialization.JsonConverter
) :
public class ElementConverter : JsonConverter<IElement>
{
public override IElement Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
public override void Write(Utf8JsonWriter writer, IElement value, JsonSerializerOptions options)
{
if (value is ElementA)
JsonSerializer.Serialize(writer, value as ElementA, typeof(ElementA), options);
else if (value is ElementB)
JsonSerializer.Serialize(writer, value as ElementB, typeof(ElementB), options);
else
throw new ArgumentOutOfRangeException(nameof(value), $"Unknown implementation of the interface {nameof(IElement)} for the parameter {nameof(value)}. Unknown implementation: {value?.GetType().Name}");
}
}
这只需要对 Read
方法做更多的工作。
这对我有用:
public class TypeMappingConverter<TType, TImplementation> : JsonConverter<TType>
where TImplementation : TType
{
[return: MaybeNull]
public override TType Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
JsonSerializer.Deserialize<TImplementation>(ref reader, options);
public override void Write(
Utf8JsonWriter writer, TType value, JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, (TImplementation)value!, options);
}
用法:
var options =
new JsonSerializerOptions
{
Converters =
{
new TypeMappingConverter<BaseType, ImplementationType>()
}
};
JsonSerializer.Deserialize<Wrapper>(value, options);
测试:
[Fact]
public void Should_serialize_references()
{
// arrange
var inputEntity = new Entity
{
References =
{
new Reference
{
MyProperty = "abcd"
},
new Reference
{
MyProperty = "abcd"
}
}
};
var options = new JsonSerializerOptions
{
WriteIndented = true,
Converters =
{
new TypeMappingConverter<IReference, Reference>()
}
};
var expectedOutput =
@"{
""References"": [
{
""MyProperty"": ""abcd""
},
{
""MyProperty"": ""abcd""
}
]
}";
// act
var actualOutput = JsonSerializer.Serialize(inputEntity, options);
// assert
Assert.Equal(expectedOutput, actualOutput);
}
[Fact]
public void Should_deserialize_references()
{
// arrange
var inputJson =
@"{
""References"": [
{
""MyProperty"": ""abcd""
},
{
""MyProperty"": ""abcd""
}
]
}";
var expectedOutput = new Entity
{
References =
{
new Reference
{
MyProperty = "abcd"
},
new Reference
{
MyProperty = "abcd"
}
}
};
var options = new JsonSerializerOptions
{
WriteIndented = true
};
options.Converters.AddTypeMapping<IReference, Reference>();
// act
var actualOutput = JsonSerializer.Deserialize<Entity>(inputJson, options);
// assert
actualOutput
.Should()
.BeEquivalentTo(expectedOutput);
}
public class Entity
{
HashSet<IReference>? _References;
public ICollection<IReference> References
{
get => _References ??= new HashSet<IReference>();
set => _References = value?.ToHashSet();
}
}
public interface IReference
{
public string? MyProperty { get; set; }
}
public class Reference : IReference
{
public string? MyProperty { get; set; }
}
您要查找的内容称为多态序列化。
Here's Microsoft documentation article
根据文档,您只需将界面转换为对象即可。
例如:
public class TreeRow
{
[JsonIgnore]
public ICell[] Groups { get; set; } = new ICell[0];
[JsonIgnore]
public ICell[] Aggregates { get; set; } = new ICell[0];
[JsonPropertyName("Groups")]
public object[] JsonGroups => Groups;
[JsonPropertyName("Aggregates")]
public object[] JsonAggregates => Aggregates;
public TreeRow[] Children { get; set; } = new TreeRow[0];
}
我遇到过同样的问题,但我的问题可能与你的问题无关。事实证明,传入的 JSON 数据必须序列化到的每个对象都需要一个不带参数的构造函数。我所有的对象都有带有所有参数的构造函数(以便更容易地从数据库创建和填充它们)。
我目前在 Blazor 应用程序中遇到了同样的问题,所以我无法轻松切换到 Newtonsoft.Json
。我找到了两种方法。一个是现实黑客。您可以创建自定义转换器,在 Read/Write 方法中使用 Newtonsoft.Json
,而不是 System.Text.Json
。但这不是我想要的。所以我制作了一些自定义接口转换器。我有一些可行的解决方案,尚未经过广泛测试,但它可以满足我的需要。
情况
我有一个 List<TInterface>
对象实现 TInterface
。但是有很多不同的实现。我需要在服务器上序列化数据,并在客户端 WASM 应用程序上反序列化所有数据。对于JavaScript反序列化,后面提到的自定义Write方法的实现就足够了。对于 C# 中的反序列化,我需要知道为列表中的每个项目序列化的对象的确切类型。
首先,我需要JsonConverterAttribute
界面。所以我关注了这篇文章:https://khalidabuhakmeh.com/serialize-interface-instances-system-text-json。 Writer
的一些实现将处理接口类型。但是没有 Read
实现。所以我不得不自己做。
如何
- 修改
Write
方法以将对象类型作为第一个 属性 写入 JSON 对象。使用 JsonDocument 从原始对象获取所有属性。
- 在阅读 JSON 时,使用克隆 reader(如 Microsoft docs 中针对自定义 json 转换器的建议)找到第一个 属性 命名为
$type
带有类型信息。比创建该类型的实例并使用类型从原始 reader. 反序列化数据
代码
接口和类:
[JsonInterfaceConverter(typeof(InterfaceConverter<ITest>))]
public interface ITest
{
int Id { get; set; }
string Name { get; set; }
}
public class ImageTest : ITest
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Image { get; set; } = string.Empty;
}
public class TextTest : ITest
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Text { get; set; } = string.Empty;
public bool IsEnabled { get; set; }
}
接口转换器属性:
// Source: https://khalidabuhakmeh.com/serialize-interface-instances-system-text-json
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false)]
public class JsonInterfaceConverterAttribute : JsonConverterAttribute
{
public JsonInterfaceConverterAttribute(Type converterType)
: base(converterType)
{
}
}
转换器:
public class InterfaceConverter<T> : JsonConverter<T>
where T : class
{
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
Utf8JsonReader readerClone = reader;
if (readerClone.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}
readerClone.Read();
if (readerClone.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}
string propertyName = readerClone.GetString();
if (propertyName != "$type")
{
throw new JsonException();
}
readerClone.Read();
if (readerClone.TokenType != JsonTokenType.String)
{
throw new JsonException();
}
string typeValue = readerClone.GetString();
var instance = Activator.CreateInstance(Assembly.GetExecutingAssembly().FullName, typeValue).Unwrap();
var entityType = instance.GetType();
var deserialized = JsonSerializer.Deserialize(ref reader, entityType, options);
return (T)deserialized;
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
switch (value)
{
case null:
JsonSerializer.Serialize(writer, (T)null, options);
break;
default:
{
var type = value.GetType();
using var jsonDocument = JsonDocument.Parse(JsonSerializer.Serialize(value, type, options));
writer.WriteStartObject();
writer.WriteString("$type", type.FullName);
foreach (var element in jsonDocument.RootElement.EnumerateObject())
{
element.WriteTo(writer);
}
writer.WriteEndObject();
break;
}
}
}
}
用法:
var list = new List<ITest>
{
new ImageTest { Id = 1, Name = "Image test", Image = "some.url.here" },
new TextTest { Id = 2, Name = "Text test", Text = "kasdglaskhdgl aksjdgl asd gasdg", IsEnabled = true },
new TextTest { Id = 3, Name = "Text test 2", Text = "asd gasdg", IsEnabled = false },
new ImageTest { Id = 4, Name = "Second image", Image = "diff.url.here" }
};
var json = JsonSerializer.Serialize(list);
var data = JsonSerializer.Deserialize<List<ITest>>(json);
// JSON data
// [
// {
// "$type":"ConsoleApp1.ImageTest",
// "Id":1,
// "Name":"Image test",
// "Image":"some.url.here"
// },
// {
// "$type":"ConsoleApp1.TextTest",
// "Id":2,
// "Name":"Text test",
// "Text":"kasdglaskhdgl aksjdgl asd gasdg",
// "IsEnabled":true
// },
// {
// "$type":"ConsoleApp1.TextTest",
// "Id":3,
// "Name":"Text test 2",
// "Text":"asd gasdg",
// "IsEnabled":false
// },
// {
// "$type":"ConsoleApp1.ImageTest",
// "Id":4,
// "Name":"Second image",
// "Image":"diff.url.here"
// }
// ]
编辑:
我用这个逻辑做了一个 NuGet 包。您可以在这里下载:InterfaceConverter.SystemTextJson
编辑 26.3.2022:
NuGet 包版本实现了更多逻辑,例如。在所有引用的程序集中查找类型。
改进@t00thy 解决方案
您的解决方案很好,但是如果在其他程序集中使用具体类型怎么办?
转换器Class
public class InterfaceConverter<T> : JsonConverter<T> where T : class
{
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
Utf8JsonReader readerClone = reader;
if (readerClone.TokenType != JsonTokenType.StartObject)
throw new JsonException("Problem in Start object! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
readerClone.Read();
if (readerClone.TokenType != JsonTokenType.PropertyName)
throw new JsonException("Token Type not equal to property name! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
string? propertyName = readerClone.GetString();
if (string.IsNullOrWhiteSpace(propertyName) || propertyName != "$type")
throw new JsonException("Unable to get $type! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
readerClone.Read();
if (readerClone.TokenType != JsonTokenType.String)
throw new JsonException("Token Type is not JsonTokenString! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
string? typeValue = readerClone.GetString();
if(string.IsNullOrWhiteSpace(typeValue))
throw new JsonException("typeValue is null or empty string! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
string? asmbFullName = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(ass => !string.IsNullOrEmpty(ass.GetName().Name) && ass.GetName().Name.Equals(typeValue.Split(" ")[1]))?.FullName;
if (string.IsNullOrWhiteSpace(asmbFullName))
throw new JsonException("Assembly name is null or empty string! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
ObjectHandle? instance = Activator.CreateInstance(asmbFullName, typeValue.Split(" ")[0]);
if(instance == null)
throw new JsonException("Unable to create object handler! Handler is null! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
object? unwrapedInstance = instance.Unwrap();
if(unwrapedInstance == null)
throw new JsonException("Unable to unwrap instance! Or instance is null! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
Type? entityType = unwrapedInstance.GetType();
if(entityType == null)
throw new JsonException("Instance type is null! Or instance is null! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
object? deserialized = JsonSerializer.Deserialize(ref reader, entityType, options);
if(deserialized == null)
throw new JsonException("De-Serialized object is null here!");
return (T)deserialized;
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
switch (value)
{
case null:
JsonSerializer.Serialize(writer, typeof(T) ,options);
break;
default:
{
var type = value.GetType();
using var jsonDocument = JsonDocument.Parse(JsonSerializer.Serialize(value, type, options));
writer.WriteStartObject();
writer.WriteString("$type", type.FullName + " " + type.Assembly.GetName().Name);
foreach (var element in jsonDocument.RootElement.EnumerateObject())
{
element.WriteTo(writer);
}
writer.WriteEndObject();
break;
}
}
}
}
转换器属性
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false)]
public class JsonInterfaceConverterAttribute : JsonConverterAttribute
{
public JsonInterfaceConverterAttribute(Type converterType)
: base(converterType)
{
}
}
接口和Classes
[JsonInterfaceConverter(typeof(InterfaceConverter<IUser>))]
public interface IUser
{
int Id { get; set; }
string Name { get; set; }
IEnumerable<IRight> Rights { get; set; }
}
[JsonInterfaceConverter(typeof(InterfaceConverter<IRight>))]
public interface IRight
{
int Id { get; set; }
bool HasRight { get; set; }
}
public class User : IUser
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public IEnumerable<IRight> Rights { get; set; } = Enumerable.Empty<IRight>();
}
public class Right : IRight
{
public int Id { get; set; }
public bool HasRight { get; set; }
}
用法:
// your dependency injector
IUser user = IServiceProvider.GetRequiredService<IUser>();
user.Id = 1;
user.Name = "Xyz";
List<IRight> rights = new ();
// your dependency injector
IRight right1 = IServiceProvider.GetRequiredService<IRight>();
right1.Id = 1;
right1.HasRight = true;
rights.Add(right1);
// your dependency injector
IRight right2 = IServiceProvider.GetRequiredService<IRight>();
right2.Id = 2;
right2.HasRight = true;
rights.Add(right2);
// your dependency injector
IRight right3 = IServiceProvider.GetRequiredService<IRight>();
right3.Id = 1;
right3.HasRight = true;
rights.Add(right2);
var serializedRights = JsonSerializer.Serialize(rights);
user.Rights = rights;
// Serialization is simple
var serilizedUser = JsonSerializer.Serialize(user);
//But for DeSerialization of single object you need to use it some thing like this
// Ask your dependency injector to resolve and get type of object
IUser usr = JsonSerializer.Deserialize(serilizedUser, IServiceProvider.GetRequiredService<IUser>().GetType());
//DeSerialization of list or enumerable is simple
IEnumerable<IRight>? rits = JsonSerializer.Deserialize<IEnumerable<IRight>>(serializedRights);
我有一个母版 class,其中包含一个通用集合。集合中的元素有不同的类型,每个元素都实现了一个接口。
硕士class:
public class MasterClass
{
public ICollection<IElement> ElementCollection { get; set; }
}
合同要素:
public interface IElement
{
string Key { get; set; }
}
元素的两个示例:
public class ElementA : IElement
{
public string Key { get; set; }
public string AValue { get; set; }
}
public class ElementB : IElement
{
public string Key { get; set; }
public string BValue { get; set; }
}
我需要使用 Json 中的新 System.Text.Json
库序列化 MasterClass
对象的一个实例。使用以下代码,
public string Serialize(MasterClass masterClass)
{
var options = new JsonSerializerOptions
{
WriteIndented = true,
};
return JsonSerializer.Serialize(masterClass, options);
}
我得到以下 JSON:
{
"ElementCollection":
[
{
"Key": "myElementAKey1"
},
{
"Key": "myElementAKey2"
},
{
"Key": "myElementBKey1"
}
]
}
而不是:
{
"ElementCollection":
[
{
"Key": "myElementAKey1",
"AValue": "MyValueA-1"
},
{
"Key": "myElementAKey2",
"AValue": "MyValueA-2"
},
{
"Key": "myElementBKey1",
"AValue": "MyValueB-1"
}
]
}
我应该实施哪个 class(转换器、写入器...)以获得完整的 JSON?
在此先感谢您的帮助。
解决方案是实现通用转换器 (System.Text.Json.Serialization.JsonConverter
) :
public class ElementConverter : JsonConverter<IElement>
{
public override IElement Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
public override void Write(Utf8JsonWriter writer, IElement value, JsonSerializerOptions options)
{
if (value is ElementA)
JsonSerializer.Serialize(writer, value as ElementA, typeof(ElementA), options);
else if (value is ElementB)
JsonSerializer.Serialize(writer, value as ElementB, typeof(ElementB), options);
else
throw new ArgumentOutOfRangeException(nameof(value), $"Unknown implementation of the interface {nameof(IElement)} for the parameter {nameof(value)}. Unknown implementation: {value?.GetType().Name}");
}
}
这只需要对 Read
方法做更多的工作。
这对我有用:
public class TypeMappingConverter<TType, TImplementation> : JsonConverter<TType>
where TImplementation : TType
{
[return: MaybeNull]
public override TType Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
JsonSerializer.Deserialize<TImplementation>(ref reader, options);
public override void Write(
Utf8JsonWriter writer, TType value, JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, (TImplementation)value!, options);
}
用法:
var options =
new JsonSerializerOptions
{
Converters =
{
new TypeMappingConverter<BaseType, ImplementationType>()
}
};
JsonSerializer.Deserialize<Wrapper>(value, options);
测试:
[Fact]
public void Should_serialize_references()
{
// arrange
var inputEntity = new Entity
{
References =
{
new Reference
{
MyProperty = "abcd"
},
new Reference
{
MyProperty = "abcd"
}
}
};
var options = new JsonSerializerOptions
{
WriteIndented = true,
Converters =
{
new TypeMappingConverter<IReference, Reference>()
}
};
var expectedOutput =
@"{
""References"": [
{
""MyProperty"": ""abcd""
},
{
""MyProperty"": ""abcd""
}
]
}";
// act
var actualOutput = JsonSerializer.Serialize(inputEntity, options);
// assert
Assert.Equal(expectedOutput, actualOutput);
}
[Fact]
public void Should_deserialize_references()
{
// arrange
var inputJson =
@"{
""References"": [
{
""MyProperty"": ""abcd""
},
{
""MyProperty"": ""abcd""
}
]
}";
var expectedOutput = new Entity
{
References =
{
new Reference
{
MyProperty = "abcd"
},
new Reference
{
MyProperty = "abcd"
}
}
};
var options = new JsonSerializerOptions
{
WriteIndented = true
};
options.Converters.AddTypeMapping<IReference, Reference>();
// act
var actualOutput = JsonSerializer.Deserialize<Entity>(inputJson, options);
// assert
actualOutput
.Should()
.BeEquivalentTo(expectedOutput);
}
public class Entity
{
HashSet<IReference>? _References;
public ICollection<IReference> References
{
get => _References ??= new HashSet<IReference>();
set => _References = value?.ToHashSet();
}
}
public interface IReference
{
public string? MyProperty { get; set; }
}
public class Reference : IReference
{
public string? MyProperty { get; set; }
}
您要查找的内容称为多态序列化。
Here's Microsoft documentation article
根据文档,您只需将界面转换为对象即可。 例如:
public class TreeRow
{
[JsonIgnore]
public ICell[] Groups { get; set; } = new ICell[0];
[JsonIgnore]
public ICell[] Aggregates { get; set; } = new ICell[0];
[JsonPropertyName("Groups")]
public object[] JsonGroups => Groups;
[JsonPropertyName("Aggregates")]
public object[] JsonAggregates => Aggregates;
public TreeRow[] Children { get; set; } = new TreeRow[0];
}
我遇到过同样的问题,但我的问题可能与你的问题无关。事实证明,传入的 JSON 数据必须序列化到的每个对象都需要一个不带参数的构造函数。我所有的对象都有带有所有参数的构造函数(以便更容易地从数据库创建和填充它们)。
我目前在 Blazor 应用程序中遇到了同样的问题,所以我无法轻松切换到 Newtonsoft.Json
。我找到了两种方法。一个是现实黑客。您可以创建自定义转换器,在 Read/Write 方法中使用 Newtonsoft.Json
,而不是 System.Text.Json
。但这不是我想要的。所以我制作了一些自定义接口转换器。我有一些可行的解决方案,尚未经过广泛测试,但它可以满足我的需要。
情况
我有一个 List<TInterface>
对象实现 TInterface
。但是有很多不同的实现。我需要在服务器上序列化数据,并在客户端 WASM 应用程序上反序列化所有数据。对于JavaScript反序列化,后面提到的自定义Write方法的实现就足够了。对于 C# 中的反序列化,我需要知道为列表中的每个项目序列化的对象的确切类型。
首先,我需要JsonConverterAttribute
界面。所以我关注了这篇文章:https://khalidabuhakmeh.com/serialize-interface-instances-system-text-json。 Writer
的一些实现将处理接口类型。但是没有 Read
实现。所以我不得不自己做。
如何
- 修改
Write
方法以将对象类型作为第一个 属性 写入 JSON 对象。使用 JsonDocument 从原始对象获取所有属性。 - 在阅读 JSON 时,使用克隆 reader(如 Microsoft docs 中针对自定义 json 转换器的建议)找到第一个 属性 命名为
$type
带有类型信息。比创建该类型的实例并使用类型从原始 reader. 反序列化数据
代码
接口和类:
[JsonInterfaceConverter(typeof(InterfaceConverter<ITest>))]
public interface ITest
{
int Id { get; set; }
string Name { get; set; }
}
public class ImageTest : ITest
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Image { get; set; } = string.Empty;
}
public class TextTest : ITest
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Text { get; set; } = string.Empty;
public bool IsEnabled { get; set; }
}
接口转换器属性:
// Source: https://khalidabuhakmeh.com/serialize-interface-instances-system-text-json
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false)]
public class JsonInterfaceConverterAttribute : JsonConverterAttribute
{
public JsonInterfaceConverterAttribute(Type converterType)
: base(converterType)
{
}
}
转换器:
public class InterfaceConverter<T> : JsonConverter<T>
where T : class
{
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
Utf8JsonReader readerClone = reader;
if (readerClone.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}
readerClone.Read();
if (readerClone.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}
string propertyName = readerClone.GetString();
if (propertyName != "$type")
{
throw new JsonException();
}
readerClone.Read();
if (readerClone.TokenType != JsonTokenType.String)
{
throw new JsonException();
}
string typeValue = readerClone.GetString();
var instance = Activator.CreateInstance(Assembly.GetExecutingAssembly().FullName, typeValue).Unwrap();
var entityType = instance.GetType();
var deserialized = JsonSerializer.Deserialize(ref reader, entityType, options);
return (T)deserialized;
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
switch (value)
{
case null:
JsonSerializer.Serialize(writer, (T)null, options);
break;
default:
{
var type = value.GetType();
using var jsonDocument = JsonDocument.Parse(JsonSerializer.Serialize(value, type, options));
writer.WriteStartObject();
writer.WriteString("$type", type.FullName);
foreach (var element in jsonDocument.RootElement.EnumerateObject())
{
element.WriteTo(writer);
}
writer.WriteEndObject();
break;
}
}
}
}
用法:
var list = new List<ITest>
{
new ImageTest { Id = 1, Name = "Image test", Image = "some.url.here" },
new TextTest { Id = 2, Name = "Text test", Text = "kasdglaskhdgl aksjdgl asd gasdg", IsEnabled = true },
new TextTest { Id = 3, Name = "Text test 2", Text = "asd gasdg", IsEnabled = false },
new ImageTest { Id = 4, Name = "Second image", Image = "diff.url.here" }
};
var json = JsonSerializer.Serialize(list);
var data = JsonSerializer.Deserialize<List<ITest>>(json);
// JSON data
// [
// {
// "$type":"ConsoleApp1.ImageTest",
// "Id":1,
// "Name":"Image test",
// "Image":"some.url.here"
// },
// {
// "$type":"ConsoleApp1.TextTest",
// "Id":2,
// "Name":"Text test",
// "Text":"kasdglaskhdgl aksjdgl asd gasdg",
// "IsEnabled":true
// },
// {
// "$type":"ConsoleApp1.TextTest",
// "Id":3,
// "Name":"Text test 2",
// "Text":"asd gasdg",
// "IsEnabled":false
// },
// {
// "$type":"ConsoleApp1.ImageTest",
// "Id":4,
// "Name":"Second image",
// "Image":"diff.url.here"
// }
// ]
编辑: 我用这个逻辑做了一个 NuGet 包。您可以在这里下载:InterfaceConverter.SystemTextJson
编辑 26.3.2022: NuGet 包版本实现了更多逻辑,例如。在所有引用的程序集中查找类型。
改进@t00thy 解决方案
您的解决方案很好,但是如果在其他程序集中使用具体类型怎么办?
转换器Class
public class InterfaceConverter<T> : JsonConverter<T> where T : class
{
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
Utf8JsonReader readerClone = reader;
if (readerClone.TokenType != JsonTokenType.StartObject)
throw new JsonException("Problem in Start object! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
readerClone.Read();
if (readerClone.TokenType != JsonTokenType.PropertyName)
throw new JsonException("Token Type not equal to property name! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
string? propertyName = readerClone.GetString();
if (string.IsNullOrWhiteSpace(propertyName) || propertyName != "$type")
throw new JsonException("Unable to get $type! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
readerClone.Read();
if (readerClone.TokenType != JsonTokenType.String)
throw new JsonException("Token Type is not JsonTokenString! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
string? typeValue = readerClone.GetString();
if(string.IsNullOrWhiteSpace(typeValue))
throw new JsonException("typeValue is null or empty string! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
string? asmbFullName = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(ass => !string.IsNullOrEmpty(ass.GetName().Name) && ass.GetName().Name.Equals(typeValue.Split(" ")[1]))?.FullName;
if (string.IsNullOrWhiteSpace(asmbFullName))
throw new JsonException("Assembly name is null or empty string! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
ObjectHandle? instance = Activator.CreateInstance(asmbFullName, typeValue.Split(" ")[0]);
if(instance == null)
throw new JsonException("Unable to create object handler! Handler is null! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
object? unwrapedInstance = instance.Unwrap();
if(unwrapedInstance == null)
throw new JsonException("Unable to unwrap instance! Or instance is null! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
Type? entityType = unwrapedInstance.GetType();
if(entityType == null)
throw new JsonException("Instance type is null! Or instance is null! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
object? deserialized = JsonSerializer.Deserialize(ref reader, entityType, options);
if(deserialized == null)
throw new JsonException("De-Serialized object is null here!");
return (T)deserialized;
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
switch (value)
{
case null:
JsonSerializer.Serialize(writer, typeof(T) ,options);
break;
default:
{
var type = value.GetType();
using var jsonDocument = JsonDocument.Parse(JsonSerializer.Serialize(value, type, options));
writer.WriteStartObject();
writer.WriteString("$type", type.FullName + " " + type.Assembly.GetName().Name);
foreach (var element in jsonDocument.RootElement.EnumerateObject())
{
element.WriteTo(writer);
}
writer.WriteEndObject();
break;
}
}
}
}
转换器属性
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false)]
public class JsonInterfaceConverterAttribute : JsonConverterAttribute
{
public JsonInterfaceConverterAttribute(Type converterType)
: base(converterType)
{
}
}
接口和Classes
[JsonInterfaceConverter(typeof(InterfaceConverter<IUser>))]
public interface IUser
{
int Id { get; set; }
string Name { get; set; }
IEnumerable<IRight> Rights { get; set; }
}
[JsonInterfaceConverter(typeof(InterfaceConverter<IRight>))]
public interface IRight
{
int Id { get; set; }
bool HasRight { get; set; }
}
public class User : IUser
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public IEnumerable<IRight> Rights { get; set; } = Enumerable.Empty<IRight>();
}
public class Right : IRight
{
public int Id { get; set; }
public bool HasRight { get; set; }
}
用法:
// your dependency injector
IUser user = IServiceProvider.GetRequiredService<IUser>();
user.Id = 1;
user.Name = "Xyz";
List<IRight> rights = new ();
// your dependency injector
IRight right1 = IServiceProvider.GetRequiredService<IRight>();
right1.Id = 1;
right1.HasRight = true;
rights.Add(right1);
// your dependency injector
IRight right2 = IServiceProvider.GetRequiredService<IRight>();
right2.Id = 2;
right2.HasRight = true;
rights.Add(right2);
// your dependency injector
IRight right3 = IServiceProvider.GetRequiredService<IRight>();
right3.Id = 1;
right3.HasRight = true;
rights.Add(right2);
var serializedRights = JsonSerializer.Serialize(rights);
user.Rights = rights;
// Serialization is simple
var serilizedUser = JsonSerializer.Serialize(user);
//But for DeSerialization of single object you need to use it some thing like this
// Ask your dependency injector to resolve and get type of object
IUser usr = JsonSerializer.Deserialize(serilizedUser, IServiceProvider.GetRequiredService<IUser>().GetType());
//DeSerialization of list or enumerable is simple
IEnumerable<IRight>? rits = JsonSerializer.Deserialize<IEnumerable<IRight>>(serializedRights);