序列化为JSON时如何添加注释

问题描述 投票:0回答:1

使用 .NET 4.8 Framework 和 System.Text.Json v.9.0,我尝试使用自定义转换器在序列化时添加注释(如本文中所示)
如何向 Json.NET 输出添加注释?

public class JsonCommentConverter : JsonConverter
{
    private readonly string _comment;
    public JsonCommentConverter(string comment)
    {
        _comment = comment;
    }
...
}
public class Person
{
    [JsonConverter(typeof(JsonCommentConverter), "Name of the person")]
    public string Name { get; set; }

    [JsonConverter(typeof(JsonCommentConverter), "Age of the person")]
    public int Age { get; set; }
}

不幸的是,这是一篇旧文章,JsonConverter 不允许传递 2 个参数。

我知道 JSON 标准不允许注释,但是有没有办法在序列化点添加注释?

基本上,这就是我想要的

{
    "Name": "Jack"/*Name of the person*/,
    "Age": 22/*Age of the person*/
}

谢谢你,

c# json .net-4.8 system.text.json .net-9.0
1个回答
0
投票

虽然

JsonConverterAttribute
确实不支持传递构造函数参数,但它不是密封的,因此您可以创建自己的属性,该属性派生自它并使用所需的注释作为参数。然后覆盖
JsonConverterAttribute.CreateConverter(Type)
以返回您的
JsonCommentConverter
,并将注释传递到其构造函数中。

为此,首先创建以下自定义属性:

public sealed class JsonCommentAttribute : JsonConverterAttribute
{
    readonly string Comment;
    public JsonCommentAttribute(string Comment) { this.Comment = Comment; }
    public override JsonConverter CreateConverter (Type typeToConvert) { return new JsonCommentConverter(Comment); }
}

接下来创建

JsonCommentConverter
如下:

public class JsonCommentConverter<TBase> : DefaultConverterFactory<TBase>
{
    readonly string CommentWithDelimiters;

    public JsonCommentConverter(string comment) 
    { 
        this.CommentWithDelimiters = " /*" + comment + "*/";
    }

    protected override void Write<T>(Utf8JsonWriter writer, T value, JsonSerializerOptions modifiedOptions) 
    {
        // TODO: in .NET 9 investigate https://learn.microsoft.com/en-us/dotnet/api/microsoft.toolkit.highperformance.buffers.arraypoolbufferwriter-1 to avoid string allocations.
        var json = JsonSerializer.Serialize(value, modifiedOptions);
        writer.WriteRawValue(json + CommentWithDelimiters, skipInputValidation : true);
    }

    protected override JsonSerializerOptions ModifyOptions(JsonSerializerOptions options) 
    { 
        var modifiedOptions = base.ModifyOptions(options);
        modifiedOptions.WriteIndented = false;
        return modifiedOptions;
    }
}

public abstract class DefaultConverterFactory<TBase> : JsonConverterFactory
{
    // Adapted from this answer https://stackoverflow.com/a/78512783/3744182
    // To https://stackoverflow.com/questions/78507408/in-system-text-json-is-it-possible-to-minify-only-array-items
    class DefaultConverter<TConcrete> : JsonConverter<TConcrete> where TConcrete : TBase
    {
        readonly JsonSerializerOptions modifiedOptions;
        readonly DefaultConverterFactory<TBase> factory;

        public DefaultConverter(JsonSerializerOptions modifiedOptions, DefaultConverterFactory<TBase> factory) { this.modifiedOptions = modifiedOptions; this.factory = factory; }

