ลำดับของฟิลด์ซีเรียลไลซ์โดยใช้ JSON.NET


140

มีวิธีระบุลำดับของฟิลด์ในออบเจ็กต์ JSON แบบอนุกรมโดยใช้JSON.NETหรือไม่

ก็เพียงพอแล้วที่จะระบุว่าเขตข้อมูลเดียวจะปรากฏก่อนเสมอ


7
ฉันคิดว่าเขาอาจสนใจที่จะแสดงฟิลด์ ID (หรือที่คล้ายกัน) ก่อนแล้วจึงแสดงฟิลด์อื่น ๆ ทั้งหมด นี่เป็นมิตรกับผู้ใช้ปลายทางมากกว่าการมองหาช่องที่ขึ้นต้นด้วย A..I
Michael Bahig

3
คุณสมบัติ JSON ถูกกำหนดให้ไม่เรียงลำดับ ฉันคิดว่ามันเป็นการดีอย่างยิ่งที่จะบังคับคำสั่ง OUTPUT เฉพาะในระหว่างการทำให้เป็นอนุกรม (เพื่อประโยชน์ในการจ้องมอง JSON) แต่มันจะเป็นการตัดสินใจที่ไม่ดีในการสร้าง DEPENDENCY สำหรับคำสั่งเฉพาะใด ๆ ในการ deserialization
DaBlick

5
เหตุผลที่ถูกต้องสองประการ: (1) การปลอมคุณสมบัติ "$ type" ซึ่งต้องเป็นคุณสมบัติแรกใน JSON (2) พยายามสร้าง JSON ที่บีบอัดข้อมูลให้ได้มากที่สุด
Stephen Chung

4
อีกสาเหตุหนึ่งอาจเป็น (3) การแสดงตามรูปแบบบัญญัติซึ่งใช้ไวยากรณ์ JSON - ต้องรับประกันว่าวัตถุเดียวกันจะสร้างสตริง JSON เดียวกัน ลำดับที่กำหนดของแอตทริบิวต์เป็นข้อกำหนดเบื้องต้นที่จำเป็นสำหรับสิ่งนี้
MarkusSchaber

2
เควินคุณสามารถอัปเดตคำตอบที่ยอมรับสำหรับคำถามนี้ได้หรือไม่
Millie Smith

คำตอบ:


257

วิธีที่ได้รับการสนับสนุนคือการใช้JsonPropertyแอตทริบิวต์กับคุณสมบัติของคลาสที่คุณต้องการกำหนดลำดับ อ่านเอกสารคำสั่ง JsonPropertyAttributeสำหรับข้อมูลเพิ่มเติม

ผ่านคุณค่าและ serializer จะดูแลส่วนที่เหลือJsonPropertyOrder

 [JsonProperty(Order = 1)]

ซึ่งคล้ายกับไฟล์

 DataMember(Order = 1) 

ของSystem.Runtime.Serializationวัน

นี่คือข้อความสำคัญจาก @ kevin-babcock

... การตั้งค่าคำสั่งเป็น 1 จะใช้ได้เฉพาะเมื่อคุณตั้งค่าคำสั่งซื้อมากกว่า 1 ในคุณสมบัติอื่น ๆ ทั้งหมด โดยค่าเริ่มต้นคุณสมบัติใด ๆ ที่ไม่มีการตั้งค่าคำสั่งซื้อจะได้รับคำสั่งเป็น -1 ดังนั้นคุณต้องให้คุณสมบัติและลำดับที่ทำให้เป็นอนุกรมทั้งหมดหรือตั้งค่ารายการแรกของคุณเป็น -2


97
การใช้OrderคุณสมบัติของJsonPropertyAttributecan ถูกใช้เพื่อควบคุมลำดับฟิลด์ที่ถูกทำให้เป็นอนุกรม / deserialized อย่างไรก็ตามการตั้งค่าคำสั่งเป็น 1 จะใช้ได้ผลก็ต่อเมื่อคุณตั้งค่าคำสั่งซื้อมากกว่า 1 ในคุณสมบัติอื่น ๆ ทั้งหมด โดยค่าเริ่มต้นคุณสมบัติใด ๆ ที่ไม่มีการตั้งค่าคำสั่งซื้อจะได้รับคำสั่งเป็น -1 ดังนั้นคุณต้องให้คุณสมบัติและลำดับที่ทำให้เป็นอนุกรมทั้งหมดหรือตั้งค่ารายการแรกเป็น -2
Kevin Babcock

