ทำให้คุณสมบัติ deserialize แต่ไม่ทำให้เป็นอนุกรมกับ json.net


124

เรามีไฟล์คอนฟิกูเรชันบางไฟล์ที่สร้างโดยการทำให้อ็อบเจกต์ C # เป็นอนุกรมกับ Json.net

เราต้องการย้ายคุณสมบัติหนึ่งของคลาสซีเรียลไลซ์ออกจากการเป็นคุณสมบัติ enum ธรรมดาไปยังคุณสมบัติคลาส

วิธีง่ายๆวิธีหนึ่งในการทำเช่นนี้คือการทิ้งคุณสมบัติ enum เก่าไว้ในคลาสและจัดให้ Json.net อ่านคุณสมบัตินี้เมื่อเราโหลด config แต่อย่าบันทึกอีกครั้งเมื่อเราจัดลำดับออบเจ็กต์ต่อไป เราจะจัดการกับการสร้างคลาสใหม่จาก enum เก่าแยกกัน

มีวิธีง่ายๆในการทำเครื่องหมาย (เช่นด้วยแอตทริบิวต์) คุณสมบัติของออบเจ็กต์ C # หรือไม่เพื่อที่ Json.net จะเพิกเฉยต่อเมื่อทำให้เป็นซีเรียลไลซ์เท่านั้น แต่จะต้องเข้าร่วมเมื่อทำการ deserializing


สิ่งที่เกี่ยวกับตัวแปลงที่กำหนดเอง: คุณสามารถใช้เป็นแอตทริบิวต์ในคุณสมบัติของคุณแทนที่ ReadJson และ WriteJson ด้วยองค์ประกอบที่แตกต่างกันได้หรือไม่? ตัวอย่าง (ไม่ใช่สิ่งที่คุณต้องการ แต่ ... ) weblogs.asp.net/thangchung/archive/2010/08/26/…
Raphaël Althaus

แอตทริบิวต์ OnDeserializedสามารถแก้ปัญหาให้คุณได้
Estefany Velez

