Reputation: 7217
I have this sample hierarchical data:
{
"Name": "Car 1",
"AvailableColors": [
"Red",
"Green"
],
"RelatedItems": {
"Brand": {
"Name": "Brand 1",
"RelatedItems": {
"ImportingCompanies": [
{
"Name": "Company 1",
"RelatedItems": {
"CeoName": "CEO 1"
}
},
{
"Name": "Company 2",
"RelatedItems": {
"CeoName": "CEO 2"
}
}
]
}
}
}
}
Basically what I need is to copy all properties of any RelatedItems
to its parent object. The result should become:
{
"Name": "Car 1",
"AvailableColors": [
"Red",
"Green"
],
"Brand": {
"Name": "Brand 1",
"ImportingCompanies": [
{
"Name": "Company 1",
"CeoName": "CEO 1"
},
{
"Name": "Company 2",
"CeoName": "CEO 2"
}
]
}
}
I'm using ASP.NET Core and I have configured my startup this way:
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
options.SerializerSettings.Converters.Add(new RelatedItemsFlattenerJsonConverter());
});
and this is the code I've written for RelatedItemsFlattenerJsonConverter
:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
JToken token = JToken.FromObject(value);
if (token.Type == JTokenType.Array)
{
var array = (JArray)token;
foreach (var item in token)
{
FlattenRelatedItems(item);
}
}
else if (token.Type == JTokenType.Object)
{
var @object = (JObject)token;
FlattenRelatedItems(@object);
}
token.WriteTo(writer);
stopwatch.Stop();
}
private void FlattenRelatedItems(JToken token)
{
if (token.Type == JTokenType.Array)
{
var array = (JArray)token;
foreach (var item in token)
{
FlattenRelatedItems(item);
}
}
else if (token.Type == JTokenType.Object)
{
var @object = (JObject)token;
var relatedItemsProperty = @object.Properties().FirstOrDefault(i => i.Name.ToLower() == "RelatedItems".ToLower());
if (relatedItemsProperty.IsNotNull())
{
var relatedItems = @object[relatedItemsProperty.Name];
@object.Remove(relatedItemsProperty.Name);
var keys = ((JObject)relatedItems).Properties().Select(i => i.Name).ToList();
foreach (var key in keys)
{
@object.Add(key, relatedItems[key]);
}
}
var properties = @object.Properties().ToList();
foreach (var property in properties)
{
FlattenRelatedItems(@object[property.Name]);
}
}
}
It works like a charm. However, when I add the custom RelatedItemsFlattenerJsonConverter
, my API response's casing would become PascalCased. And when I don't add it, the ContractResolver
is respected and my API response is camelCased. What should I do to both have the custom converter, and camelCase for the API?
Upvotes: 1
Views: 667
Reputation: 129707
When you call JToken.FromObject(value)
inside WriteJson
, you are not passing it the serializer, so it does not know about the CamelCasePropertyNamesContractResolver
(or any other settings) you have configured. JToken.FromObject()
uses a new serializer instance internally by default.
However, in this case, if you pass in the serializer as is, you may run into a self-referencing loop, as the converter attempts to call itself. What you need to do is create your own instance of JsonSerializer
inside WriteJson
and then copy the resolver reference (and any other important settings you want to keep, excluding the converter, of course) from the serializer that was passed to WriteJson
. Then pass that new serializer to JToken.FromObject()
.
In other words, change this line:
JToken token = JToken.FromObject(value);
To this:
var innerSerializer = new JsonSerializer();
innerSerializer.ContractResolver = serializer.ContractResolver;
// if any other settings from the outer serializer are needed, copy them here
JToken token = JToken.FromObject(value, innerSerializer);
Upvotes: 2