1
ใช้งานได้สำหรับการทำให้เป็นอนุกรม แต่คำสั่งไม่ได้รับการพิจารณาเกี่ยวกับการดีซีเรียลไลเซชัน ตามเอกสารนี้แอตทริบิวต์คำสั่งใช้สำหรับทั้งการทำให้เป็นอนุกรมและการดีซีเรียลไลเซชัน มีวิธีแก้ปัญหาหรือไม่?
cangosta

1
มีคุณสมบัติที่คล้ายกันสำหรับไฟล์JavaScriptSerializer.
Shimmy Weitzhandler

4
@cangosta คำสั่งสำหรับ deserialization ไม่ควรมีความสำคัญ .. ยกเว้นในบางกรณีที่คาดว่าจะ "แปลก" มาก
user2864740

1
อ่าน github ที่คล้ายกันประเด็นการอภิปรายเกี่ยวกับความปรารถนาที่จะให้คำสั่งได้รับการเคารพใน deserialisation: github.com/JamesNK/Newtonsoft.Json/issues/758โดยทั่วไปไม่มีโอกาสนี้
Tyeth

128

จริงๆคุณสามารถควบคุมการสั่งซื้อโดยการใช้IContractResolverหรือเอาชนะDefaultContractResolver's CreatePropertiesวิธี

นี่คือตัวอย่างการใช้งานอย่างง่ายของฉันIContractResolverซึ่งเรียงลำดับคุณสมบัติตามตัวอักษร:

public class OrderedContractResolver : DefaultContractResolver
{
    protected override System.Collections.Generic.IList<JsonProperty> CreateProperties(System.Type type, MemberSerialization memberSerialization)
    {
        return base.CreateProperties(type, memberSerialization).OrderBy(p => p.PropertyName).ToList();
    }
}

จากนั้นตั้งค่าและจัดลำดับวัตถุและฟิลด์ JSON จะเรียงตามลำดับตัวอักษร:

var settings = new JsonSerializerSettings()
{
    ContractResolver = new OrderedContractResolver()
};

var json = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);

11
สิ่งนี้มีประโยชน์มาก (+1) แต่มีข้อแม้อย่างหนึ่ง: ดูเหมือนว่าการทำให้เป็นอนุกรมของพจนานุกรมไม่ได้ใช้การปรับแต่ง CreateProperties นี้ พวกเขาจัดลำดับได้ดี แต่ไม่ได้เรียงลำดับ ฉันคิดว่ามีวิธีอื่นในการปรับแต่งการทำให้เป็นอนุกรมของพจนานุกรม แต่ฉันไม่พบ
solublefish

สมบูรณ์แบบ. เป็นเพียงสิ่งที่ฉันต้องการ ขอบคุณ.
Wade Hatler

นี่เป็นทางออกที่ยอดเยี่ยม ทำงานได้อย่างสมบูรณ์แบบสำหรับฉันโดยเฉพาะอย่างยิ่งเมื่อวางออบเจ็กต์ JSON 2 ตัวเคียงข้างกันและวางคุณสมบัติไว้
Vince

19

ในกรณีของฉันคำตอบของ Mattias ไม่ได้ผล CreatePropertiesวิธีการก็ไม่เคยเรียกว่า

หลังจากการดีบักNewtonsoft.Jsonภายในบางส่วนฉันได้หาวิธีแก้ปัญหาอื่น

public class JsonUtility
{
    public static string NormalizeJsonString(string json)
    {
        // Parse json string into JObject.
        var parsedObject = JObject.Parse(json);

        // Sort properties of JObject.
        var normalizedObject = SortPropertiesAlphabetically(parsedObject);

        // Serialize JObject .
        return JsonConvert.SerializeObject(normalizedObject);
    }

    private static JObject SortPropertiesAlphabetically(JObject original)
    {
        var result = new JObject();

        foreach (var property in original.Properties().ToList().OrderBy(p => p.Name))
        {
            var value = property.Value as JObject;

            if (value != null)
            {
                value = SortPropertiesAlphabetically(value);
                result.Add(property.Name, value);
            }
            else
            {
                result.Add(property.Name, property.Value);
            }
        }

        return result;
    }
}

2
นี่คือการแก้ไขที่จำเป็นสำหรับเราเมื่อใช้คำสั่ง
noocyte

สิ่งนี้จะเพิ่มค่าโสหุ้ยของการแยกสารและการทำให้เป็นอนุกรมเพิ่มเติม ฉันได้เพิ่มโซลูชันที่จะใช้ได้กับคลาสปกติพจนานุกรมและ ExpandoObject (วัตถุแบบไดนามิก) ด้วย
Jay Shah

