我遇到一种情况,即API有多个类似于数组的对象作为对象的单个属性。例如:
"parent": {
id: 4,
/*... other fields ...*/
"prop_1": "A",
"prop_2": "B",
/*... other "props" ...*/
"prop_24": "W"
}
我希望C#中的结果模型不重复相同的结构,并将prop_X反序列化为List并序列化回到那个混乱。
class Parent {
[JsonProperty("id")]
public int ParentId { get; set; }
/*... other properties ...*/
public List<string> Props { get; set; }
}
我尝试将JsonConverter
属性添加到Props
属性,但我无法弄清楚如何在父级上获取所需的道具。我可以将转换器添加到父对象,但由于两个原因,它会导致问题。
我的想法是让所有对象实现一个接口IHasProps
,然后编写IHasPropsJsonConverter
。转换器会尝试使用内置功能来读取和写入道具,除非在写入时遇到指示其prop对象的类型的属性,并且在读取时遇到与模式^prop\d+$
匹配的字段。
这似乎是 overkill 。还有更好的方法吗?
答案 0 :(得分:2)
Your approach of using a converter should work but is a little tricky to do in a generic fashion without getting a stack overflow exception. For writing, see Generic method of modifying JSON before being returned to client for one way of doing it. For reading, you can load into a JObject
, populate the regular properties along the lines of Json.NET custom serialization with JsonConverter - how to get the “default” behavior then identify and parse the "prop_XXX"
properties. Note that these solutions don't play well with TypeNameHandling
or PreserveReferencesHandling
.
However, a simpler approach may be to make use of [JsonExtensionData]
to temporarily store your variable set of properties in a IDictionary<string, JToken>
during the serialization process and then add them to the List<string> Props
when serialization is complete. This can be done using serialization callbacks:
public class Parent
{
public Parent() { this.Props = new List<string>(); }
[JsonProperty("id")]
public int ParentId { get; set; }
[JsonProperty("value")]
public string Value { get; set; }
[JsonIgnore]
public List<string> Props { get; set; }
[JsonExtensionData]
JObject extensionData; // JObject implements IDictionary<string, JToken> and preserves document order.
[OnSerializing]
void OnSerializing(StreamingContext ctx)
{
VariablePropertyListExtensions.OnSerializing(Props, ref extensionData, false);
}
[OnSerialized]
void OnSerialized(StreamingContext ctx)
{
VariablePropertyListExtensions.OnSerialized(Props, ref extensionData, false);
}
[OnDeserializing]
void OnDeserializing(StreamingContext ctx)
{
VariablePropertyListExtensions.OnDeserializing(Props, ref extensionData, false);
}
[OnDeserialized]
void OnDeserialized(StreamingContext ctx)
{
if (Props == null)
Props = new List<string>();
VariablePropertyListExtensions.OnDeserialized(Props, ref extensionData, false);
}
}
public static class VariablePropertyListExtensions
{
public const string Prefix = "prop_";
readonly static Regex regex;
static VariablePropertyListExtensions()
{
regex = new Regex("^" + Prefix + @"\d+" + "$", RegexOptions.CultureInvariant | RegexOptions.Compiled); // Add | RegexOptions.IgnoreCase if required
}
public static void OnSerializing<TDictionary>(IList<string> props, ref TDictionary extensionData, bool keepUnknownProperties) where TDictionary : class, IDictionary<string, JToken>, new()
{
Debug.Assert(keepUnknownProperties || (extensionData == null || extensionData.Count == 0));
// Add the prop_ properties.
if (props == null || props.Count < 1)
return;
extensionData = extensionData ?? new TDictionary();
for (int i = 0; i < props.Count; i++)
extensionData.Add(Prefix + (i + 1).ToString(NumberFormatInfo.InvariantInfo), (JValue)props[i]);
}
internal static void OnSerialized<TDictionary>(IList<string> props, ref TDictionary extensionData, bool keepUnknownProperties) where TDictionary : class, IDictionary<string, JToken>, new()
{
// Remove the prop_ properties.
if (extensionData == null)
return;
foreach (var name in extensionData.Keys.Where(k => regex.IsMatch(k)).ToList())
extensionData.Remove(name);
// null out extension data if no longer needed
if (!keepUnknownProperties || extensionData.Count == 0)
extensionData = null;
}
internal static void OnDeserializing<TDictionary>(IList<string> props, ref TDictionary extensionData, bool keepUnknownProperties) where TDictionary : class, IDictionary<string, JToken>, new()
{
Debug.Assert(keepUnknownProperties || (extensionData == null || extensionData.Count == 0));
}
internal static void OnDeserialized<TDictionary>(IList<string> props, ref TDictionary extensionData, bool keepUnknownProperties) where TDictionary : class, IDictionary<string, JToken>, new()
{
props.Clear();
if (extensionData == null)
return;
foreach (var item in extensionData.Where(i => regex.IsMatch(i.Key)).ToList())
{
props.Add(item.Value.ToObject<string>());
extensionData.Remove(item.Key);
}
// null out extension data if no longer needed
if (!keepUnknownProperties || extensionData.Count == 0)
extensionData = null;
}
}
Here I have moved the logic for populating and deserializing the extension data dictionary to a helper class for reuse in multiple classes. Note that I am adding the "prop_XXX"
properties to the properties list in document order. Since the standard states that a JSON object is an unordered set of key/value pairs, for added robustness you might want to sort them by their XXX
index.
Sample fiddle.