Urutan bidang serial menggunakan JSON.NET

138

Apakah ada cara untuk menentukan urutan bidang dalam objek JSON berseri menggunakan JSON.NET ?

Itu akan cukup untuk menentukan bahwa satu bidang selalu muncul terlebih dahulu.

Kevin Montrose
sumber
7
saya pikir dia mungkin tertarik untuk menunjukkan bidang ID (atau serupa) pertama, dan kemudian semua bidang lainnya. ini lebih ramah untuk pengguna akhir daripada mencarinya setelah bidang yang dimulai dengan A..I
Michael Bahig
3
Properti JSON didefinisikan sebagai tidak berurutan. Saya pikir itu benar-benar baik-baik saja untuk memaksakan perintah OUTPUT tertentu selama serialisasi (demi melihat JSON mungkin), tapi itu akan menjadi keputusan yang buruk untuk membuat DEPENDENSI pada urutan tertentu tentang deserialisasi.
DaBlick
5
Beberapa alasan yang valid: (1) memalsukan properti "$ type" yang harus menjadi properti pertama di JSON, (2) mencoba menghasilkan JSON yang mengompres sebanyak mungkin
Stephen Chung
4
Alasan lain mungkin (3) representasi kanonik yang menggunakan sintaks JSON - objek yang sama harus dijamin untuk menghasilkan string JSON yang sama. Urutan atribut yang deterministik merupakan prasyarat yang diperlukan untuk ini.
MarkusSchaber
2
Kevin, dapatkah Anda memperbarui jawaban yang diterima pada pertanyaan ini?
Millie Smith

Jawaban:

256

Cara yang didukung adalah dengan menggunakan JsonPropertyatribut pada properti kelas yang ingin Anda atur urutannya. Baca dokumentasi pesanan JsonPropertyAttribute untuk informasi lebih lanjut.

Lulus JsonPropertysebuah Ordernilai dan serializer akan mengurus sisanya.

 [JsonProperty(Order = 1)]

Ini sangat mirip dengan

 DataMember(Order = 1) 

System.Runtime.Serializationhari - hari.

Ini adalah catatan penting dari @ kevin-babcock

... mengatur pesanan ke 1 hanya akan berfungsi jika Anda menetapkan pesanan lebih besar dari 1 pada semua properti lainnya. Secara default, properti apa pun tanpa pengaturan Pesanan akan diberi urutan -1. Jadi, Anda harus memberikan semua properti dan pesanan berseri, atau mengatur item pertama Anda ke -2

Steve
sumber
97
Menggunakan Orderproperti dari JsonPropertyAttributedapat digunakan untuk mengontrol urutan bidang yang serial / deserialized. Namun, mengatur pesanan ke 1 hanya akan berfungsi jika Anda menetapkan pesanan lebih besar dari 1 pada semua properti lainnya. Secara default, properti apa pun tanpa pengaturan Pesanan akan diberi urutan -1. Jadi, Anda harus memberikan semua properti dan pesanan berseri, atau mengatur item pertama Anda ke -2.
Kevin Babcock
1
Ini berfungsi untuk serialisasi, tetapi urutannya tidak dipertimbangkan pada deserialisasi. Menurut dokumentasi, atribut urutan digunakan untuk serialisasi dan deserialisasi. Apakah ada solusinya?
cangosta
1
Apakah ada properti serupa untuk JavaScriptSerializer.
Shimmy Weitzhandler
4
@cangosta Urutan untuk deserialisasi seharusnya tidak penting .. kecuali dalam beberapa kasus harapan yang sangat "aneh".
user2864740
1
Baca diskusi masalah github serupa di sekitar keinginan agar Orde dihormati dalam deserialisasi: github.com/JamesNK/Newtonsoft.Json/issues/758 Pada dasarnya tidak ada kesempatan untuk ini.
Tyeth
126

Anda benar-benar dapat mengontrol urutan dengan menerapkan IContractResolveratau override DefaultContractResolver's CreatePropertiesmetode.

Berikut adalah contoh implementasi sederhana saya IContractResolveryang memesan properti secara alfabet:

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();
    }
}

