La réponse de @ИгорьОрлов fonctionne lorsque vous avez des types qui ne peuvent être instanciés directement que par JSON.net (en raison de [JsonConstructor]
et/ou de l'utilisation de [JsonProperty]
directement sur les paramètres du constructeur. Cependant, écraser contract.Converter = null
ne fonctionne pas lorsque JSON.net a déjà mis en cache le convertisseur à utiliser.
(Cela ne poserait pas de problème si JSON.NET utilisait des types immuables pour indiquer quand les données et la configuration ne sont plus modifiables, <em>le soupir</em>)
Dans mon cas, j'ai fait ceci:
- Implémenter un
JsonConverter
personnalisé (où T
est la classe de base de mon DTO).
- Définir une sous-classe de
DefaultContractResolver
qui remplace ResolveContractConverter
pour renvoyer mon JsonConverter
personnalisé pour seulement la classe de base.
En détail, et par exemple:
Supposons que j'ai ces DTOs immuables qui représentent un système de fichiers distant (donc il y a DirectoryDto
et FileDto
qui héritent tous deux de FileSystemDto
, tout comme DirectoryInfo
et FileInfo
dérivent de 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" );
}
}
Supposons que j'ai un tableau JSON de FileSystemDto
:
[
{ "name": "foo.txt", "kind": "File", "length": 12345 },
{ "name": "bar.txt", "kind": "File", "length": 12345 },
{ "name": "subdir", "kind": "Directory" },
]
Je veux que Json.net désérialise cela en List
...
Donc, définissez une sous-classe de DefaultContractResolver
(ou si vous avez déjà une implémentation de résolveur, sous-classez (ou composez) cela) et remplacez ResolveContractConverter
:
public class MyContractResolver : DefaultContractResolver
{
protected override JsonConverter? ResolveContractConverter( Type objectType )
{
if( objectType == typeof(FileSystemDto) )
{
return MyJsonConverter.Instance;
}
else if( objectType == typeof(FileDto ) )
{
// utiliser par défaut
}
else if( objectType == typeof(DirectoryDto) )
{
// utiliser par défaut
}
return base.ResolveContractConverter( objectType );
}
}
Ensuite, implémentez MyJsonConverter
:
public class MyJsonConverter : JsonConverter
{
public static MyJsonConverter Instance { get; } = new MyJsonConverter();
private MyJsonConverter() {}
// TODO: Remplacer `CanWrite => false` et `WriteJson { throw; }` si vous le souhaitez.
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( serializer );
}
else if( kind == "Directory" )
{
return jsonObject.ToObject( serializer );
}
}
}
return null; // ou lancer, selon votre strictesse.
}
}
Ensuite, pour désérialiser, utilisez une instance de JsonSerializer
avec le ContractResolver
correctement défini, par exemple:
public static IReadOnlyList DeserializeFileSystemJsonArray( String json )
{
JsonSerializer jss = new JsonSerializer()
{
ContractResolver = new KuduDtoContractResolver()
};
using( StringReader strRdr = new StringReader( json ) )
using( JsonTextReader jsonRdr = new JsonTextReader( strRdr ) )
{
List? list = jss.Deserialize< List >( jsonRdr );
// TODO: Lancer si `list` est null.
return list;
}
}