используйте System.Text.Json для условной сериализации объекта как одной строки
Я работаю над внедрением ActivityPub в C #, и иногда ссылки представляют собой «строки», такие как URL-ссылки, а иногда ссылки являются объектами с подтипом Link. (Ссылка: Сущность)
Мне интересно, есть ли возможный способ использовать System.Text.Json для сериализации объекта Link в виде строки, если выполняется определенный набор условий (просто напишите одну строку в писатель) и запишите весь объект по умолчанию в писатель, если условие не верно.
Я пробовал следовать этому решению: как использовать сериализацию по умолчанию в настраиваемом System.Text.Json JsonConverter?, который по-прежнему работает со скрипкой кода, но не работает с моей имплементацией, и я не совсем уверен, почему.
Кто-нибудь знает, как я могу отладить это, или лучший способ убедиться, что
Link : Entity
объекты могут иногда сериализоваться в строки?
При этом я получаю следующую ошибку:
(в этом случае я даже попытался добавить полученный по умолчанию ctor в modifiedOptions) Несмотря на это, он говорит, что нет данных, отображаемых для класса Link. Я также попытался добавить атрибут JsonSerializeable непосредственно в класс Link.
Ошибка:
Metadata for type 'ActivityPub.Types.Link' was not provided to the serializer. The serializer method used does not support reflection-based creation of serialization-related type metadata. If using source generation, ensure that all root types passed to the serializer have been indicated with 'JsonSerializableAttribute', along with any types that might be serialized polymorphically.
Моя базовая библиотека кода: https://github.com/Meep-Tech/ActivityHub.Net/tree/collapse_links_during_serialization
Код теста:
static void Main(string[] args) {
Settings.DefaultContext = new Link("ActivityPub.Net.Testing");
var testObject = new Object {
Type = "Test",
At = new Link("/terry") {
Rels = new string[] {
"test",
"test2"
}
},
Attribution = "/meep",
Audience = new Link("/all") {
Rel = "test"
}
};
string json = testObject
.Serialize();
System.IO.File.WriteAllLines(
"test.json",
new[] { json }
);
Object @object = json.DeSerializeEntity<Object>();
System.IO.File.WriteAllLines(
"test1.json",
new[] { @object.ToString() }
);
}
1 ответ
В моей исходной версии я кэшировал преобразователь по умолчанию, потому что в своей документации Как писать пользовательские преобразователи для сериализации JSON (сортировки) в .NET Microsoft рекомендует при сериализации сложного объекта кэшировать все необходимые преобразователи по соображениям производительности:
public DictionaryEnumConverterInner(JsonSerializerOptions options) { // For performance, use the existing converter if available. _valueConverter = (JsonConverter<TValue>)options .GetConverter(typeof(TValue)); // Cache the key and value types. _keyType = typeof(TKey); _valueType = typeof(TValue); }
Однако это оказалось проблематичным по нескольким причинам:
При сериализации полиморфного значения с объявленным типом
object
, нефункциональный преобразователь возвращаетсяGetConverter()
.При сериализации числовых значений возвращаемый преобразователь игнорирует
NumberHandling
параметр.И теперь, похоже, вы, возможно, столкнулись с третьей проблемой: при использовании возвращаемый преобразователь может не работать.
Это достаточно проблем, чтобы оправдать игнорирование рекомендации Microsoft. Просто
DefaultConverter<T>
следующим образом:
public abstract class DefaultConverterFactory<T> : JsonConverterFactory
{
class DefaultConverter : JsonConverter<T>
{
readonly JsonSerializerOptions modifiedOptions;
readonly DefaultConverterFactory<T> factory;
public DefaultConverter(JsonSerializerOptions options, DefaultConverterFactory<T> factory)
{
this.factory = factory;
this.modifiedOptions = options.CopyAndRemoveConverter(factory.GetType());
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => factory.Write(writer, value, modifiedOptions);
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => factory.Read(ref reader, typeToConvert, modifiedOptions);
}
protected virtual T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions modifiedOptions)
=> (T)JsonSerializer.Deserialize(ref reader, typeToConvert, modifiedOptions);
protected virtual void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions modifiedOptions)
=> JsonSerializer.Serialize(writer, value, modifiedOptions);
public override bool CanConvert(Type typeToConvert) => typeof(T) == typeToConvert;
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new DefaultConverter(options, this);
}
public static class JsonSerializerExtensions
{
public static JsonSerializerOptions CopyAndRemoveConverter(this JsonSerializerOptions options, Type converterType)
{
var copy = new JsonSerializerOptions(options);
for (var i = copy.Converters.Count - 1; i >= 0; i--)
if (copy.Converters[i].GetType() == converterType)
copy.Converters.RemoveAt(i);
return copy;
}
}
Тогда в любых классах, производных от
DefaultConverterFactory<T>
например вот этот , удалите последний параметр
JsonConverter<T> defaultConverter
из
Read()
а также
Write()
, и теперь ваш код должен работать.
(Кстати, вы, кажется, используете[JsonSerializable(typeof(Link))]
неправильно. Вы применяете его к своему классу модели
Link
но, согласно генерации исходного кода сериализатора во время компиляциидокументам , его следует применять к некоторому подклассуJsonSerializerContext
для вашей модели, а не для самой модели.)