问题
Suppose I have the following JToken:
@"{
""data"": [
{
""company"": {
""ID"": ""12345"",
""location"": ""Some Location""
},
""name"": ""Some Name""
}
]
}";
I want to pass this token into a FlattenToken function that outputs this JToken:
@"{
""data"": [
{
""company_ID"": ""12345"",
""company_location"": ""Some Location"",
""name"": ""Some Name""
}
]}"
The reason for doing this is so that I can then take the flattened JToken and deserialize it into a DataTable.
I'm getting lost in a jumble of JObjects, JTokens, JProperties, and other JMadness, though. I saw the answer on this post, which was helpful, but I'm still not getting it right.
Here's what I have so far:
public static JToken FlattenToken(JToken token)
{
foreach (JToken topLevelItem in token["data"].Children())
{
foreach (JToken field in topLevelItem.Value<JToken>())
{
foreach (JProperty property in field.Value<JObject>().Properties())
{
field.AddAfterSelf(JObject.Parse(@"{""" + property.Name + "_" + property.Value));
}
field.Remove();
}
}
return token;
}
The first iteration through the outer foreach loop, topLevelItem =
{
"company": {
"ID": "12345"
},
"name": "Some Name"
}
And the first iteration through the second foreach loop, field =
"company": {
"ID": "12345"
}
Looking good so far. But when I hit the innermost foreach loop, I get an exception on the foreach line: "Cannot cast Newtonsoft.Json.Linq.JProperty to Newtonsoft.Json.Linq.JToken."
Not sure what's going on there. I was under the impression that the field.Value call was going to produce a JToken and try to cast it to a JProperty. So where is a JProperty trying to be casted to a JToken, as the error suggests?
Also, this feels like a pretty gross way of flattening out a JToken. Is there a better way?
回答1:
The hierarchy of objects in Json.NET can be rather deep. A rough guide can be found in this answer.
To solve your problem, you first need an extension method to take the properties of a JObject
and return then in a collection with a name prefix:
public static class JsonExtensions
{
public static IEnumerable<KeyValuePair<string, JToken>> FlattenFields(this JObject obj, string prefix)
{
foreach (var field in obj)
{
string fieldName = prefix + "_" + field.Key;
var fieldValue = field.Value;
yield return new KeyValuePair<string, JToken>(fieldName, fieldValue);
}
}
}
Next, you need some recursive tools to iterate through a Json.NET hierarchy and rewrite the collection of properties of selected JObject
's:
public static class JsonExtensions
{
public static IEnumerable<T> Yield<T>(this T item)
{
yield return item;
}
public static JToken EditFields(this JToken token, Func<KeyValuePair<string, JToken>, IEnumerable<KeyValuePair<string, JToken>>> editor)
{
if (token == null)
return null;
switch (token.Type)
{
case JTokenType.Array:
return EditFields((JArray)token, editor);
case JTokenType.Object:
return EditFields((JObject)token, editor);
default:
return token;
}
}
static JToken EditFields(JArray array, Func<KeyValuePair<string, JToken>, IEnumerable<KeyValuePair<string, JToken>>> editor)
{
JArray newArray = null;
foreach (var element in array)
{
var newElement = EditFields(element, editor);
if (newElement != null)
{
if (newArray == null)
newArray = new JArray();
newArray.Add(newElement);
}
}
return newArray;
}
static JToken EditFields(JObject obj, Func<KeyValuePair<string, JToken>, IEnumerable<KeyValuePair<string, JToken>>> editor)
{
JObject newObj = null;
foreach (var field in obj)
{
foreach (var newField in editor(field))
{
if (newObj == null)
newObj = new JObject();
newObj[newField.Key] = newField.Value.EditFields(editor);
}
}
return newObj;
}
}
Finally, put these together to create a method that promotes properties of a named JObject
property to their parent JObject
, prepending the property name plus an underscore:
public static class JsonExtensions
{
public static JToken PromoteNamedPropertiesToParents(this JToken token, string propertyName)
{
return token.EditFields(pair =>
{
if (pair.Key == propertyName && pair.Value is JObject)
{
return ((JObject)pair.Value).FlattenFields(pair.Key);
}
return pair.Yield();
});
}
}
And then, to test:
public static class TestFlatten
{
public static void Test()
{
string jsonString = @"{
""data"": [
{
""company"": {
""ID"": ""12345"",
""location"": ""Some Location""
},
""name"": ""Some Name""
}
]
}";
JObject obj = JObject.Parse(jsonString);
var newObj = (JObject)obj.PromoteNamedPropertiesToParents("company");
Debug.WriteLine(newObj);
}
}
And the output is:
{
"data": [
{
"company_ID": "12345",
"company_location": "Some Location",
"name": "Some Name"
}
]
}
Which is what you want. Please note that this code creates a new JObject
hierarchy rather than modifying the original hierarchy.
来源:https://stackoverflow.com/questions/27713897/flattening-out-a-jtoken