12

ในกรณีของฉันวิธีแก้ปัญหาของ niaher ไม่ได้ผลเพราะไม่ได้จัดการกับวัตถุในอาร์เรย์

จากวิธีแก้ปัญหาของเขานี่คือสิ่งที่ฉันคิดขึ้นมา

public static class JsonUtility
{
    public static string NormalizeJsonString(string json)
    {
        JToken parsed = JToken.Parse(json);

        JToken normalized = NormalizeToken(parsed);

        return JsonConvert.SerializeObject(normalized);
    }

    private static JToken NormalizeToken(JToken token)
    {
        JObject o;
        JArray array;
        if ((o = token as JObject) != null)
        {
            List<JProperty> orderedProperties = new List<JProperty>(o.Properties());
            orderedProperties.Sort(delegate(JProperty x, JProperty y) { return x.Name.CompareTo(y.Name); });
            JObject normalized = new JObject();
            foreach (JProperty property in orderedProperties)
            {
                normalized.Add(property.Name, NormalizeToken(property.Value));
            }
            return normalized;
        }
        else if ((array = token as JArray) != null)
        {
            for (int i = 0; i < array.Count; i++)
            {
                array[i] = NormalizeToken(array[i]);
            }
            return array;
        }
        else
        {
            return token;
        }
    }
}

สิ่งนี้จะเพิ่มค่าโสหุ้ยของการแยกสารและการทำให้เป็นอนุกรมเพิ่มเติม
Jay Shah

ทางออกที่ยอดเยี่ยม ขอบคุณ.
MaYaN

3

ดังที่ Charlie กล่าวไว้คุณสามารถควบคุมลำดับของคุณสมบัติ JSON ได้โดยการสั่งซื้อคุณสมบัติในคลาสเอง น่าเสียดายที่วิธีนี้ใช้ไม่ได้กับคุณสมบัติที่สืบทอดมาจากคลาสพื้นฐาน คุณสมบัติของคลาสพื้นฐานจะถูกเรียงลำดับตามที่วางไว้ในโค้ด แต่จะปรากฏก่อนคุณสมบัติคลาสพื้นฐาน

และสำหรับใครก็ตามที่สงสัยว่าทำไมคุณถึงต้องการเรียงตามตัวอักษรคุณสมบัติ JSON การทำงานกับไฟล์ JSON ดิบนั้นง่ายกว่ามากโดยเฉพาะอย่างยิ่งสำหรับคลาสที่มีคุณสมบัติมากมายหากมีการเรียงลำดับ


2

สิ่งนี้จะใช้ได้กับคลาสปกติพจนานุกรมและ ExpandoObject (วัตถุไดนามิก) เช่นกัน

class OrderedPropertiesContractResolver : DefaultContractResolver
    {
        protected override IList<JsonProperty> CreateProperties(System.Type type, MemberSerialization memberSerialization)
        {
            var props = base.CreateProperties(type, memberSerialization);
            return props.OrderBy(p => p.PropertyName).ToList();
        }
    }



class OrderedExpandoPropertiesConverter : ExpandoObjectConverter
    {
        public override bool CanWrite
        {
            get { return true; }
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            var expando = (IDictionary<string, object>)value;
            var orderedDictionary = expando.OrderBy(x => x.Key).ToDictionary(t => t.Key, t => t.Value);
            serializer.Serialize(writer, orderedDictionary);
        }
    }



var settings = new JsonSerializerSettings
        {
            ContractResolver = new OrderedPropertiesContractResolver(),
            Converters = { new OrderedExpandoPropertiesConverter() }
        };

var serializedString = JsonConvert.SerializeObject(obj, settings);

นี่ไม่ใช่พฤติกรรมการสั่งซื้อเริ่มต้นระหว่างการทำให้เป็นอนุกรมใช่หรือไม่
mr5

1
เพื่อช่วยชีวิตคนอื่นให้เสียเวลาสักสองสามนาทีโปรดทราบว่าคำตอบนี้ใช้ไม่ได้กับพจนานุกรมแม้จะมีการอ้างสิทธิ์ก็ตาม CreatePropertiesไม่ถูกเรียกใช้ระหว่างการทำให้เป็นอนุกรมของพจนานุกรม ฉันสำรวจ repo JSON.net สำหรับสิ่งที่เครื่องจักรกำลังรวบรวมผ่านรายการพจนานุกรม ไม่เกี่ยวกับการoverrideปรับแต่งใด ๆหรืออื่น ๆ สำหรับการสั่งซื้อ เพียงแค่รับรายการตามที่เป็นอยู่จากตัวแจงนับของวัตถุ ดูเหมือนว่าฉันต้องสร้างSortedDictionaryหรือSortedListบังคับให้ JSON.net ทำสิ่งนี้ ยื่นข้อเสนอแนะคุณสมบัติ: github.com/JamesNK/Newtonsoft.Json/issues/2270
William