Dan kemudian mengatur pengaturan dan membuat serial objek, dan bidang JSON akan berada dalam urutan abjad:

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

var json = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
Mattias Nordberg
sumber
11
Ini cukup membantu (+1) tetapi satu peringatan: tampaknya serialisasi kamus tidak menggunakan kustomisasi CreateProperties ini. Mereka membuat serial baik-baik saja tetapi tidak berakhir diurutkan. Saya berasumsi ada cara berbeda untuk menyesuaikan serialisasi kamus, tetapi saya belum menemukannya.
solublefish
Sempurna. Melakukan apa yang saya inginkan. Terima kasih.
Wade Hatler
Ini solusi hebat. Bekerja dengan sempurna untuk saya terutama ketika meletakkan 2 objek JSON berdampingan dan memiliki properti yang sejajar.
Vince
16

Dalam kasus saya, jawaban Mattias tidak berhasil. The CreatePropertiesMetode tidak pernah disebut.

Setelah debugging Newtonsoft.Jsoninternal, saya datang dengan solusi lain.

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;
    }
}
lebih lugu
sumber
2
Ini adalah perbaikan yang diperlukan bagi kami saat menggunakan dikt.
noocyte
Ini menambah biaya tambahan deserialisasi dan serialisasi tambahan. Saya telah menambahkan solusi yang akan bekerja untuk kelas normal, kamus dan ExpandoObject (objek dinamis) juga
Jay Shah
11

Dalam kasus saya, solusi niaher tidak berfungsi karena tidak menangani objek dalam array.

Berdasarkan solusinya, inilah yang saya hasilkan

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;
        }
    }
}
Tuan-Tu Tran
sumber
Ini menambah biaya tambahan deserialisasi dan serialisasi tambahan.
Jay Shah
Solusi yang sangat baik. Terima kasih.
MaYaN
3

Seperti yang dicatat Charlie, Anda dapat mengontrol urutan properti JSON dengan memesan properti di kelas itu sendiri. Sayangnya, pendekatan ini tidak berfungsi untuk properti yang diwarisi dari kelas dasar. Properti kelas dasar akan dipesan karena mereka diletakkan dalam kode, tetapi akan muncul sebelum properti kelas dasar.

Dan bagi siapa pun yang bertanya-tanya mengapa Anda mungkin ingin mengabjadkan properti JSON, jauh lebih mudah untuk bekerja dengan file JSON mentah, terutama untuk kelas dengan banyak properti, jika dipesan.

Jack Bond
sumber
2

Ini akan berfungsi untuk kelas normal, kamus dan ExpandoObject (objek dinamis) juga.

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);
Jay Shah
sumber
Bukankah ini perilaku pemesanan default selama serialisasi?
mr5
1
Untuk menghemat waktu beberapa menit, perhatikan bahwa jawaban ini tidak berfungsi untuk kamus meskipun ada klaim. CreatePropertiestidak dipanggil selama serialisasi kamus. Saya menjelajahi repo JSON.net untuk mesin apa yang sebenarnya menembus entri kamus. Itu tidak terhubung ke overridekustomisasi apa pun atau lainnya untuk memesan. Itu hanya mengambil entri apa adanya dari pencacah objek. Sepertinya saya harus membuat SortedDictionaryatau SortedListmemaksa JSON.net untuk melakukan ini. Saran fitur diajukan: github.com/JamesNK/Newtonsoft.Json/issues/2270
William
2

Jika Anda tidak ingin menempatkan JsonProperty Orderatribut pada setiap properti kelas, maka sangat mudah untuk membuat ContractResolver Anda sendiri ...

Antarmuka IContractResolver menyediakan cara untuk menyesuaikan bagaimana JsonSerializer membuat serial dan deserializes .NET objek ke JSON tanpa menempatkan atribut pada kelas Anda.

Seperti ini:

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;
    }
}

Melaksanakan:

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

Metode rekursif berikut menggunakan refleksi untuk mengurutkan daftar token internal pada JObjectinstance yang ada daripada membuat grafik objek yang diurutkan baru. Kode ini bergantung pada detail implementasi Json.NET internal dan tidak boleh digunakan dalam produksi.

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);
    }
}
Nathan Baulch
sumber
0

