【问题标题】:Json.Net Combine 2 Properties on SerializationJson.Net 结合了序列化的 2 个属性
【发布时间】:2017-08-04 14:50:49
【问题描述】:

我对 Json.Net 和(反)序列化还很陌生。我有一个有几个字段的对象

public class Person {
    public string Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Address1 { get; set; }
    public string Address2 { get; set; }
}

var p = new Person() {
    Id = 123,
    FirstName = "John",
    LastName = "Doe",
    Address1 = "456 Main St",
    Address2 = "Apt 2"
}

我需要序列化成下面的json

{
    "id": 123
    , "fullName": "John Doe"
    , "street": "456 Main St Apt 2"
}

并将其反序列化为

// Person.Id = 123
// Person.Address1 = 456 Main St Apt 2
// Person.Address2 = null

// we are ok not trying to split the 'street' into both 'Address1' and 'Address2'

我不确定执行此操作的最佳方法。我是否需要使用ConverterExtensionDataConstructor 或其他我没有找到的东西,因为我不知道如何搜索它;)

任何帮助和示例都会很棒。

【问题讨论】:

    标签: c# json serialization json.net


    【解决方案1】:

    要组合这两个属性,您可以添加如下所示的属性:

    public string FullName => FirstName + " " + LastName;
    

    当然,您需要一些逻辑来处理空值等。

    您可以使用属性来装饰您的类,这些属性将决定如何序列化属性名称。你的班级最终可能看起来像这样:

    using Newtonsoft.Json;
    using System.Linq;
    
    public class Person {
        [JsonProperty("id")]
        public string Id { get; set; }
    
        [JsonProperty("fullName")]
        public string FullName
        {
            get
            {
                return FirstName + " " + LastName; 
            }
            set
            {
                if (value.Contains(" "))
                {
                    var split = value.Split(' ');
                    FirstName = split[0];
                    LastName = string.Join(" ", split.Skip(1));
                }
                else 
                {
                    FirstName = value;
                }
            }
        }
    
        [JsonIgnore]
        public string FirstName { get; set; }
    
        [JsonIgnore]
        public string LastName { get; set; }
    }
    

    JsonProperty 属性允许您在属性被序列化时设置其名称,而 JsonIgnore 属性告诉序列化程序不要在输出中包含这些属性。

    然后您可以使用以下方法获取 JSON:

    var json = JsonConvert.SerializeObject(person);
    

    【讨论】:

    • OP 明确表示他已经在使用 JSON.net。不需要关于 Newtonsoft 的额外说明,它们是一样的
    • 谢谢 Alan,我确实使用 Newtonsoft.Json(我昨天了解到大多数搜索都使用 Json.Net,因为它是同一件事)=)但是,我确实有一些顾虑,因为我确实有其他领域我需要序列化/反序列化遵循类似模式并且不希望以这种方式设置它。我会更新我的问题以反映这一点。
    • 抱歉,我倾向于简化问题,所以它不是那么大/复杂,但后来我意识到,当给出答案时,我应该让它变得复杂。但是,对于我遇到的其他情况,您的回答绝对值得。
    • @RoLYroLLs 没问题。您将不得不告诉序列化程序如何在某处组合属性,所以如果不在类上,我不确定您可以在哪里做。希望其他人会有解决方案:)
    • 谢谢。据我所知,我认为我应该使用OnSerializing,但我没有通过足够多的例子来正确理解它,或者我什至在正确的轨道上。我也希望有人能帮助我至少指出正确的方向。
    【解决方案2】:

    @Alan Buchanan shows 一种很好的简单方法来实现您在问题中描述的内容。但是,如果您不想为类添加其他属性,则需要使用自定义 JsonConverter 来代替。这是一个如何编写它的示例:

    class PersonConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(Person);
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            Person person = (Person)value;
            JObject result = new JObject();
            result.Add("id", person.Id);
            result.Add("fullname", (person.FirstName + " " + person.LastName).Trim());
            result.Add("address", (person.Address1 + " " + person.Address2).Trim());
            result.WriteTo(writer);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JObject obj = JObject.Load(reader);
            Person person = new Person();
            person.Id = (int)obj["id"];   // assuming id will always be present in the JSON
            string fullName = (string)obj["fullname"];
            if (fullName != null)
            {
                string[] parts = fullName.Split(new char[] { ' ' }, 2);
                // if there's only 1 part, I'm assuming it is the first name
                if (parts.Length > 0)
                    person.FirstName = parts[0];
                if (parts.Length > 1)
                    person.LastName = parts[1];
            }
            person.Address1 = (string)obj["address"];  // don't bother trying to split address
            return person;
        }
    }
    

    您会注意到PersonConverter 类包含三个方法,所有这些方法都需要由它所继承的JsonConverter 抽象基类来实现:CanConvertReadJsonWriteJson

    CanConvert 方法告诉 Json.Net 该转换器处理 Person 对象。 Json.Net 可能会也可能不会调用此方法,具体取决于您使用转换器的方式。稍后会详细介绍。

    WriteJson 负责在序列化过程中为目标对象创建 JSON。您可以在这里看到我在内部使用JObject 来使用Person 类中的数据构建JSON。你也可以直接在 writer 上调用方法,而不是使用 JObject,但我觉得那样会比较麻烦。

    ReadJson 处理相反的情况:在反序列化期间从 JSON 重构对象。同样,我使用JObject 作为中介从读取器获取数据,然后将其拆分到新的@​​987654340@ 实例中。

    如果你只需要序列化而不需要反序列化,你可以覆盖CanRead返回false,然后让ReadJson抛出一个NotImplementedException。同样,如果您只需要反序列化而不需要序列化,则可以覆盖 CanWrite 属性。

    有两种方法可以使用JsonConverter:您可以将实例传递给序列化/反序列化方法(通过方法参数或JsonSerializerSettings)...

    string json = JsonConvert.SerializeObject(p, new PersonConverter());
    

    ...或者您可以使用[JsonConverter] 属性将转换器绑定到您的类:

    [JsonConverter(typeof(PersonConverter))]
    public class Person 
    {
        ...
    }
    

    如果您使用前一种方法,那么 Json.Net 会调用 CanConvert 来确定您的转换器可以处理的类型。如果您使用该属性,则永远不会调用 CanConvert,因为 Json.Net 假定您已在目标类的属性中指定了正确的转换器类型。

    这是一个往返演示:https://dotnetfiddle.net/EGok23

    【讨论】:

    • 谢谢!与OnSerializing 相比,这似乎是我可能不得不这样做的方式。我会测试一下,然后告诉你效果如何。
    • Brian,让我问你,对于这个位于 class 级别的 converter,我是否需要添加 ALL 我想要的属性(de)序列化还是只序列化那些我想转换的?
    • 我在您的小提琴示例中测试了我的想法,是的,我确实需要包含该转换器上的所有字段。有没有另一种方法来只操纵某些属性?我有很多属性的对象,不值得为这样的 1 或 2 个字段添加到此转换器。
    【解决方案3】:

    虽然 @Alan Buchanan 的 answer 和 @Brian Rogers answer 展示了很好的解决方案,但这些并没有以简单的方式满足我的需求。

    最适合我的是使用OnSerializingOnDeserialized。如果我做错了什么,可以做得更好,或者应该做一些不同的事情,请告诉我。

    注意: 我知道在Deserialization 时,Address2 字段将为空,Address1 将是 Address1Address2 的组合,但是这对我们的实施来说没问题。

    public class Person {
        public string Id { get; set; }
        [JsonProperty("fullName")]
        public string FullName { get; set; }
        [JsonIgnore]
        public string FirstName { get; set; }
        [JsonIgnore]
        public string LastName { get; set; }
        [JsonProperty("street")]
        public string Street { get; set; }
        [JsonIgnore]
        public string Address1 { get; set; }
        [JsonIgnore]
        public string Address2 { get; set; }
    
        [OnSerializing]
        internal void OnSerializing(StreamingContext context) {
            Street = (Address1 + " " + Address2).Trim();
        }
    
        [OnDeserialized]
        internal void OnDeserialized(StreamingContext context) {
            Address1 = Street;
        }
    }
    
    // C# Object
    var p = new Person() {
        Id = 123,
        FirstName = "John",
        LastName = "Doe",
        Address1 = "456 Main St",
        Address2 = "Apt 2"
    }
    
    // Json to output and consume
    { "id": 123, "fullName": "John Doe", "street": "456 Main St Apt 2" }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-04-01
      • 1970-01-01
      • 2016-08-27
      • 2023-03-29
      • 1970-01-01
      • 1970-01-01
      • 2021-05-26
      • 2015-12-31
      相关资源
      最近更新 更多