2

หากคุณไม่ต้องการใส่JsonProperty Orderแอตทริบิวต์ให้กับคุณสมบัติทุกคลาสมันง่ายมากที่จะสร้าง ContractResolver ของคุณเอง ...

อินเทอร์เฟซ IContractResolver จัดเตรียมวิธีการปรับแต่งวิธีที่ JsonSerializer ทำให้เป็นซีเรียลไลซ์และแยกอ็อบเจ็กต์. NET เป็น JSON โดยไม่ต้องวางแอตทริบิวต์ในคลาสของคุณ

แบบนี้:

private class SortedPropertiesContractResolver : DefaultContractResolver
{
    // use a static instance for optimal performance
    static SortedPropertiesContractResolver instance;

    static SortedPropertiesContractResolver() { instance = new SortedPropertiesContractResolver(); }

    public static SortedPropertiesContractResolver Instance { get { return instance; } }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var properties = base.CreateProperties(type, memberSerialization);
        if (properties != null)
            return properties.OrderBy(p => p.UnderlyingName).ToList();
        return properties;
    }
}

ดำเนินการ:

var settings = new JsonSerializerSettings { ContractResolver = SortedPropertiesContractResolver.Instance };
var json = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);

0

วิธีการเรียกซ้ำต่อไปนี้ใช้การสะท้อนกลับเพื่อจัดเรียงรายการโทเค็นภายในบนJObjectอินสแตนซ์ที่มีอยู่แทนที่จะสร้างกราฟออบเจ็กต์ที่จัดเรียงใหม่เอี่ยม รหัสนี้อาศัยรายละเอียดการใช้งาน Json.NET ภายในและไม่ควรใช้ในการผลิต

void SortProperties(JToken token)
{
    var obj = token as JObject;
    if (obj != null)
    {
        var props = typeof (JObject)
            .GetField("_properties",
                      BindingFlags.NonPublic | BindingFlags.Instance)
            .GetValue(obj);
        var items = typeof (Collection<JToken>)
            .GetField("items", BindingFlags.NonPublic | BindingFlags.Instance)
            .GetValue(props);
        ArrayList.Adapter((IList) items)
            .Sort(new ComparisonComparer(
                (x, y) =>
                {
                    var xProp = x as JProperty;
                    var yProp = y as JProperty;
                    return xProp != null && yProp != null
                        ? string.Compare(xProp.Name, yProp.Name)
                        : 0;
                }));
    }
    foreach (var child in token.Children())
    {
        SortProperties(child);
    }
}

0

อันที่จริงเนื่องจาก Object ของฉันเป็น JObject อยู่แล้วฉันจึงใช้วิธีแก้ปัญหาต่อไปนี้:

public class SortedJObject : JObject
{
    public SortedJObject(JObject other)
    {
        var pairs = new List<KeyValuePair<string, JToken>>();
        foreach (var pair in other)
        {
            pairs.Add(pair);
        }
        pairs.OrderBy(p => p.Key).ForEach(pair => this[pair.Key] = pair.Value);
    }
}

แล้วใช้มันดังนี้:

string serializedObj = JsonConvert.SerializeObject(new SortedJObject(dataObject));

0

ถ้าคุณควบคุม (เช่นเขียน) ชั้นเรียนให้วางคุณสมบัติตามลำดับตัวอักษรและจะเรียงลำดับตามลำดับตัวอักษรเมื่อJsonConvert.SerializeObject()ถูกเรียก


0

ฉันต้องการทำให้เป็นอนุกรมของวัตถุ comblex และรักษาลำดับของคุณสมบัติตามที่กำหนดไว้ในโค้ด ฉันไม่สามารถเพิ่มได้[JsonProperty(Order = 1)]เพราะชั้นเรียนนั้นอยู่นอกขอบเขตของฉัน

โซลูชันนี้ยังคำนึงถึงคุณสมบัติที่กำหนดไว้ในคลาสพื้นฐานควรมีลำดับความสำคัญสูงกว่า

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

public class PersonWithAge : Person
{
    public int Age { get; set; }
}

public class Person
{
    public string Name { get; set; }
}