Sebenarnya, karena Object saya sudah menjadi JObject, saya menggunakan solusi berikut:

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);
    }
}

dan kemudian gunakan seperti ini:

string serializedObj = JsonConvert.SerializeObject(new SortedJObject(dataObject));
Danny R
sumber
0

Jika Anda mengontrol (yaitu menulis) kelas, letakkan properti dalam urutan abjad dan mereka akan berseri dalam urutan abjad ketika JsonConvert.SerializeObject()dipanggil.

Charlie
sumber
0

Saya ingin membuat serial objek comblex dan menjaga urutan properti seperti yang didefinisikan dalam kode. Saya tidak bisa menambahkan [JsonProperty(Order = 1)]karena kelas itu sendiri berada di luar jangkauan saya.

Solusi ini juga memperhitungkan bahwa properti yang didefinisikan dalam kelas dasar harus memiliki prioritas yang lebih tinggi.

Ini mungkin bukan antipeluru, karena tidak ada tempat yang MetaDataAttributememastikan bahwa urutan yang benar, tetapi tampaknya berfungsi. Untuk kasus penggunaan saya ini ok. karena saya hanya ingin mempertahankan keterbacaan manusia untuk file konfigurasi yang dihasilkan secara otomatis.

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();
    }
}

Jürgen Steinblock
sumber
-1

Jika Anda ingin mengonfigurasi API Anda secara global dengan bidang yang dipesan, silakan gabungkan jawaban 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();
    }
}

dengan jawaban saya di sini:

Bagaimana cara memaksa ASP.NET Web API untuk selalu mengembalikan JSON?

Carlo Saccone
sumber
-5

MEMPERBARUI

Saya baru saja melihat downvotes. Silakan lihat jawaban dari 'Steve' di bawah untuk cara melakukan ini.

ASLI

Saya mengikuti JsonConvert.SerializeObject(key)pemanggilan metode melalui refleksi (di mana kuncinya adalah IList) dan menemukan bahwa JsonSerializerInternalWriter.SerializeList dipanggil. Dibutuhkan daftar dan loop melalui

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

di mana nilai adalah parameter IList yang dibawa.

Jawaban singkatnya adalah ... Tidak, tidak ada cara untuk mengatur urutan kolom yang tercantum dalam string JSON.

DougJones
sumber
18
Jawaban singkat, tetapi mungkin sudah ketinggalan zaman. Lihatlah jawaban Steve (didukung oleh James Newton-king)
Brad Bruce
-6

Tidak ada urutan bidang dalam format JSON sehingga mendefinisikan pesanan tidak masuk akal.

{ id: 1, name: 'John' }setara dengan { name: 'John', id: 1 }(keduanya mewakili instance objek yang sangat setara)

Darin Dimitrov
sumber
12
@Darin - tetapi ada perintah dalam serialisasi. "{id: 1, name: 'John'}" dan "{name: 'John', id: 1}" berbeda dengan string , yang merupakan perhatian saya di sini. Tentu saja, benda-benda itu setara ketika deserialized.
Kevin Montrose
1
@Darin - tidak, tidak dalam kasus ini. Saya membuat serial sesuatu dan kemudian meneruskannya sebagai string ke layanan yang hanya berurusan dengan string (bukan JSON sadari), dan akan lebih mudah untuk berbagai alasan untuk satu bidang muncul pertama kali dalam string.
Kevin Montrose
1
itu bagus untuk pengujian juga, bisa hanya melihat string daripada harus deserialize.
Steve
9
Urutan serialisasi yang stabil juga berguna untuk validasi cache. Ini sepele untuk mengambil checksum dari sebuah string - tidak berlaku untuk grafik objek penuh.
solublefish
1
Urutan serialisasi juga berguna saat melakukan tes unit sehingga Anda dapat dengan mudah mengatakan bahwa string respons yang diharapkan vs yang sebenarnya sama bahkan ketika urutan properti json berbeda.
anon