        public override void Write(Utf8JsonWriter writer, TConcrete value, JsonSerializerOptions options) { factory.Write(writer, value, modifiedOptions); }
        // In .NET 9 use
        // return public override TConcrete? Read
        public override TConcrete Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return factory.Read<TConcrete>(ref reader, typeToConvert, modifiedOptions); }
    }

    protected virtual JsonSerializerOptions ModifyOptions(JsonSerializerOptions options) { return options.CopyAndRemoveConverter(this.GetType()); }

    // In .NET 9 use
    // return public override T? Read(
    protected virtual TConcrete Read<TConcrete>(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions modifiedOptions) where TConcrete : TBase
    {
        // In .NET 9 use
        // return (T?)
        return (TConcrete)JsonSerializer.Deserialize(ref reader, typeToConvert, modifiedOptions);
    }

    protected virtual void Write<TConcrete>(Utf8JsonWriter writer, TConcrete value, JsonSerializerOptions modifiedOptions) where TConcrete : TBase
    { 
        JsonSerializer.Serialize(writer, value, modifiedOptions); 
    }

    public sealed override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) 
    { 
        var modifiedOptions =  ModifyOptions(options);
        if (typeToConvert == typeof(TBase))
            return new DefaultConverter<TBase>(modifiedOptions, this);
        else
            // In .NET 9 apply ! to the end of this line to suppress a nullable warning
            return (JsonConverter)Activator.CreateInstance(typeof(DefaultConverter<>).MakeGenericType(typeof(TBase), typeToConvert), new object [] {modifiedOptions, this });
    }
    
    public override bool CanConvert(Type typeToConvert) { return typeof(TBase).IsAssignableFrom(typeToConvert); }
}

public static partial class JsonExtensions
{
    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;
    }
}

最后修改

Person
如下:

public class Person
{
    [JsonComment("Name of the person")]
    public string Name { get; set; }

    [JsonComment("Age of the person")]
    public int Age { get; set; }
}

并使用以下

JsonSerializerOptions
对其进行序列化:

var person = new Person { Name = "Jack", Age = 22 };

var options = new JsonSerializerOptions
{
    ReadCommentHandling = JsonCommentHandling.Skip,
    // Add other options as required, e.g.
    WriteIndented = true,
};

var json = JsonSerializer.Serialize(person, options);

var person2 = JsonSerializer.Deserialize<Person>(json, options);

您的 JSON 将根据需要:

{
  "Name": "Jack" /*Name of the person*/,
  "Age": 22 /*Age of the person*/
}

备注:

  • 严格来说,JSON标准中不包含注释,System.Text.Json默认不允许注释。 不过,您可以通过设置

    JsonCommentHandling
    来启用支持,如如何使用 System.Text.Json 解析带有注释的 JSON?.

  • 我在 .NET 9 中测试了我的解决方案,但您使用的是 .NET 4.8 Framework 和 System.Text.Json v.9.0。 这是一个不寻常的组合,Microsoft 似乎没有很好地支持它,因此您可能会遇到问题。 由于

    Utf8JsonReader
    是在
    C# 7.2
    中首次引入的 ref struct,因此在构建时必须确保使用此语言版本或更高版本。 要了解如何操作,请查看 。NET Framework 4.8 似乎使用早于 7 的 C# 版本

    如果您确实遇到问题,例如spans 或 ref structs,我建议坚持使用 .NET Framework 4.x 中的 Newtonsoft。 正如您所发现的,Newtonsoft 确实支持通过转换器向属性添加注释,请参阅如何向 Json.NET 输出添加注释?了解详细信息。

    在编写上面的代码时,我尝试不使用 7.3 以上的任何 C# 语言功能,但我没有可用于测试 .NET 4.8 Framework 和 System.Text.Json v.9.0 组合的工具。

  • 由于您的目标是 .NET Framework,所以我删除了所有可为 null 的注释,但在 .NET 9 中,人们希望应用它们。

  • 只需在转换器内调用

    Utf8JsonWriter.WriteCommentValue()
    ,如下所示:

    JsonSerializer.Serialize(writer, value, options);
    writer.WriteCommentValue(Comment);
    

    导致评论被放置在下一行并缩进:

    {
      "Name": "Jack"
      /*Name of the person*/,
      "Age": 22
      /*Age of the person*/
    }
    

    由于您的问题显示评论出现在同一行,所以我必须调整答案 In System.Text.Json is it possible to minify only array items? 暂时禁用缩进。

  • 虽然这对您来说并不重要,但在

    源生成模式
    中不支持从JsonPropertyAttribute派生。 使用源生成时需要不同的实现。

演示 .NET 9 在这里摆弄:https://dotnetfiddle.net/Y2p2I5

© www.soinside.com 2019 - 2024. All rights reserved.