JSON.NETを使用したシリアル化フィールドの順序


138

JSON.NETを使用してシリアル化されたJSONオブジェクトのフィールドの順序を指定する方法はありますか?

単一のフィールドが常に最初に表示されるように指定するだけで十分です。


7
私はおそらく彼が最初にIDフィールド(または類似のもの)を表示し、次に他のすべてのフィールドを表示することに興味があると思います。これは、A..Iで始まるフィールドの後にそれを探すよりも、エンドユーザーにとって使いやすい
Michael Bahig

3
JSONプロパティは、順序付けされていないと定義されています。(おそらくJSONを目立たせるために)シリアル化中に特定のOUTPUT命令を強制することは絶対に問題ないと思いますが、逆シリアル化の特定の順序に依存関係を作成するのは悪い決定です。
DaBlick 2015年

5
いくつかの正当な理由:(1)JSONの最初のプロパティである必要がある「$ type」プロパティを偽造している、(2)可能な限り圧縮するJSONを生成しようとしている
Stephen Chung

4
別の理由としては、(3)JSON構文を使用する正規表現が考えられます。同じオブジェクトが同じJSON文字列を生成することが保証されている必要があります。これには、属性の確定的な順序が必要な前提条件です。
MarkusSchaber 2018年

2
ケビン、この質問の承認済み回答を更新できますか?
ミリースミス

回答:


256

サポートされている方法は、 JsonProperty、順序を設定するクラスプロパティの属性ことです。詳細については、JsonPropertyAttribute注文ドキュメントをご覧ください。

渡しJsonPropertyANのOrder値をとシリアライザは、残りの世話をします。

 [JsonProperty(Order = 1)]

これは

 DataMember(Order = 1) 

System.Runtime.Serialization日。

ここに@ kevin-babcockからの重要なメモがあります

...順序を1に設定すると、他のすべてのプロパティで1より大きい順序を設定した場合にのみ機能します。デフォルトでは、Order設定のないプロパティには、-1の順序が与えられます。したがって、シリアル化されたすべてのプロパティと順序を指定するか、最初の項目を-2に設定する必要があります


97
Orderプロパティを使用すると、JsonPropertyAttributeフィールドがシリアル化/非シリアル化される順序を制御できます。ただし、順序を1に設定すると、他のすべてのプロパティで1より大きい順序を設定した場合にのみ機能します。デフォルトでは、Order設定のないプロパティには、-1の順序が与えられます。したがって、シリアル化されたすべてのプロパティと順序を指定するか、最初の項目を-2に設定する必要があります。
ケビンバブコック2014

1
シリアル化では機能しますが、逆シリアル化では順序は考慮されていません。ドキュメントによると、order属性はシリアライズとデシリアライズの両方に使用されます。回避策はありますか?
cangosta 2015

1
の同様のプロパティはありJavaScriptSerializerますか?
Shimmy Weitzhandler 2015

4
@cangosta逆シリアル化の順序は、非常に「奇妙な」期待の場合を除いて、重要ではありません。
user2864740

1
ご注文は、デシリアライゼーションで尊重されるための欲望の周りに同様のgithubの問題の議論を読むgithub.com/JamesNK/Newtonsoft.Json/issues/758基本的にこの1のチャンスがありません。
Tyeth、

126

のメソッドを実装IContractResolverまたはオーバーライドすることで、実際に順序を制御できます。DefaultContractResolverCreateProperties

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)が、1つの注意点があります。辞書のシリアル化では、このCreatePropertiesカスタマイズが使用されていないようです。それらは正常にシリアライズされますが、ソートされません。辞書のシリアル化をカスタマイズする別の方法があると思いますが、見つかりませんでした。
水溶性魚

完璧です。私が欲しかったことだけをします。ありがとう。
ウェイドハトラー、2016年

これは素晴らしいソリューションです。特に、2つのJSONオブジェクトを並べて配置し、プロパティを並べる場合、私にとっては完璧に機能しました。
ビンス

16

私の場合、マティアスの答えはうまくいきませんでした。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
これは、dictsを使用するときに必要な修正でした。
noocyte

これにより、追加の逆シリアル化とシリアル化のオーバーヘッドが追加されます。通常のクラス、辞書、ExpandoObject(動的オブジェクト)でも機能するソリューションを追加しました
Jay Shah

11

私の場合、ナイアハーのソリューションは配列内のオブジェクトを処理しなかったため、機能しませんでした。

彼の解決策に基づいて、これは私が思いついたものです

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辞書のシリアライズ中には呼び出されません。JSON.netリポジトリーを調べて、実際に辞書の項目を機械で動かしていないものを探しました。それはoverride注文のために、または他のカスタマイズにフックしません。オブジェクトの列挙子からエントリをそのまま取得します。JSON.netを作成するSortedDictionarySortedList、強制的に実行する必要があるようです。提案された機能の提案: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

実際、私のオブジェクトはすでに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が呼び出されることがわかりました。リストを受け取り、viaを介してループします

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

ここで、valuesは、取り込まれたIListパラメータです。

短い答えは...いいえ、JSON文字列にフィールドがリストされる順序を設定する組み込みの方法はありません。


18
短い答えですが、おそらく時代遅れです。スティーブの答え(ジェームズニュートンキングがサポート)をチェックしてください
ブラッドブルース

-6

JSON形式にはフィールドの順序がないため、順序を定義しても意味がありません。

{ id: 1, name: 'John' }と等価です{ name: 'John', id: 1 }(どちらも厳密に等価なオブジェクトインスタンスを表します)


12
@Darin-シリアル化に順序があります。"{id:1、name: 'John'}"と "{name: 'John'、id:1}"は、文字列としては異なります。もちろん、オブジェクトは逆シリアル化されたときに同等です。
ケビンモントローズ

1
@Darin-いいえ、この場合ではありません。何かをシリアル化し、それを文字列として処理するサービス(JSON対応ではない)に文字列として渡します。さまざまな理由から、1つのフィールドが文字列の最初に表示されるのは便利です。
ケビンモントローズ

1
逆シリアル化する必要がなく、文字列を見ることができるため、テストにも適しています。
Steve

9
安定したシリアル化の順序は、キャッシュの検証にも便利です。文字列のチェックサムを取ることは簡単です-完全なオブジェクトグラフには当てはまりません。
可溶性魚

1
シリアル化の順序は、単体テストを実行するときにも便利です。jsonプロパティの順序が異なっていても、期待される応答文字列と実際の応答文字列は等しいと簡単に言えます。
anon 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.