使用 System.Text.Json 有条件地将对象序列化为单个字符串

Posted

技术标签:

【中文标题】使用 System.Text.Json 有条件地将对象序列化为单个字符串【英文标题】:use System.Text.Json to serialize an Object as a single String conditionally 【发布时间】:2022-01-12 02:17:11 【问题描述】:

我正在用 c# 编写 ActivityPub 实现,有时链接是像 url 链接一样的“字符串”,有时链接是具有 Link 子类型的对象。 (链接:实体)

我想知道如果一组条件为真(只需将一个字符串写入编写器),是否有可能使用 System.Text.Json 将 Link 对象序列化为字符串,并写入整个默认值如果条件不成立,则反对作者。

我已尝试遵循此解决方案:How to use default serialization in a custom System.Text.Json JsonConverter?,它仍然适用于代码小提琴,但不适用于我的实施,我不太清楚为什么。

有谁知道我可以如何调试它,或者有更好的方法来确保 Link : Entity 对象有时可以序列化为字符串?

我得到以下错误:

(在这种情况下,我什至尝试将获取的默认 ctor 添加到 modifiedOptions) Reguardless,它说没有为 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() 
      );
    

【问题讨论】:

@SuperMeip 在问题本身中发布代码和异常文本。图片不能被复制、谷歌搜索、编译或测试。如果您想获得帮助,请让人们轻松地帮助您。不要强迫人们输入您的代码或点击链接 @PanagiotisKanavos 您正在谈论的信息似乎已经存在。我还能包括什么?你刷新页面了吗?此外,我认为发布到存储库的链接比在我的帖子中复制粘贴整个存储库更容易 您是否有机会为您的问题提供minimal reproducible example? Microsoft 建议缓存和重用JsonConverter 实例——但这纯粹是为了性能。如果您删除 if (converter != null) converter.Write(writer, value, options); 逻辑并始终执行 JsonSerializer.Serialize(writer, value, options); 会发生什么?顺便说一句,我注意到您似乎正在混合使用新的JsonSerializable 属性和自定义转换器生成多态序列化、编译时序列化程序,因此minimal reproducible example 确实有助于澄清问题所在。 顺便说一句,您似乎错误地使用了[JsonSerializable(typeof(Link))]。您正在将其应用于Link,但根据docs,它应该应用于JsonSerializerContext 的某个子类Links 【参考方案1】:

在我的original version of DefaultConverterFactory&lt;T&gt; 中,我缓存了默认转换器,因为在其文档How to write custom converters for JSON serialization (marshalling) in .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 设置。

    现在看来您可能遇到了第三个问题:使用compile-time serializer source generation时,返回的转换器可能无法正常工作。

这些问题足以证明忽略 Microsoft 的建议。只需DefaultConverter&lt;T&gt;如下:

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&lt;T&gt; 派生的任何类中,例如this one here,从Read()Write() 中删除最后一个参数JsonConverter&lt;T&gt; defaultConverter,您的代码现在应该可以工作了。

(顺便说一句,您似乎错误地使用了[JsonSerializable(typeof(Link))]。您正在将它应用于您的模型类Link,但是,根据docs,它应该应用于您的模型的JsonSerializerContext 的某个子类-- 不是模型本身。)

【讨论】:

以上是关于使用 System.Text.Json 有条件地将对象序列化为单个字符串的主要内容,如果未能解决你的问题,请参考以下文章

使用 System.Text.Json 修改 JSON 文件

使用 System.Text.Json 使用动态键查询或反序列化 json

如何将类字段与 System.Text.Json.JsonSerializer 一起使用?

为啥使用 System.Text.Json 的 JSON 反序列化这么慢?

是否可以在 System.Text.Json 中找到未映射的属性?

是否有 System.Text.Json 替代 Json.NET 的 JsonProperty(Order)?