使用JsonPropertyName匹配无效的 Bootstrap 参数

nnvyjq4y  于 4个月前  发布在  Bootstrap
关注(0)|答案(4)|浏览(66)

我尝试将JsonPropertyName与System.Text.Json中的JsonConstuctor属性结合使用,以将json序列化添加到现有库中。
每个类型都有一个构造函数,它主要匹配只读字段(和/或get only属性)。问题是名称并不总是匹配,我不想重命名公共成员的属性/参数。
所以一个class/struct可能看起来像这样:

public struct Person
{
    public Person(string Name)
    {
        FirstName = Name
    }

    public string FirstName { get; }
}

字符串
我本以为这会奏效:

public struct Person
{
    [JsonConstructor]
    public Person(string Name)
    {
        FirstName = Name
    }

    [JsonPropertyName("Name")]
    public string FirstName { get; }
}


json输出看起来正确

{
   "Name": "Joe"
}


但在执行异常化时,会抛出以下错误。
“类型”Person“上的对象化构造函数中的每个参数必须绑定到对象化时的对象属性或字段。每个参数名称必须与对象上的属性或字段匹配。匹配可以不区分大小写。”
当我写这篇文章的时候,我现在在想,[JsonConstructor]属性并没有强制解析器调用构造函数,并将JSON键与参数名匹配,而是使用构造函数中的名称来只设置那些在名称上匹配的属性。在任何情况下,是否有方法不修改Person结构(理想情况下使用属性)来支持System.Text.Json序列化?

mpbci0fu

mpbci0fu1#

我从来没有见过System.Text.Json.JsonConstructor正常工作,当我看到一个例子,我会让你知道。所以我建议你发送电子邮件给微软,感谢他们的一个惊人的解析器,并使用Newtonsoft.Json

using Newtonsoft.Json;

Person person =Newtonsoft.Json.JsonConvert.DeserializeObject<Person>(json);

public struct Person
{
    [Newtonsoft.Json.JsonConstructor]
    public Person(string Name)
    {
        FirstName = Name;
    }

    [JsonProperty("Name")]
    public string FirstName { get; }
}

字符串

eagi6jfj

eagi6jfj2#

如果你能够改变现有的库,那么使用C# 9,你可以添加一个private init;并执行以下操作:

public struct Person
{
    public Person(string Name)
    {
        this.FirstName = Name;
    }
    
    [JsonInclude]
    [JsonPropertyName("Name")]
    public string FirstName { get; private init; }
}

字符串
您需要[JsonInclude]才能允许解析器使用private init
这不会改变结构体的公共API,从您的问题来看,您可能对此没有意见。
在C# 9之前,您可以使用private set;,但这会在内部打开API。

guz6ccqo

guz6ccqo3#

如果你将这个构造函数参数从Name重命名为firstName,那么使用System.Text.Json就可以正常工作。
FirstName * 也可以,但camel case对于参数来说更常见。
这样做,构造函数参数firstName匹配FirstName属性的名称,就像异常消息中提到的那样。
每个参数名称必须与对象的属性或字段匹配。匹配可以不区分大小写。

public struct Person
{
    [JsonConstructor]
    public Person(string firstName)
    {
        FirstName = firstName;
    }

    [JsonPropertyName("Name")]
    public string FirstName { get; }
}

个字符


的数据

zu0ti5jz

zu0ti5jz4#

你可以使用json构造函数并使用反射中的属性或字段名称,或者使用JsonParameterName属性而不使用json构造函数,你不能两者兼而有之。也许.net 9允许它,但当你查看源代码生成器时,它不允许,因为它在属性名称上使用反射。要亲自查看,右键单击ToDoJsonContext并选择“go to definition.”,然后查看它期望如何调用构造函数
正如Yits提到的,删除json构造函数(意味着你需要支持无参数构造函数)。
下面是一个小的测试应用程序:

using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace JsonTestConsole
{
    internal class Program
    {
        static async Task Main(string[] args)
        {
            /*test and work with a single entry*/

            var entry = new ToDo() { Rank = Random.Shared.Next(1, int.MaxValue), Task = "test a class" };
            var json = JsonSerializer.Serialize<ToDo>(entry, ToDoJsonContext.Default.ToDo);
            Console.WriteLine(json);
            Debug.Assert(json.Contains("\"a\":"), "supposed to have \"a\": property");
            var copy = JsonSerializer.Deserialize<ToDo>(json, ToDoJsonContext.Default.ToDo);
            Debug.Assert(copy is not null, "Serialization failed for a single ToDo");
            Debug.Assert(copy.Rank == entry.Rank, $"supposed to have the same rank {copy.Rank} and the original has {entry.Rank}.");
            Debug.Assert(string.Equals(copy.Task, entry.Task, StringComparison.Ordinal), $"Texts is supposed to be the same however copy task = {copy.Task} and entry task = {entry.Task}");
            Console.WriteLine($"Copy has Rank:{copy.Rank} and entry has {entry.Rank}");
            Console.WriteLine($"Copy has Task:{copy.Task} and entry has {entry.Task}");

            /*test and work with a list*/
            var data = new List<ToDo> { new() { Rank = 1, Task = "test json serialization" }, new() { Rank = 2, Task = "test call constructor" } };
            using var stream = new MemoryStream();
            await JsonSerializer.SerializeAsync(stream, data, ToDoListJsonContext.Default.ListToDo);
            json = System.Text.Encoding.UTF8.GetString(stream.ToArray());

            Console.WriteLine(json);
            Debug.Assert(json.Contains("\"a\":"), "supposed to have \"a\": property");

            var clone = JsonSerializer.Deserialize<List<ToDo>>(json, ToDoListJsonContext.Default.ListToDo);
            Debug.Assert(clone is not null, "Serialization failed");
            Debug.Assert(clone.Count == data.Count, $"supposed to have the same number of items, clone has {clone.Count} and data has {data.Count}.");
            Console.WriteLine($"Cone has {clone.Count} entries");
#if !DEBUG
            Console.WriteLine("Press enter to exit");
            Console.ReadLine();
#endif
        }
    }

    [System.Text.Json.Serialization.JsonSerializable(typeof(ToDo))]
    [System.Text.Json.Serialization.JsonSourceGenerationOptions(
            GenerationMode = System.Text.Json.Serialization.JsonSourceGenerationMode.Metadata)]
    public partial class ToDoJsonContext : System.Text.Json.Serialization.JsonSerializerContext
    {
    }

    [System.Text.Json.Serialization.JsonSerializable(typeof(List<ToDo>))]
    [System.Text.Json.Serialization.JsonSourceGenerationOptions(GenerationMode = System.Text.Json.Serialization.JsonSourceGenerationMode.Metadata)]
    public partial class ToDoListJsonContext : System.Text.Json.Serialization.JsonSerializerContext
    {
    }

    public class ToDo
    {

        [JsonInclude]
        [JsonPropertyName("a")]
        public int Rank { get; set; }

        [JsonInclude]
        [JsonPropertyName("b")]
        public required string Task { get; set; }
    }

}

字符串
我还设置了我的项目来支持AOT:

<PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <PublishAot>true</PublishAot>
  </PropertyGroup>


我使用以下发布设置在发布模式和AoT中运行它:x1c 0d1x
工作起来很有魅力:
{“a”:378268935,“B”:“test a class”} [{“a”:1,“B”:“test json serialization”},{“a”:2,“B”:“test call constructor”}]按enter退出

相关问题