Предположим, что приведена ниже модель:
public enum TypeAModels
{
A = 1,
B = 2
}
[JsonDerivedType(typeof(TypeA_A), "aa")]
[JsonDerivedType(typeof(TypeA_B), "ab")]
public abstract class TypeA
{
public abstract TypeAModels TypeAModels { get; }
}
public class TypeA_A : TypeA
{
public int Age { get; set; }
public TypeB TypeB { get; set; }
public override TypeAModels TypeAModels => TypeAModels.A;
}
public class TypeA_B : TypeA
{
public string Name { get; set; }
public override TypeAModels TypeAModels => TypeAModels.B;
}
public enum TypeBModels
{
A = 1,
B = 2
}
[JsonDerivedType(typeof(TypeB_A), "ba")]
[JsonDerivedType(typeof(TypeB_B), "bb")]
public abstract class TypeB
{
public abstract TypeBModels TypeBModels { get; }
}
public class TypeB_A : TypeB
{
public int Year { get; set; }
public override TypeBModels TypeBModels => TypeBModels.A;
}
public class TypeB_B : TypeB
{
public string UserName { get; set; }
public override TypeBModels TypeBModels => TypeBModels.B;
}
//create a TypeA object of type TypeA_A
TypeA typeAA = new TypeA_A() { Age = 30, TypeB = new TypeB_A { Year = 1982 } };
var jsonString = JsonSerializer.Serialize(typeAA, new JsonSerializerOptions());
/*{
"$type":"aa",
"Age":30,
"TypeB":{"$type":"ba",
"Year":1982,
"TypeBModels":1},
"TypeAModels":1}*/
var deserializedTypeAA = JsonSerializer.Deserialize<TypeA>(jsonString, new JsonSerializerOptions());
Когда я запускаю код JsonSerializer.Serialize
, я получаю ожидаемый JSON, а когда я запускаю код JsonSerializer.Deserialize<TypeA>
, ему также удается создать правильный объект из JSON.
Проблема начинается, когда у меня есть конечная точка MinimalApi, например:
app.MapPost("/", async (TypeA typeA)
.
Поскольку клиентские запросы основаны на модели, которая не содержит дискриминатора типа, необходимого для сериализации JSON, чтобы знать, как ее десериализовать. Например, клиентский JSON может выглядеть так:
{
"Age": 30,
"TypeB": {
"Year": 1982,
"TypeBModels": 1
},
"TypeAModels": 1
}
Для таких запросов сериализация JSON завершается с ошибкой с общим исключением: System.NotSupportedException: 'Deserialization of types without a parameterless constructor, a singular parameterized...
Единственный способ, который я могу придумать, - это прочитать и проанализировать «вручную» каждое поле JSON отдельно. Это возможно, но это не идеальный способ, ИМХО. Есть ли более элегантный способ?
Примечание. Я нашел это API-предложение, которое может стать хорошим решением, когда оно будет реализовано, но в настоящее время оно еще не реализовано.
@dbc Спасибо за ваш комментарий! Я постарался прояснить, что мне нужно, пожалуйста, взгляните на измененный вопрос. Я видел ссылки, прежде чем спрашивать, но не нашел нужного решения. Спасибо!
Есть ли у вас муравьиный контроль над сериализованным форматом JSON? Встроенная поддержка полиморфизма в System.Text.Json требует, чтобы дискриминатор типа был первым свойством объекта, а ваше, "TypeAModels": 1
, — последним. Можно ли это изменить?
Пока ваши производные типы не запечатаны, вы можете применить к ним [JsonDerivedType(typeof(TSelf))], чтобы System.Text.Json выдавал самоидентификатор при сериализации. Таким образом, если я изменю ваши модели, например. следующее:
public enum TypeAModels
{
A = 1,
B = 2
}
[JsonPolymorphic(TypeDiscriminatorPropertyName = MyTypeDiscriminatorPropertyName)]
[JsonDerivedType(typeof(TypeA_A), (int)TypeAModels.A)]
[JsonDerivedType(typeof(TypeA_B), (int)TypeAModels.B)]
public abstract class TypeA
{
protected const string MyTypeDiscriminatorPropertyName = "TypeAModels";
}
[JsonPolymorphic(TypeDiscriminatorPropertyName = MyTypeDiscriminatorPropertyName)]
[JsonDerivedType(typeof(TypeA_A), (int)TypeAModels.A)] // Self identifier
public class TypeA_A : TypeA
{
public int Age { get; set; }
public TypeB TypeB { get; set; }
}
[JsonPolymorphic(TypeDiscriminatorPropertyName = MyTypeDiscriminatorPropertyName)]
[JsonDerivedType(typeof(TypeA_B), (int)TypeAModels.B)] // Self identifier
public class TypeA_B : TypeA
{
public string Name { get; set; }
}
public enum TypeBModels
{
A = 1,
B = 2
}
[JsonPolymorphic(TypeDiscriminatorPropertyName = MyTypeDiscriminatorPropertyName)]
[JsonDerivedType(typeof(TypeB_A), (int)TypeBModels.A)]
[JsonDerivedType(typeof(TypeB_B), (int)TypeBModels.B)]
public abstract class TypeB
{
protected const string MyTypeDiscriminatorPropertyName = "TypeBModels";
}
[JsonPolymorphic(TypeDiscriminatorPropertyName = MyTypeDiscriminatorPropertyName)]
[JsonDerivedType(typeof(TypeB_A), (int)TypeBModels.A)] // Self identifier
public class TypeB_A : TypeB
{
public int Year { get; set; }
}
[JsonPolymorphic(TypeDiscriminatorPropertyName = MyTypeDiscriminatorPropertyName)]
[JsonDerivedType(typeof(TypeB_B), (int)TypeBModels.B)] // Self identifier
public class TypeB_B : TypeB
{
public string UserName { get; set; }
}
Теперь я могу сериализовать экземпляр TypeA_A
и десериализовать как TypeA
:
TypeA_A typeAA = new() { Age = 30, TypeB = new TypeB_A { Year = 1982 } };
var json = JsonSerializer.Serialize(typeAA, options);
var typeABack = JsonSerializer.Deserialize<TypeA>(json);
Примечания:
Я исключил ваши свойства TypeAModels
и TypeBModels
в пользу использования JsonPolymorphicAttribute.TypeDiscriminatorPropertyName. Это предотвращает дублирование информации о типе в сериализованном JSON, но требует, чтобы дискриминатор типа появился первым в JSON:
{
"TypeAModels": 1, // This must be the first property
"Age": 30,
"TypeB": {
"TypeBModels": 1, // This must be the first property
"Year": 1982
}
}
Microsoft отказывается разрешать отправку информации о дискриминаторе типов при сериализации запечатанного типа. См. System.Text.Json Polymorphic Type Resolve Issue #77532 , который был закрыт MSFT как «ответ», и [Предложение API]: Полиморфный атрибут System.Text.Json должен предоставлять возможность включать дискриминаторы типов в производные Types #93471, который был открыт специально для сериализации информации о типе для запечатанных производных типов.
Если ваши производные типы запечатаны, вам может потребоваться принять совершенно другую стратегию, например, использовать ручной преобразователь, подобный одному из Возможна ли полиморфная десериализация в System.Text.Json? или переписать конечную точку для явной сериализации с использованием базового типа, а не конкретного типа.
Если вы не хотите вручную применять параметры полиморфизма к производным типам, вы можете создать собственный модификатор typeinfo для копирования параметров полиморфизма из контрактов базового типа в контракты производного типа. См., например. этот ответ от Гуру Строна на Как я могу сериализовать многоуровневую иерархию полиморфных типов с помощью System.Text.Json в .NET 7?для одного примера. Возможно, его необходимо улучшить, например. чтобы скопировать имя дискриминатора типа.
Демо-рабочий пример здесь.
Это очень хороший способ решить проблему дискриминатора типов. Недостатком здесь является то, что: 1. Клиент должен сохранять порядок полей (что не является стандартом). 2. Модель не отражает структуру JSON. Я имею в виду, что клиент должен указать TypeAModels
, но модель этого не отражает (это защищенное поле). Я предпочитаю в таких случаях выбирать ручной способ. В любом случае, я отмечу ваш ответ как принятый, поскольку вы описали все доступные подходы.
@Roni - Пункт 1 действительно является недостатком. Не уверен, что понимаю пункт 2. Если вам все еще нужно свойство дискриминатора типа TypeAModels
по другим причинам, вы можете сохранить его, пока пометите его и все его переопределения как [JsonIgnore]
. Я удалил его, так как он показался лишним. См. dotnetfiddle.net/mbsJLJ. Должен ли я добавить эту информацию в вопрос?
Я имею в виду, что если я взгляну на точку зрения клиента, предполагая, что он хочет POST
нового TypeA_A
. Клиент знает только модель TypeA_A(Age, TypeB(Year))
, поэтому JSON, который он отправит, будет таким: { "age: 30, "typeB": { "year": 1982 }}
. Но на самом деле я должен получить и TypeAModels
, и TypeBModels
. Это более понятно? Спасибо!!
@Roni - В вашем исходном вопросе были указаны дискриминаторы типа, такие как "TypeAModels": 1
, так почему же клиент о них не знает? Использует ли клиент те же модели C# + платформу, что и сервер? Разные модели, но все же C#/.NET? Или совершенно другая технология, например. JavaScript? Я ответил на ваш вопрос, предполагая, что вас устраивает показанный «клиентский JSON», разве это не так?
@Roni - в любом случае, если вам вообще не нужны дискриминаторы типов, вам нужно как-то изменить свой подход. Одним из способов было бы добавить JsonConverter<TypeA>
, который предварительно загружает JsonDocument
и проверяет наличие Age
, TypeB
или Name
. Другой вариант — устранить иерархию классов и добавить все возможные свойства к TypeA
и TypeB
и условно игнорировать их, если они не установлены.
Сообщение об ошибке вводит в заблуждение. Вам нужно применить атрибуты или использовать
JsonConverter
для десериализации полиморфной модели. См. этот ответ и другие вопросы Возможна ли полиморфная десериализация в System.Text.Json?. Достаточно ли этот общий вопрос отвечает на ваш вопрос или вам нужна более конкретная помощь?