JSON.NET kullanarak serileştirilmiş alanların sırası


138

JSON.NET kullanarak serileştirilmiş bir JSON nesnesindeki alanların sırasını belirtmek için bir yolu var mı ?

Her zaman önce tek bir alanın görüneceğini belirtmek yeterli olacaktır.


7
muhtemelen önce ID alanını (veya benzerlerini) ve sonra diğer tüm alanları göstermekle ilgilendiğini düşünüyorum. Bu, A..I ile başlayan alanlardan sonra son kullanıcılar için daha dostudur
Michael Bahig

3
JSON özellikleri sırasız olarak tanımlanır. Serileştirme sırasında belirli bir ÇIKIŞ emrini zorlamak iyi olur (belki de JSON'u göz ardı etmek için), ancak serileştirme ile ilgili herhangi bir siparişte BAĞIMLILIK oluşturmak kötü bir karar olacaktır.
DaBlick

5
Birkaç geçerli neden: (1) JSON'daki ilk mülk olması gereken bir "$ type" mülkü taklit etmek, (2) mümkün olduğunca sıkıştıran JSON üretmeye çalışmak
Stephen Chung

4
Başka bir neden de (3) JSON sözdizimini kullanan standart bir gösterim olabilir - aynı nesnenin aynı JSON dizesini üreteceği garanti edilmelidir. Niteliklerin belirleyici bir sırası bunun için gerekli bir ön koşuldur.
MarkusSchaber

2
Kevin, bu soruya kabul edilen cevabı güncelleyebilir misin?
Millie Smith

Yanıtlar:


256

Desteklenen yol, JsonPropertyniteliğini, sırasını ayarlamak istediğiniz sınıf özelliklerinde kullanmaktır . Daha fazla bilgi için JsonPropertyAttribute sipariş belgelerini okuyun .

Geçiş JsonPropertybir Orderdeğeri ve seri hale gerisini halleder.

 [JsonProperty(Order = 1)]

Bu çok benzer

 DataMember(Order = 1) 

arasında System.Runtime.Serializationgünlerde.

İşte @ kevin-babcock'dan önemli bir not

... siparişin 1 olarak ayarlanması yalnızca diğer tüm özellikler için 1'den büyük bir sipariş ayarlarsanız çalışır. Varsayılan olarak, Sipariş ayarı olmayan herhangi bir özelliğe -1 sırası verilir. Bu nedenle, tüm serileştirilmiş özellikleri ve siparişi vermeli veya ilk öğenizi -2 olarak ayarlamalısınız


97
' In Orderözelliğini kullanmak, JsonPropertyAttributealanların serileştirildiği / serileştirildiği sırayı kontrol etmek için kullanılabilir. Ancak, siparişin 1 olarak ayarlanması yalnızca diğer tüm özellikler için 1'den büyük bir sipariş ayarladıysanız çalışır. Varsayılan olarak, Sipariş ayarı olmayan herhangi bir özelliğe -1 sırası verilir. Bu nedenle, tüm serileştirilmiş özellikleri ve siparişi vermeli veya ilk öğenizi -2 olarak ayarlamalısınız.
Kevin Babcock

1
Serileştirme için çalışır, ancak serileştirme siparişi dikkate alınmaz. Belgelere göre, sipariş niteliği hem serileştirme hem de serileştirme için kullanılır. Bir çözüm var mı?
cangosta

1
İçin benzer bir özellik var mı JavaScriptSerializer?
Shimmy Weitzhandler

4
@cangosta Serileştirme sırası önemli olmamalı .. bazı "garip" beklenti durumları dışında.
user2864740

1
Diziselleştirmede Sipariş'e saygı duyulma arzusu hakkındaki benzer github sorunları tartışmasını okuyun: github.com/JamesNK/Newtonsoft.Json/issues/758 Temelde bu şansı yok.
Tyeth

126

'S yöntemini uygulayarak IContractResolverveya geçersiz kılarak siparişi kontrol edebilirsiniz .DefaultContractResolverCreateProperties

İşte IContractResolverözelliklerinin alfabetik olarak sıralandığı basit uygulamamın bir örneği :

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

Ardından ayarları yapın ve nesneyi serileştirin; JSON alanları alfabetik sırada olacaktır:

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

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

11
Bu oldukça yararlı (+1) ancak bir uyarı: sözlüklerin serileştirilmesi bu CreateProperties özelleştirmesini kullanmıyor gibi görünüyor. Onlar seri hale ama sıralı bitmiyor. Sözlüklerin serileştirilmesini özelleştirmek için farklı bir yol olduğunu düşünüyorum, ancak bulamadım.
çözünür

Mükemmel. Sadece istediğimi yapar. Teşekkürler.
Wade Hatler

Bu harika bir çözüm. Özellikle 2 JSON nesnesini yan yana koyarak ve özellikleri hizalarken benim için mükemmel çalıştı.
Vince

16

Benim durumumda Mattias'ın cevabı işe yaramadı. CreatePropertiesYöntem hiç çağrılmadı.

Newtonsoft.JsonDahili hata ayıklamadan sonra başka bir çözüm buldum.

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
Bu, dicts kullanırken bizim için gerekli düzeltmeydi.
noocyte

Bu, ek serileştirme ve serileştirme ek yükü ekler. Normal sınıflar, sözlükler ve ExpandoObject (dinamik nesne) için de işe yarayacak bir çözüm ekledim
Jay Shah

11

Benim durumumda, niaher'ın çözümü işe yaramadı çünkü dizilerdeki nesneleri işlemedi.

Onun çözümüne dayanarak bulduğum şey bu

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

Bu, ek serileştirme ve serileştirme ek yükü ekler.
Jay Shah

Mükemmel çözüm. Teşekkür ederim.
MaYaN

3

Charlie'nin belirttiği gibi, sınıfın kendisindeki özellikleri sipariş ederek JSON özelliklerinin sırasını biraz kontrol edebilirsiniz. Ne yazık ki, bu yaklaşım bir temel sınıftan miras alınan mülkler için geçerli değildir. Temel sınıf özellikleri, kodda belirtildiği gibi sıralanır, ancak temel sınıf özelliklerinden önce görünür.

Ve neden JSON özelliklerini alfabetikleştirmek isteyebileceğinizi merak eden herkes için, sipariş edilirlerse, özellikle çok sayıda özelliğe sahip sınıflar için ham JSON dosyalarıyla çalışmak çok daha kolaydır.


2

Bu normal sınıflar, sözlükler ve ExpandoObject (dinamik nesne) için de işe yarayacaktır.

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

Serileştirme sırasında varsayılan sipariş davranışı bu değil miydi?
mr5

1
Bir başkasını birkaç dakika boşa harcamak için, bu cevabın iddialara rağmen sözlükler için işe yaramadığını unutmayın. CreatePropertiessözlüğün serileştirilmesi sırasında çağrılmaz. JSON.net repo'yu, makine girişleri üzerinden gerçekte ne çektiğini keşfettim. overrideSipariş için herhangi bir veya başka bir özelleştirmeye bağlanmaz. Yalnızca girişleri nesnenin numaralandırıcısından olduğu gibi alır. Görünüşe göre bunu yapmak için JSON.net'i zorlamak SortedDictionaryya SortedListda zorlamak zorundayım. Özellik öneri yayınlandı: github.com/JamesNK/Newtonsoft.Json/issues/2270
William

2

JsonProperty OrderHer sınıf mülke bir özellik koymak istemiyorsanız , o zaman kendi ContractResolver'ınızı yapmak çok basit ...

IContractResolver arabirimi, JsonSerializer'ın sınıflarınıza öznitelik yerleştirmeden .NET nesnelerini JSON'a nasıl serileştirdiğini ve serileştirdiğini özelleştirmek için bir yol sağlar.

Bunun gibi:

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

Uygulanışı:

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

0

Aşağıdaki özyinelemeli yöntem, JObjectyepyeni bir sıralanmış nesne grafiği oluşturmak yerine , dahili belirteç listesini var olan bir örnekte sıralamak için yansıma kullanır . Bu kod, dahili Json.NET uygulama ayrıntılarına dayanır ve üretimde kullanılmamalıdır.

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

Aslında, Nesnem zaten bir JObject olduğundan, aşağıdaki çözümü kullandım:

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

ve sonra şu şekilde kullanın:

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

0

Sınıfı kontrol ederseniz (yani yazarsanız), özellikleri alfabetik sıraya koyarsanız JsonConvert.SerializeObject(), çağrıldığında alfabetik sıraya göre serileştirilir .


0

Bir comblex nesnesini serileştirmek ve kodda tanımlandığı gibi özelliklerin sırasını tutmak istiyorum. Ekleyemiyorum [JsonProperty(Order = 1)]çünkü sınıfın kendisi kapsamımın dışında.

Bu çözüm ayrıca, bir temel sınıfta tanımlanan özelliklerin daha yüksek önceliğe sahip olması gerektiğini de dikkate alır.

Bu, kurşun geçirmez olmayabilir, çünkü hiçbir yerde MetaDataAttributedoğru siparişin temin edildiği tanımlanmamıştır , ancak işe yarıyor gibi görünüyor. Benim kullanım durumum için bu tamam. çünkü sadece otomatik olarak oluşturulmuş bir yapılandırma dosyası için okunabilirliği korumak istiyorum.

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'nızı sipariş edilen alanlarla global olarak yapılandırmak istiyorsanız, lütfen Mattias Nordberg yanıtını birleştirin:

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

cevabım burada:

ASP.NET Web API'sini her zaman JSON döndürmeye zorlama


-5

GÜNCELLEME

Az önce yokuşları gördüm. Bunun nasıl yapılacağı için lütfen aşağıdaki 'Steve'in cevabına bakınız.

ORİJİNAL

JsonConvert.SerializeObject(key)Yöntem çağrısını yansıma ile izledim (burada anahtar bir IList idi) ve JsonSerializerInternalWriter.SerializeList'in çağrıldığını buldum. Bir liste alır ve üzerinden dolaşır

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

burada değerler getirilen IList parametresidir.

Kısa cevap ... Hayır, alanların JSON dizesinde listelenen sıralamayı ayarlamak için yerleşik bir yöntem yoktur.


18
Kısa cevap, ama muhtemelen eski. Steve'in cevabına göz atın (James Newton kralı tarafından desteklenir)
Brad Bruce

-6

JSON biçimindeki alanların sırası yoktur, bu nedenle bir sipariş tanımlamak mantıklı değildir.

{ id: 1, name: 'John' }eşittir { name: 'John', id: 1 }(her ikisi de kesinlikle eşdeğer bir nesne örneğini temsil eder)


12
@Darin - ama serileştirmede bir emir var. "{id: 1, name: 'John'}" ve "{name: 'John', id: 1}", dizgelerden farklı , burada önem verdiğim şey bu. Tabii ki, serileri kaldırıldığında nesneler eşdeğerdir.
Kevin Montrose

1
@Darin - hayır, bu durumda değil. Ben bir şey serileştirme ve daha sonra sadece dizeleri (JSON farkında değil) fırsatlar bir hizmete bir dize olarak geçirerek ve bir alanda dize ilk görünmesi için çeşitli nedenlerle uygun olurdu.
Kevin Montrose

1
aynı zamanda test için de iyidir, seriyi kaldırmak zorunda kalmak yerine sadece dizelere bakabilmek.
Steve

9
Sabit bir serileştirme sırası da önbellek doğrulaması için kullanışlıdır. Tam bir nesne grafiği için doğru değil - bir dizenin sağlama toplamını almak önemsizdir.
çözünür

1
Serileştirme sırası da birim testleri yaparken kullanışlıdır, böylece json özelliklerinin sırası farklı olsa bile beklenen yanıtla gerçek yanıt dizelerinin eşit olduğunu kolayca söyleyebilirsiniz.
anon
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.