มีวิธีระบุลำดับของฟิลด์ในออบเจ็กต์ JSON แบบอนุกรมโดยใช้JSON.NETหรือไม่
ก็เพียงพอแล้วที่จะระบุว่าเขตข้อมูลเดียวจะปรากฏก่อนเสมอ
มีวิธีระบุลำดับของฟิลด์ในออบเจ็กต์ JSON แบบอนุกรมโดยใช้JSON.NETหรือไม่
ก็เพียงพอแล้วที่จะระบุว่าเขตข้อมูลเดียวจะปรากฏก่อนเสมอ
คำตอบ:
วิธีที่ได้รับการสนับสนุนคือการใช้JsonProperty
แอตทริบิวต์กับคุณสมบัติของคลาสที่คุณต้องการกำหนดลำดับ อ่านเอกสารคำสั่ง JsonPropertyAttributeสำหรับข้อมูลเพิ่มเติม
ผ่านคุณค่าและ serializer จะดูแลส่วนที่เหลือJsonProperty
Order
[JsonProperty(Order = 1)]
ซึ่งคล้ายกับไฟล์
DataMember(Order = 1)
ของSystem.Runtime.Serialization
วัน
นี่คือข้อความสำคัญจาก @ kevin-babcock
... การตั้งค่าคำสั่งเป็น 1 จะใช้ได้เฉพาะเมื่อคุณตั้งค่าคำสั่งซื้อมากกว่า 1 ในคุณสมบัติอื่น ๆ ทั้งหมด โดยค่าเริ่มต้นคุณสมบัติใด ๆ ที่ไม่มีการตั้งค่าคำสั่งซื้อจะได้รับคำสั่งเป็น -1 ดังนั้นคุณต้องให้คุณสมบัติและลำดับที่ทำให้เป็นอนุกรมทั้งหมดหรือตั้งค่ารายการแรกของคุณเป็น -2
Order
คุณสมบัติของJsonPropertyAttribute
can ถูกใช้เพื่อควบคุมลำดับฟิลด์ที่ถูกทำให้เป็นอนุกรม / deserialized อย่างไรก็ตามการตั้งค่าคำสั่งเป็น 1 จะใช้ได้ผลก็ต่อเมื่อคุณตั้งค่าคำสั่งซื้อมากกว่า 1 ในคุณสมบัติอื่น ๆ ทั้งหมด โดยค่าเริ่มต้นคุณสมบัติใด ๆ ที่ไม่มีการตั้งค่าคำสั่งซื้อจะได้รับคำสั่งเป็น -1 ดังนั้นคุณต้องให้คุณสมบัติและลำดับที่ทำให้เป็นอนุกรมทั้งหมดหรือตั้งค่ารายการแรกเป็น -2
JavaScriptSerializer
.
จริงๆคุณสามารถควบคุมการสั่งซื้อโดยการใช้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);
ในกรณีของฉันคำตอบของ 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;
}
}
ในกรณีของฉันวิธีแก้ปัญหาของ 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;
}
}
}
ดังที่ Charlie กล่าวไว้คุณสามารถควบคุมลำดับของคุณสมบัติ JSON ได้โดยการสั่งซื้อคุณสมบัติในคลาสเอง น่าเสียดายที่วิธีนี้ใช้ไม่ได้กับคุณสมบัติที่สืบทอดมาจากคลาสพื้นฐาน คุณสมบัติของคลาสพื้นฐานจะถูกเรียงลำดับตามที่วางไว้ในโค้ด แต่จะปรากฏก่อนคุณสมบัติคลาสพื้นฐาน
และสำหรับใครก็ตามที่สงสัยว่าทำไมคุณถึงต้องการเรียงตามตัวอักษรคุณสมบัติ JSON การทำงานกับไฟล์ JSON ดิบนั้นง่ายกว่ามากโดยเฉพาะอย่างยิ่งสำหรับคลาสที่มีคุณสมบัติมากมายหากมีการเรียงลำดับ
สิ่งนี้จะใช้ได้กับคลาสปกติพจนานุกรมและ 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);
CreateProperties
ไม่ถูกเรียกใช้ระหว่างการทำให้เป็นอนุกรมของพจนานุกรม ฉันสำรวจ repo JSON.net สำหรับสิ่งที่เครื่องจักรกำลังรวบรวมผ่านรายการพจนานุกรม ไม่เกี่ยวกับการoverride
ปรับแต่งใด ๆหรืออื่น ๆ สำหรับการสั่งซื้อ เพียงแค่รับรายการตามที่เป็นอยู่จากตัวแจงนับของวัตถุ ดูเหมือนว่าฉันต้องสร้างSortedDictionary
หรือSortedList
บังคับให้ JSON.net ทำสิ่งนี้ ยื่นข้อเสนอแนะคุณสมบัติ: github.com/JamesNK/Newtonsoft.Json/issues/2270
หากคุณไม่ต้องการใส่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);
วิธีการเรียกซ้ำต่อไปนี้ใช้การสะท้อนกลับเพื่อจัดเรียงรายการโทเค็นภายในบน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);
}
}
อันที่จริงเนื่องจาก 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));
ถ้าคุณควบคุม (เช่นเขียน) ชั้นเรียนให้วางคุณสมบัติตามลำดับตัวอักษรและจะเรียงลำดับตามลำดับตัวอักษรเมื่อJsonConvert.SerializeObject()
ถูกเรียก
ฉันต้องการทำให้เป็นอนุกรมของวัตถุ 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();
}
}
หากคุณต้องการกำหนดค่า 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();
}
}
ด้วยคำตอบของฉันที่นี่:
อัปเดต
ฉันเพิ่งเห็นการโหวตลดลง โปรดดูคำตอบจาก 'สตีฟ' ด้านล่างสำหรับวิธีดำเนินการนี้
เดิม
ฉันติดตามJsonConvert.SerializeObject(key)
การเรียกใช้เมธอดผ่านการสะท้อน (โดยที่คีย์คือ IList) และพบว่ามีการเรียก JsonSerializerInternalWriter.SerializeList ใช้รายการและวนซ้ำผ่าน
for (int i = 0; i < values.Count; i++) { ...
โดยที่ค่าคือพารามิเตอร์ IList ที่นำเข้ามา
คำตอบสั้น ๆ คือ ... ไม่ไม่มีวิธีในการตั้งค่าลำดับฟิลด์ที่ระบุไว้ในสตริง JSON
ไม่มีลำดับของฟิลด์ในรูปแบบ JSON ดังนั้นการกำหนดลำดับจึงไม่สมเหตุสมผล
{ id: 1, name: 'John' }
เทียบเท่ากับ{ name: 'John', id: 1 }
(ทั้งคู่แสดงถึงอินสแตนซ์วัตถุที่เทียบเท่ากันอย่างเคร่งครัด)