@ИгорьОрлов 的答案适用于您的类型只能由 JSON.net 直接实例化(由于[JsonConstructor]
和/或[JsonProperty]
直接在构造函数参数上使用) 。但是,当 JSON.net 已经缓存了要使用的转换器时,覆盖contract.Converter = null
不起作用。
(如果 JSON.NET 使用不可变类型来指示数据和配置何时不再可变,这将不是问题,le sigh )
就我而言,我这样做了:
- 实现了一个自定义
JsonConverter<T>
(T
我的 DTO 的基类在哪里)。
- 定义了一个
DefaultContractResolver
子类,该子类覆盖以仅ResolveContractConverter
返回基类的自定义。JsonConverter
详细并举例:
假设我有这些代表远程文件系统的不可变 DTO(所以有'DirectoryDto
并且FileDto
它们都继承FileSystemDto
,就像如何DirectoryInfo
和FileInfo
派生一样System.IO.FileSystemInfo
):
public enum DtoKind
{
None = 0,
File,
Directory
}
public abstract class FileSystemDto
{
protected FileSystemDto( String name, DtoKind kind )
{
this.Name = name ?? throw new ArgumentNullException(nameof(name));
this.Kind = kind;
}
[JsonProperty( "name" )]
public String Name { get; }
[JsonProperty( "kind" )]
public String Kind { get; }
}
public class FileDto : FileSystemDto
{
[JsonConstructor]
public FileDto(
[JsonProperty("name" )] String name,
[JsonProperty("length")] Int64 length,
[JsonProperty("kind") ] DtoKind kind
)
: base( name: name, kind: kind )
{
if( kind != DtoKind.File ) throw new InvalidOperationException( "blargh" );
this.Length = length;
}
[JsonProperty( "length" )]
public Int64 Length { get; }
}
public class DirectoryDto : FileSystemDto
{
[JsonConstructor]
public FileDto(
[JsonProperty("name")] String name,
[JsonProperty("kind")] DtoKind kind
)
: base( name: name, kind: kind )
{
if( kind != DtoKind.Directory ) throw new InvalidOperationException( "blargh" );
}
}
假设我有一个 JSON 数组FileSystemDto
:
[
{ "name": "foo.txt", "kind": "File", "length": 12345 },
{ "name": "bar.txt", "kind": "File", "length": 12345 },
{ "name": "subdir", "kind": "Directory" },
]
我希望 Json.net 将其反序列化为List<FileSystemDto>
...
所以定义一个子类DefaultContractResolver
(或者如果你已经有一个解析器实现,那么子类(或compose))并覆盖ResolveContractConverter
:
public class MyContractResolver : DefaultContractResolver
{
protected override JsonConverter? ResolveContractConverter( Type objectType )
{
if( objectType == typeof(FileSystemDto) )
{
return MyJsonConverter.Instance;
}
else if( objectType == typeof(FileDto ) )
{
// use default
}
else if( objectType == typeof(DirectoryDto) )
{
// use default
}
return base.ResolveContractConverter( objectType );
}
}
然后实施MyJsonConverter
:
public class MyJsonConverter : JsonConverter<FileSystemDto>
{
public static MyJsonConverter Instance { get; } = new MyJsonConverter();
private MyJsonConverter() {}
// TODO: Override `CanWrite => false` and `WriteJson { throw; }` if you like.
public override FileSystemDto? ReadJson( JsonReader reader, Type objectType, FileSystemDto? existingValue, Boolean hasExistingValue, JsonSerializer serializer )
{
if( reader.TokenType == JsonToken.Null ) return null;
if( objectType == typeof(FileSystemDto) )
{
JObject jsonObject = JObject.Load( reader );
if( jsonObject.Property( "kind" )?.Value is JValue jv && jv.Value is String kind )
{
if( kind == "File" )
{
return jsonObject.ToObject<FileDto>( serializer );
}
else if( kind == "Directory" )
{
return jsonObject.ToObject<DirectoryDto>( serializer );
}
}
}
return null; // or throw, depending on your strictness.
}
}
然后,要反序列化,请使用正确设置的JsonSerializer
实例ContractResolver
,例如:
public static IReadOnlyList<FileSystemDto> DeserializeFileSystemJsonArray( String json )
{
JsonSerializer jss = new JsonSerializer()
{
ContractResolver = new KuduDtoContractResolver()
};
using( StringReader strRdr = new StringReader( json ) )
using( JsonTextReader jsonRdr = new JsonTextReader( strRdr ) )
{
List<FileSystemDto>? list = jss.Deserialize< List<FileSystemDto> >( jsonRdr );
// TODO: Throw if `list` is null.
return list;
}
}