JSON.NET을 사용한 직렬화 된 필드의 순서


137

JSON.NET을 사용하여 직렬화 된 JSON 객체에서 필드 순서를 지정하는 방법이 있습니까?

단일 필드가 항상 먼저 표시되도록 지정하면 충분합니다.


7
그는 아마도 ID 필드 (또는 비슷한)를 먼저 표시 한 다음 다른 모든 필드를 표시하는 데 관심이 있다고 생각합니다. 이 A..I로 시작 필드 후 찾고 이상의 최종 사용자 친화적이다
마이클 Bahig

3
JSON 속성은 정렬되지 않은 것으로 정의됩니다. 직렬화 중에 특정 JSON 출력을 강제하는 것이 절대적으로 좋다고 생각하지만 (직렬 JSON을 보는 경우) 직렬화 해제의 특정 순서에 따라 DEPENDENCY를 작성하는 것은 나쁜 결정이 될 것입니다.
DaBlick

5
몇 가지 유효한 이유 : (1) JSON에서 첫 번째 속성이어야하는 "$ type"속성을 가짜로 만드는 것, (2) 가능한 한 많이 압축하는 JSON을 생성하려고 함
Stephen Chung

4
또 다른 이유는 (3) JSON 구문을 사용하는 표준 표현 일 수 있습니다. 동일한 객체가 동일한 JSON 문자열을 생성하도록 보장해야합니다. 속성의 결정 론적 순서는이를위한 필수 전제 조건입니다.
MarkusSchaber

2
Kevin,이 질문에 대한 답변을 업데이트 할 수 있습니까?
Millie Smith

답변:


255

지원되는 방법은 JsonProperty순서를 설정하려는 클래스 특성 에서 속성 을 사용하는 것입니다. 자세한 정보 는 JsonPropertyAttribute 주문 문서 를 읽으십시오 .

패스 JsonPropertyOrder값과 시리얼 라이저는 나머지를 다룰 것이다.

 [JsonProperty(Order = 1)]

이것은 매우 유사합니다

 DataMember(Order = 1) 

System.Runtime.Serialization일.

@ kevin-babcock의 중요한 메모는 다음과 같습니다.

... 순서를 1로 설정하면 다른 모든 속성에서 순서를 1보다 크게 설정 한 경우에만 작동합니다. 기본적으로 주문 설정이없는 속성은 -1의 순서로 제공됩니다. 따라서 모든 직렬화 된 속성과 순서를 제공하거나 첫 번째 항목을 -2로 설정해야합니다.


97
Order속성을 사용하여 JsonPropertyAttribute필드를 직렬화 / 직렬화하는 순서를 제어 할 수 있습니다. 그러나 순서를 1로 설정하면 다른 모든 속성에서 순서를 1보다 크게 설정 한 경우에만 작동합니다. 기본적으로 주문 설정이없는 속성은 -1의 순서로 제공됩니다. 따라서 모든 직렬화 된 속성과 순서를 제공하거나 첫 번째 항목을 -2로 설정해야합니다.
Kevin Babcock

1
직렬화에서는 작동하지만 역 직렬화에서는 순서가 고려되지 않습니다. 설명서에 따르면 order 속성은 직렬화 및 역 직렬화에 모두 사용됩니다. 해결 방법이 있습니까?
cangosta

1
에 대한 유사한 특성이 있는가 JavaScriptSerializer.
Shimmy Weitzhandler

4
@cangosta 역 직렬화 순서는 매우 "홀수"인 경우를 제외하고는 중요하지 않습니다.
user2864740

1
deserialization에서 Order가 존중 받기를 원하는 github 이슈에 대한 토론을 읽으십시오 : github.com/JamesNK/Newtonsoft.Json/issues/758 기본적 으로이 기회는 없습니다.
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) 한 가지주의 사항 : 사전의 직렬화가이 CreateProperties 사용자 정의를 사용하지 않는 것 같습니다. 그것들은 잘 직렬화되지만 결국 정렬되지는 않습니다. 사전의 직렬화를 사용자 정의하는 다른 방법이 있다고 생각하지만 찾지 못했습니다.
solublefish

완전한. 내가 원하는 걸 해 감사.
웨이드 해 틀러

이것은 훌륭한 솔루션입니다. 2 개의 JSON 객체를 나란히 놓고 속성을 정렬 할 때 특히 완벽하게 작동했습니다.
Vince

16

제 경우에는 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
dicts를 사용할 때 필요한 수정 사항입니다.
noocyte

이것은 추가적인 역 직렬화 및 직렬화의 오버 헤드를 추가합니다. 일반 클래스, 사전 및 ExpandoObject (동적 객체)에서도 작동하는 솔루션을 추가했습니다
Jay Shah

11

필자의 경우 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사전의 직렬화 중에는 호출되지 않습니다. 기계가 실제로 사전 항목을 통해 구타하는 것에 대한 JSON.net 저장소를 탐색했습니다. override주문을 위해 다른 사용자 정의에 연결되지 않습니다 . 객체의 열거 자에서 그대로 항목을 가져옵니다. JSON.net을 구성 SortedDictionary하거나 SortedList강제로 작성해야합니다. 제안 된 기능 제안 : github.com/JamesNK/Newtonsoft.Json/issues/2270
윌리엄

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 웹 API가 항상 JSON을 반환하도록하는 방법은 무엇입니까?


-5

최신 정보

난 그냥 downvotes를 보았다. 이 작업을 수행하는 방법은 아래 'Steve'의 답변을 참조하십시오.

실물

JsonConvert.SerializeObject(key)리플렉션 (키가 IList)을 통해 메소드 호출을 따랐으며 JsonSerializerInternalWriter.SerializeList가 호출되는 것을 발견했습니다. 목록을 가져와 통해 반복합니다.

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

여기서 values는 가져온 IList 매개 변수입니다.

짧은 대답은 ... 아니요, JSON 문자열에 필드가 나열되는 순서를 설정하는 방법이 없습니다.


18
짧은 대답이지만 구식 일 수 있습니다. Steve의 답변을 확인하십시오 (James Newton-king 지원)
Brad Bruce

-6

JSON 형식의 필드 순서가 없으므로 순서를 정의하는 것은 의미가 없습니다.

{ id: 1, name: 'John' }(동일 { name: 'John', id: 1 }하게 동등한 객체 인스턴스를 나타냄)


12
@Darin-직렬화에 주문이 있습니다. "{id : 1, name : 'John'}"및 "{name : 'John', id : 1}"은 strings 와 다르므로 여기서주의해야합니다. 물론, 직렬화 해제시 객체는 동일합니다.
Kevin Montrose

1
@Darin-아니오,이 경우에는 아닙니다. 나는 무언가를 직렬화 한 다음 문자열 만 처리하는 서비스에 JSON으로 전달합니다 (JSON을 인식하지 못함). 한 필드가 문자열의 첫 번째 필드에 나타나는 여러 가지 이유로 편리합니다.
Kevin Montrose

1
직렬화를 해제하는 대신 문자열을 볼 수 있기 때문에 테스트에도 적합합니다.
Steve

9
안정적인 직렬화 순서는 캐시 유효성 검사에도 유용합니다. 전체 객체 그래프가 아닌 문자열의 체크섬을 취하는 것은 쉽지 않습니다.
solublefish

1
직렬화 순서는 단위 테스트를 수행 할 때도 편리하므로 json 속성의 순서가 다르더라도 예상 응답 문자열과 실제 응답 문자열이 동일하다는 것을 쉽게 알 수 있습니다.
anon
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.