ไม่น่าจะเป็นไปได้โดยใช้แอตทริบิวต์ `[JsonIgnore] '?? james.newtonking.com/archive/2009/10/23/…
Juri

คุณสามารถขยายความเกี่ยวกับวิธีใช้ในทิศทางเดียวเท่านั้นตามย่อหน้าสุดท้ายของ q ได้หรือไม่?
Will Dean

เป็นไปได้ที่จะใช้ [JsonIgnore] ร่วมกับตัวตั้งค่าส่วนตัวรองที่ตกแต่งด้วยแอตทริบิวต์ [JsonProperty] มีวิธีแก้ปัญหาง่ายๆอีกสองสามวิธีเช่นกัน ฉันได้เพิ่มการเขียนรายละเอียดแล้ว
Brian Rogers

คำตอบ:


133

มีวิธีง่ายๆหลายวิธีที่คุณสามารถใช้เพื่อให้ได้ผลลัพธ์ที่คุณต้องการ

ตัวอย่างเช่นสมมติว่าคุณมีคลาสของคุณที่กำหนดไว้เช่นนี้:

class Config
{
    public Fizz ObsoleteSetting { get; set; }
    public Bang ReplacementSetting { get; set; }
}

enum Fizz { Alpha, Beta, Gamma }

class Bang
{
    public string Value { get; set; }
}

และคุณต้องการทำสิ่งนี้:

string json = @"{ ""ObsoleteSetting"" : ""Gamma"" }";

// deserialize
Config config = JsonConvert.DeserializeObject<Config>(json);

// migrate
config.ReplacementSetting = 
    new Bang { Value = config.ObsoleteSetting.ToString() };

// serialize
json = JsonConvert.SerializeObject(config);
Console.WriteLine(json);

เพื่อรับสิ่งนี้:

{"ReplacementSetting":{"Value":"Gamma"}}

แนวทางที่ 1: เพิ่มเมธอด ShouldSerialize

Json.NET มีความสามารถในการทำให้เป็นอนุกรมคุณสมบัติตามเงื่อนไขโดยมองหาShouldSerializeวิธีการที่เกี่ยวข้องในคลาส

หากต้องการใช้คุณลักษณะนี้ให้เพิ่มShouldSerializeBlah()วิธีการบูลีนลงในคลาสของคุณโดยBlahแทนที่ด้วยชื่อของคุณสมบัติที่คุณไม่ต้องการทำให้เป็นอนุกรม falseให้ดำเนินการตามวิธีนี้มักจะกลับมา

class Config
{
    public Fizz ObsoleteSetting { get; set; }

    public Bang ReplacementSetting { get; set; }

    public bool ShouldSerializeObsoleteSetting()
    {
        return false;
    }
}

หมายเหตุ: หากคุณชอบแนวทางนี้ แต่ไม่ต้องการทำให้อินเทอร์เฟซสาธารณะของชั้นเรียนของคุณยุ่งเหยิงด้วยการแนะนำShouldSerializeวิธีการคุณสามารถใช้IContractResolverเพื่อทำสิ่งเดียวกันโดยทางโปรแกรม ดูConditional Property Serializationในเอกสารประกอบ

แนวทางที่ 2: จัดการ JSON ด้วย JObjects

แทนที่จะใช้JsonConvert.SerializeObjectเพื่อทำการทำให้เป็นอนุกรมให้โหลดออบเจ็กต์ config ลงใน a JObjectจากนั้นเพียงแค่ลบคุณสมบัติที่ไม่ต้องการออกจาก JSON ก่อนที่จะเขียนออกมา มันเป็นโค้ดพิเศษสองสามบรรทัด

JObject jo = JObject.FromObject(config);

// remove the "ObsoleteSetting" JProperty from its parent
jo["ObsoleteSetting"].Parent.Remove();

json = jo.ToString();

แนวทางที่ 3: การใช้คุณลักษณะอย่างชาญฉลาด (ab)

  1. ใช้[JsonIgnore]แอตทริบิวต์กับคุณสมบัติที่คุณไม่ต้องการทำให้เป็นอนุกรม
  2. เพิ่มตัวตั้งค่าคุณสมบัติส่วนตัวสำรองลงในคลาสด้วยชนิดเดียวกับคุณสมบัติดั้งเดิม ทำให้การดำเนินการตามคุณสมบัตินั้นตั้งค่าคุณสมบัติเดิม
  3. ใช้[JsonProperty]แอ็ตทริบิวต์กับ setter ทางเลือกโดยให้ชื่อ JSON เดียวกันกับคุณสมบัติดั้งเดิม

นี่คือConfigคลาสที่แก้ไข:

class Config
{
    [JsonIgnore]
    public Fizz ObsoleteSetting { get; set; }

    [JsonProperty("ObsoleteSetting")]
    private Fizz ObsoleteSettingAlternateSetter
    {
        // get is intentionally omitted here
        set { ObsoleteSetting = value; }
    }

    public Bang ReplacementSetting { get; set; }
}

7
เราแก้ไขมันในโปรเจ็กต์ของเรา (ซึ่งใช้ซูเปอร์เซ็ตเฉพาะการรวมภายในของโมเดลพื้นฐานโดยที่คุณสมบัติระดับซูเปอร์คลาสไม่ควรต่อเนื่องกัน) โดยการตั้งค่าคุณสมบัติรับเป็นภายใน การมีตัวตั้งค่าสาธารณะอนุญาตให้ Web Api ตั้งค่าคุณสมบัติ แต่หยุดไม่ให้ทำให้เป็นอนุกรม
Daniel Saidi

7
ร่วมกับการใช้JsonPropertyAttributeC # 6.0 คุณสามารถใช้nameofคีย์เวิร์ดแทนการใช้ "magic strings" ได้ สิ่งนี้ทำให้การปรับโครงสร้างใหม่ง่ายขึ้นและพิสูจน์ได้ง่ายขึ้นมาก - นอกจากนี้หากคุณพลาดการเปลี่ยนชื่อเหตุการณ์ใด ๆ คอมไพเลอร์จะเตือนคุณ เมื่อใช้ตัวอย่างของ @ Brian การใช้งานจะเป็นดังนี้:[JsonProperty(nameof(ObsoleteSetting))]
Geoff James

1
เป็นความคิดที่ดีที่จะใช้ nameof () ในการประกาศของ JsonProperty โดยเฉพาะในสถานการณ์เดิมนี้ JSON แสดงถึงสัญญาภายนอก (และหวังว่าจะเป็นนิรันดร์) กับอินเทอร์เฟซอื่นและคุณไม่ต้องการเปลี่ยนชื่อคุณสมบัติ JSON อย่างแน่นอนหากคุณ refactor คุณจะทำลายความเข้ากันได้ของไฟล์ JSON และส่วนประกอบทั้งหมดที่สร้าง JSON ในรูปแบบนี้ ในความเป็นจริงคุณควรใส่ JsonProperty (…) ด้วยชื่อเต็มในทุกคุณสมบัติที่ต่อเนื่องกันเพื่อให้แน่ใจว่าจะไม่เปลี่ยนแปลงหากคุณเปลี่ยนชื่อคุณสมบัติในภายหลัง
Ammo Goettsch

36

สำหรับสถานการณ์ใด ๆ ที่เป็นที่ยอมรับให้มีการทำเครื่องหมายคุณสมบัติ deserialization-only ของคุณไว้ภายในมีวิธีง่ายๆที่น่าทึ่งซึ่งไม่ได้ขึ้นอยู่กับคุณสมบัติเลย เพียงทำเครื่องหมายคุณสมบัติเป็น get ภายใน แต่ตั้งค่าสาธารณะ:

public class JsonTest {

    public string SomeProperty { internal get; set; }

}

สิ่งนี้ส่งผลให้การ deserialization ถูกต้องโดยใช้การตั้งค่าเริ่มต้น / ตัวแก้ไข / ฯลฯ แต่คุณสมบัติถูกดึงออกจากเอาต์พุตที่ต่อเนื่องกัน


วิธีแก้ปัญหาที่เรียบง่าย แต่ชาญฉลาด
hbulens

เนื่องจากโปรดทราบว่าคุณสมบัติจะถูกละเว้นโดยโมดูลการตรวจสอบเกินไป (ดังนั้นคุณไม่สามารถทำเครื่องหมายเป็น [จำเป็น] สำหรับ deserialization อีกต่อไปเนื่องจากต้องอาศัยgetวิธีการสาธารณะ)
Martin Hansen

2
นี้ไม่ได้ทำงานกับทั้งมิได้internal privateมันเป็นอนุกรมเสมอ
พอล

สิ่งนี้ไม่ได้ผลสำหรับฉัน ได้รับคุณสมบัติไม่พบข้อผิดพลาดเมื่อ deserializing
Drew Sumido

31

ฉันชอบที่จะยึดติดกับคุณลักษณะนี้นี่คือวิธีที่ฉันใช้เมื่อต้องการยกเลิกการกำหนดค่าคุณสมบัติ แต่ไม่ทำให้เป็นอนุกรมหรือในทางกลับกัน

ขั้นตอนที่ 1 - สร้างแอตทริบิวต์ที่กำหนดเอง

public class JsonIgnoreSerializationAttribute : Attribute { }

ขั้นตอนที่ 2 - สร้าง Reslover ตามสัญญาที่กำหนดเอง

class JsonPropertiesResolver : DefaultContractResolver
{
    protected override List<MemberInfo> GetSerializableMembers(Type objectType)
    {
        //Return properties that do NOT have the JsonIgnoreSerializationAttribute
        return objectType.GetProperties()
                         .Where(pi => !Attribute.IsDefined(pi, typeof(JsonIgnoreSerializationAttribute)))
                         .ToList<MemberInfo>();
    }
}

ขั้นตอนที่ 3 - เพิ่มแอตทริบิวต์ในกรณีที่ไม่จำเป็นต้องใช้การทำให้เป็นอนุกรม แต่การดีซีเรียลไลเซชันคือ

    [JsonIgnoreSerialization]
    public string Prop1 { get; set; } //Will be skipped when serialized

    [JsonIgnoreSerialization]
    public string Prop2 { get; set; } //Also will be skipped when serialized

    public string Prop3 { get; set; } //Will not be skipped when serialized

ขั้นตอนที่ 4 - ใช้มัน

var sweet = JsonConvert.SerializeObject(myObj, new JsonSerializerSettings { ContractResolver = new JsonPropertiesResolver() });

หวังว่านี่จะช่วยได้! นอกจากนี้ยังเป็นที่น่าสังเกตว่าสิ่งนี้จะเพิกเฉยต่อคุณสมบัติเมื่อ Deserialization เกิดขึ้นเมื่อฉัน derserializing ฉันแค่ใช้ตัวแปลงในแบบเดิม

JsonConvert.DeserializeObject<MyType>(myString);

ขอบคุณสำหรับการใช้งานที่เป็นประโยชน์นี้ มีวิธีที่จะขยายการใช้งานฐานGetSerializableMembersแทนที่จะลบล้างทั้งหมดหรือไม่?
Alain

4
ไม่เป็นไรเพิ่งรู้ว่ามันง่ายเหมือน:return base.GetSerializableMembers(objectType).Where(pi => !Attribute.IsDefined(pi, typeof(JsonIgnoreSerializationAttribute))).ToList();
Alain

ไม่แน่ใจว่าเหตุใดจึงไม่ใช่คำตอบยอดนิยม มันสะอาดตามรูปแบบของ newtonsoft และทำได้ง่าย สิ่งเดียวที่ฉันจะเพิ่มคือคุณสามารถตั้งค่าได้ทั่วโลกโดยใช้JsonConvert.DefaultSettings = () => new JsonSerializerSettings { ContractResolver = new JsonPropertiesResolver() }
Matt M

1
ไม่เป็นไรนี่ไม่ได้ทำตามที่ผู้ถามขอ นี่คือการสร้าง JsonIgnore ขึ้นมาใหม่ มันไม่ได้ข้ามคุณสมบัติสำหรับการทำให้เป็นอนุกรม แต่ตั้งค่าคุณสมบัติระหว่างการดีซีเรียลไลเซชันเนื่องจากไม่มีวิธีใดที่เมธอด GetSerializableMembers จะทราบว่าเป็นการอ่านหรือเขียนดังนั้นคุณจึงไม่รวมคุณสมบัติสำหรับทั้งสองอย่าง วิธีนี้ไม่ได้ผล
Matt M

7

ใช้คุณสมบัติ setter:

[JsonProperty(nameof(IgnoreOnSerializing))]
public string IgnoreOnSerializingSetter { set { _ignoreOnSerializing = value; } }

[JsonIgnore]
private string _ignoreOnSerializing;

[JsonIgnore]
public string IgnoreOnSerializing
{
    get { return this._ignoreOnSerializing; }
    set { this._ignoreOnSerializing = value; }
}

หวังว่าจะช่วยได้


1
ขอบคุณ โปรดสังเกตว่า JsonProperty ควรมีตัวพิมพ์ใหญ่IgnoreOnSerializingเท่ากับคุณสมบัติ ฉันแนะนำให้ใช้nameof(IgnoreOnSerializing)เพื่อหลีกเลี่ยงสายเวทย์มนตร์ในกรณีที่เปลี่ยนชื่อ
Bendik สิงหาคมNesbø

5

หลังจากที่ฉันใช้เวลานานพอสมควรในการค้นหาวิธีตั้งค่าสถานะคุณสมบัติคลาสให้เป็นแบบ De-Serializable และไม่ต่ออนุกรมได้ฉันพบว่าไม่มีสิ่งที่ต้องทำเลย ดังนั้นฉันจึงคิดวิธีแก้ปัญหาที่รวมไลบรารีหรือเทคนิคการทำให้เป็นอนุกรม (System.Runtime.Serialization.Json & Newtonsoft.Json) สองไลบรารีที่แตกต่างกันและได้ผลสำหรับฉันดังนี้:

  • ตั้งค่าสถานะคลาสและคลาสย่อยทั้งหมดของคุณเป็น "DataContract"
  • ตั้งค่าสถานะคุณสมบัติทั้งหมดของคลาสและคลาสย่อยของคุณเป็น "DataMember"
  • ตั้งค่าสถานะคุณสมบัติทั้งหมดของคลาสและคลาสย่อยของคุณเป็น "JsonProperty" ยกเว้นคุณสมบัติที่คุณต้องการไม่ให้เป็นอนุกรม
  • ตอนนี้ตั้งค่าสถานะคุณสมบัติที่คุณไม่ต้องการให้ซีเรียลไลซ์เป็น "JsonIgnore"
  • จากนั้น Serialize โดยใช้ "Newtonsoft.Json.JsonConvert.SerializeObject" และ De-Serialize โดยใช้ "System.Runtime.Serialization.Json.DataContractJsonSerializer"

    using System;
    using System.Collections.Generic;
    using Newtonsoft.Json;
    using System.Runtime.Serialization;
    using System.IO;
    using System.Runtime.Serialization.Json;
    using System.Text;
    
    namespace LUM_Win.model
    {
        [DataContract]
        public class User
        {
            public User() { }
            public User(String JSONObject)
            {
                MemoryStream stream = new MemoryStream(Encoding.Unicode.GetBytes(JSONObject));
                DataContractJsonSerializer dataContractJsonSerializer = new DataContractJsonSerializer(typeof(User));
    
                User user = (User)dataContractJsonSerializer.ReadObject(stream);
                this.ID = user.ID;
                this.Country = user.Country;
                this.FirstName = user.FirstName;
                this.LastName = user.LastName;
                this.Nickname = user.Nickname;
                this.PhoneNumber = user.PhoneNumber;
                this.DisplayPicture = user.DisplayPicture;
                this.IsRegistred = user.IsRegistred;
                this.IsConfirmed = user.IsConfirmed;
                this.VerificationCode = user.VerificationCode;
                this.Meetings = user.Meetings;
            }
    
            [DataMember(Name = "_id")]
            [JsonProperty(PropertyName = "_id")]
            public String ID { get; set; }
    
            [DataMember(Name = "country")]
            [JsonProperty(PropertyName = "country")]
            public String Country { get; set; }
    
            [DataMember(Name = "firstname")]
            [JsonProperty(PropertyName = "firstname")]
            public String FirstName { get; set; }
    
            [DataMember(Name = "lastname")]
            [JsonProperty(PropertyName = "lastname")]
            public String LastName { get; set; }
    
            [DataMember(Name = "nickname")]
            [JsonProperty(PropertyName = "nickname")]
            public String Nickname { get; set; }
    
            [DataMember(Name = "number")]
            [JsonProperty(PropertyName = "number")]
            public String PhoneNumber { get; set; }
    
            [DataMember(Name = "thumbnail")]
            [JsonProperty(PropertyName = "thumbnail")]
            public String DisplayPicture { get; set; }
    
            [DataMember(Name = "registered")]
            [JsonProperty(PropertyName = "registered")]
            public bool IsRegistred { get; set; }
    
            [DataMember(Name = "confirmed")]
            [JsonProperty(PropertyName = "confirmed")]
            public bool IsConfirmed { get; set; }
    
            [JsonIgnore]
            [DataMember(Name = "verification_code")]
            public String VerificationCode { get; set; }
    
            [JsonIgnore]
            [DataMember(Name = "meeting_ids")]
            public List<Meeting> Meetings { get; set; }
    
            public String toJSONString()
            {
                return JsonConvert.SerializeObject(this, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
            }
        }
    }

หวังว่าจะช่วย ...


1
บราโวอาเหม็ดอาบูลัสม์ ขอบคุณที่ช่วยฉันจากการทำงานมากมาย :)
Sike12

2

ด้วยการอ้างอิงถึงโซลูชันของ @ ThoHo การใช้ setter เป็นสิ่งที่จำเป็นจริงๆโดยไม่มีแท็กเพิ่มเติม

สำหรับฉันก่อนหน้านี้ฉันมีรหัสอ้างอิงเดียวที่ฉันต้องการโหลดและเพิ่มลงในคอลเล็กชันรหัสอ้างอิงใหม่ โดยการเปลี่ยนนิยามของ Id อ้างอิงให้มีเฉพาะเมธอด setter ซึ่งจะเพิ่มค่าให้กับคอลเล็กชันใหม่ Json ไม่สามารถเขียนค่ากลับถ้า Property ไม่มีget; วิธี.

// Old property that I want to read from Json, but never write again. No getter.
public Guid RefId { set { RefIds.Add(value); } }

// New property that will be in use from now on. Both setter and getter.
public ICollection<Guid> RefIds { get; set; }

ขณะนี้คลาสนี้เข้ากันได้กับเวอร์ชันก่อนหน้าและบันทึกเฉพาะRefIdsสำหรับเวอร์ชันใหม่เท่านั้น


1

เพื่อสร้างคำตอบของ Tho Ho สิ่งนี้สามารถใช้สำหรับฟิลด์ได้

[JsonProperty(nameof(IgnoreOnSerializing))]
public string IgnoreOnSerializingSetter { set { IgnoreOnSerializing = value; } }

[JsonIgnore]
public string IgnoreOnSerializing;

1

ขึ้นอยู่กับที่ในใบสมัครนี้จะเกิดขึ้นและถ้ามันเป็นเพียงหนึ่งในคุณสมบัติหนึ่งคู่มือวิธีการที่คุณสามารถทำเช่นนี้คือโดยการตั้งค่าคุณสมบัติโมฆะและจากนั้นในรูปแบบที่คุณสามารถระบุได้ว่าทรัพย์สินที่ถูกละเว้นถ้าค่าเป็นโมฆะ:

[JsonProperty(NullValueHandling = NullValue.Ignore)]
public string MyProperty { get; set; }

หากคุณกำลังทำงานบนเว็บแอป ASP.NET Core คุณสามารถตั้งค่านี้ทั่วโลกสำหรับคุณสมบัติทั้งหมดในทุกรุ่นโดยตั้งค่านี้ในไฟล์ Startup.cs ของคุณ:

public void ConfigureServices(IServiceCollection services) {
    // other configuration here
    services.AddMvc()
        .AddJsonOptions(options => options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore);
}

0

ถ้าคุณใช้ JsonConvert IgnoreDataMemberAttributeก็โอเคไลบรารีมาตรฐานของฉันไม่อ้างอิง Newton.Json และฉันใช้ [IgnoreDataMember] เพื่อควบคุมการทำให้เป็นอนุกรมของวัตถุ

จากเอกสารความช่วยเหลือของNewton.net


0

มีวิธีง่ายๆในการทำเครื่องหมาย (เช่นด้วยแอตทริบิวต์) คุณสมบัติของออบเจ็กต์ C # หรือไม่เพื่อที่ Json.net จะเพิกเฉยต่อเมื่อทำให้เป็นซีเรียลไลซ์เท่านั้น แต่จะต้องเข้าร่วมเมื่อทำการ deserializing

วิธีที่ง่ายที่สุดที่ฉันได้พบเช่นการเขียนนี้คือการรวมตรรกะนี้ในของคุณIContractResolver

ตัวอย่างโค้ดจากลิงค์ด้านบนคัดลอกไว้ที่นี่สำหรับลูกหลาน:

public class Employee
{
    public string Name { get; set; }
    public Employee Manager { get; set; }

    public bool ShouldSerializeManager()
    {
        // don't serialize the Manager property if an employee is their own manager
        return (Manager != this);
    }
}

public class ShouldSerializeContractResolver : DefaultContractResolver
{
    public new static readonly ShouldSerializeContractResolver Instance = new ShouldSerializeContractResolver();

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);

        if (property.DeclaringType == typeof(Employee) && property.PropertyName == "Manager")
        {
            property.ShouldSerialize =
                instance =>
                {
                    Employee e = (Employee)instance;
                    return e.Manager != e;
                };
        }

        return property;
    }
}

คำตอบทั้งหมดนั้นดี แต่วิธีนี้ดูเหมือนจะเป็นวิธีที่สะอาดที่สุด ฉันใช้สิ่งนี้จริงโดยมองหาแอตทริบิวต์ในคุณสมบัติสำหรับ SkipSerialize และ SkipDeserialize เพื่อให้คุณสามารถมาร์กอัปคลาสที่คุณควบคุมได้ คำถามดีมาก!

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.