public string GetJson()
{
    var thequeen = new PersonWithAge { Name = "Elisabeth", Age = Int32.MaxValue };

    var settings = new JsonSerializerSettings()
    {
        ContractResolver = new MetadataTokenContractResolver(),
    };

    return JsonConvert.SerializeObject(
        thequeen, Newtonsoft.Json.Formatting.Indented, settings
    );

}

public class MetadataTokenContractResolver : DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(
        Type type, MemberSerialization memberSerialization)
    {
        var props = type
           .GetProperties(BindingFlags.Instance
               | BindingFlags.Public
               | BindingFlags.NonPublic
           ).ToDictionary(k => k.Name, v =>
           {
               // first value: declaring type
               var classIndex = 0;
               var t = type;
               while (t != v.DeclaringType)
               {
                   classIndex++;
                   t = type.BaseType;
               }
               return Tuple.Create(classIndex, v.MetadataToken);
           });

        return base.CreateProperties(type, memberSerialization)
            .OrderByDescending(p => props[p.PropertyName].Item1)
            .ThenBy(p => props[p.PropertyName].Item1)
            .ToList();
    }
}


-1

หากคุณต้องการกำหนดค่า API ของคุณทั่วโลกด้วยฟิลด์ที่สั่งซื้อโปรดรวมคำตอบของ Mattias Nordberg:

public class OrderedContractResolver : DefaultContractResolver
{
    protected override System.Collections.Generic.IList<JsonProperty> CreateProperties(System.Type type, MemberSerialization memberSerialization)
    {
        return base.CreateProperties(type, memberSerialization).OrderBy(p => p.PropertyName).ToList();
    }
}

ด้วยคำตอบของฉันที่นี่:

จะบังคับให้ ASP.NET Web API ส่งคืน JSON ได้อย่างไร


-5

อัปเดต

ฉันเพิ่งเห็นการโหวตลดลง โปรดดูคำตอบจาก 'สตีฟ' ด้านล่างสำหรับวิธีดำเนินการนี้

เดิม

ฉันติดตามJsonConvert.SerializeObject(key)การเรียกใช้เมธอดผ่านการสะท้อน (โดยที่คีย์คือ IList) และพบว่ามีการเรียก JsonSerializerInternalWriter.SerializeList ใช้รายการและวนซ้ำผ่าน

for (int i = 0; i < values.Count; i++) { ...

โดยที่ค่าคือพารามิเตอร์ IList ที่นำเข้ามา

คำตอบสั้น ๆ คือ ... ไม่ไม่มีวิธีในการตั้งค่าลำดับฟิลด์ที่ระบุไว้ในสตริง JSON


18
คำตอบสั้น ๆ แต่อาจล้าสมัย ตรวจสอบคำตอบของ Steve (สนับสนุนโดย James Newton-king)
Brad Bruce

-6

ไม่มีลำดับของฟิลด์ในรูปแบบ JSON ดังนั้นการกำหนดลำดับจึงไม่สมเหตุสมผล

{ id: 1, name: 'John' }เทียบเท่ากับ{ name: 'John', id: 1 }(ทั้งคู่แสดงถึงอินสแตนซ์วัตถุที่เทียบเท่ากันอย่างเคร่งครัด)


12
@Darin - แต่มีคำสั่งใน serialization "{id: 1, name: 'John'}" และ "{name: 'John', id: 1}" ต่างกันเป็นสตริงซึ่งเป็นสิ่งที่ฉันสนใจที่นี่ แน่นอนว่าวัตถุจะเทียบเท่ากันเมื่อ deserialized
Kevin Montrose

1
@ ดาริน - ไม่ไม่ใช่ในกรณีนี้ ฉันกำลังจัดลำดับบางสิ่งบางอย่างแล้วส่งต่อเป็นสตริงไปยังบริการที่ทำข้อตกลงในสตริงเท่านั้น (ไม่ใช่ JSON ที่ทราบ) และจะสะดวกด้วยเหตุผลหลายประการที่ฟิลด์หนึ่งจะปรากฏก่อนในสตริง
Kevin Montrose

1
เหมาะสำหรับการทดสอบเช่นกันคือสามารถดูสตริงได้แทนที่จะต้อง deserialize
Steve

9
ลำดับการทำให้เป็นอนุกรมที่เสถียรมีประโยชน์สำหรับการตรวจสอบแคชด้วย การตรวจสอบสตริงของสตริงเป็นเรื่องเล็กน้อย - ไม่เป็นความจริงของกราฟออบเจ็กต์แบบเต็ม
solublefish

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