Newtonsoft Json Dynamic Property Name

Newtonsoft JSON dynamic property name

You can do this with a custom ContractResolver. The resolver can look for a custom attribute which will signal that you want the name of the JSON property to be based on the class of the items in the enumerable. If the item class has another attribute on it specifying its plural name, that name will then be used for the enumerable property, otherwise the item class name itself will be pluralized and used as the enumerable property name. Below is the code you would need.

First let's define some custom attributes:

public class JsonPropertyNameBasedOnItemClassAttribute : Attribute
{
}

public class JsonPluralNameAttribute : Attribute
{
public string PluralName { get; set; }
public JsonPluralNameAttribute(string pluralName)
{
PluralName = pluralName;
}
}

And then the resolver:

public class CustomResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty prop = base.CreateProperty(member, memberSerialization);
if (prop.PropertyType.IsGenericType && member.GetCustomAttribute<JsonPropertyNameBasedOnItemClassAttribute>() != null)
{
Type itemType = prop.PropertyType.GetGenericArguments().First();
JsonPluralNameAttribute att = itemType.GetCustomAttribute<JsonPluralNameAttribute>();
prop.PropertyName = att != null ? att.PluralName : Pluralize(itemType.Name);
}
return prop;
}

protected string Pluralize(string name)
{
if (name.EndsWith("y") && !name.EndsWith("ay") && !name.EndsWith("ey") && !name.EndsWith("oy") && !name.EndsWith("uy"))
return name.Substring(0, name.Length - 1) + "ies";

if (name.EndsWith("s"))
return name + "es";

return name + "s";
}
}

Now you can decorate the variably-named property in your PagedData<T> class with the [JsonPropertyNameBasedOnItemClass] attribute:

public class PagedData<T>
{
[JsonPropertyNameBasedOnItemClass]
public IEnumerable<T> Data { get; private set; }
...
}

And decorate your DTO classes with the [JsonPluralName] attribute:

[JsonPluralName("Users")]
public class UserDTO
{
...
}

[JsonPluralName("Items")]
public class ItemDTO
{
...
}

Finally, to serialize, create an instance of JsonSerializerSettings, set the ContractResolver property, and pass the settings to JsonConvert.SerializeObject like so:

JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new CustomResolver()
};

string json = JsonConvert.SerializeObject(pagedData, settings);

Fiddle: https://dotnetfiddle.net/GqKBnx

If you're using Web API (looks like you are), then you can install the custom resolver into the pipeline via the Register method of the WebApiConfig class (in the App_Start folder).

JsonSerializerSettings settings = config.Formatters.JsonFormatter.SerializerSettings;
settings.ContractResolver = new CustomResolver();


Another Approach

Another possible approach uses a custom JsonConverter to handle the serialization of the PagedData class specifically instead using the more general "resolver + attributes" approach presented above. The converter approach requires that there be another property on your PagedData class which specifies the JSON name to use for the enumerable Data property. You could either pass this name in the PagedData constructor or set it separately, as long as you do it before serialization time. The converter will look for that name and use it when writing out JSON for the enumerable property.

Here is the code for the converter:

public class PagedDataConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(PagedData<>);
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Type type = value.GetType();

var bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
string dataPropertyName = (string)type.GetProperty("DataPropertyName", bindingFlags).GetValue(value);
if (string.IsNullOrEmpty(dataPropertyName))
{
dataPropertyName = "Data";
}

JObject jo = new JObject();
jo.Add(dataPropertyName, JArray.FromObject(type.GetProperty("Data").GetValue(value)));
foreach (PropertyInfo prop in type.GetProperties().Where(p => !p.Name.StartsWith("Data")))
{
jo.Add(prop.Name, new JValue(prop.GetValue(value)));
}
jo.WriteTo(writer);
}

public override bool CanRead
{
get { return false; }
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

To use this converter, first add a string property called DataPropertyName to your PagedData class (it can be private if you like), then add a [JsonConverter] attribute to the class to tie it to the converter:

[JsonConverter(typeof(PagedDataConverter))]
public class PagedData<T>
{
private string DataPropertyName { get; set; }
public IEnumerable<T> Data { get; private set; }
...
}

And that's it. As long as you've set the DataPropertyName property, it will be picked up by the converter on serialization.

Fiddle: https://dotnetfiddle.net/8E8fEE

Deserialize Newtonsoft Json data with Dynamic property name - C#

straightforward example:

used a JSONProperty attribute to map result values of a dynamic property name

class Program
{
static void Main(string[] args)
{
var deserialise = JsonConvert.DeserializeObject<RITMRequestResponse>("{\"result\": {\"123\" : { \"number\" : \"123\" }}}");

Console.WriteLine(deserialise);
Console.ReadLine();
}
}

public class RITMRequestResponse
{
[JsonProperty(PropertyName = "result")]
public Dictionary<string, RITMDetails> RITMDetails { get; set; }
}

public class RITMDetails
{
public string Number { get; set; }
}

Using JSON.NET to read a dynamic property name

Here's a working dotNetFiddle: https://dotnetfiddle.net/6Zq5Ry

Here's the code:

using System;
using Newtonsoft.Json;
using System.Collections.Generic;

public class Program
{
public static void Main()
{
string json = @"{
'data': {
'4': {
'id': '12',
'email': 'lachlan12@somedomain.com',
'first_name': 'lachlan'
},
'5': {
'id': '15',
'email': 'appuswamy15email@somedomain.com',
'first_name': 'appuswamy'
}
}
}";

var data = JsonConvert.DeserializeObject<RootObject>(json);
Console.WriteLine("# of items deserialized : {0}", data.DataItems.Count);
foreach ( var item in data.DataItems)
{
Console.WriteLine(" Item Key {0}: ", item.Key);
Console.WriteLine(" id: {0}", item.Value.id);
Console.WriteLine(" email: {0}", item.Value.email);
Console.WriteLine(" first_name: {0}", item.Value.first_name);
}
}
}

public class RootObject
{
[JsonProperty(PropertyName = "data")]
public Dictionary<string,DataItem> DataItems { get; set; }
}

public class DataItem
{
public string id { get; set; }
public string email { get; set; }
public string first_name { get; set; }
}

Here's the output:

enter image description here

Serialize/Deserialize dynamic property name using JSON.NET

You could create a custom JsonConverter to handle the dynamic property name:

public class MyRequestConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(MyRequest);
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
string type = (string)jo["type"];
MyRequest req = new MyRequest
{
Type = type,
Source = (string)jo[type ?? ""]
};
return req;
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
MyRequest req = (MyRequest)value;
JObject jo = new JObject(
new JProperty("type", req.Type),
new JProperty(req.Type, req.Source));
jo.WriteTo(writer);
}
}

To use the converter, add a [JsonConverter] attribute to your class like this:

[JsonConverter(typeof(MyRequestConverter))]
public class MyRequest
{
public string Type { get; set; }
public string Source { get; set; }
}

Here is a working round-trip demo: https://dotnetfiddle.net/o7NDTV

Dynamic property name for serialization

Add a ToJObject method that returns a JObject.

public JObject ToJObject()
{
JObject jObject = new JObject()
{
{ "Id", Id },
{ propertyName, Text }
}

return jObject;
}

Then for Deserializing i would probably create a factory method something like this:

public static Home CreateFromJObject(JObject obj)
{
Home h = new Home();

foreach (var a in obj)
{
if (a.Key == "ID")
{
h.Id = a.Value.Value<int>();
}
else
{
h.propertyName = a.Key;
h.Text = a.Value.Value<string>();
}
}

return h;
}

Ofcause if you have multiple other values in there i would either change it to a switch or make sure that only the needed JObject is passed in there.

Deserializing a JSON with nested dynamic property names

Using Deserialize subsections of a JSON payload from How to use a JSON document, Utf8JsonReader, and Utf8JsonWriter in System.Text.Json as template you could do something like this:

JsonNode root = JsonNode.Parse(json)!;

Dictionary<string, X> devices = new();
foreach(string name in root["names"]!.AsArray()) {
var o = root[name][name].AsObject();
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream);
o.WriteTo(writer);
writer.Flush();
X? x = JsonSerializer.Deserialize<X>(stream.ToArray());

var innerJson = root[name][name].ToJsonString();
devices[name] = x;
}

foreach(var d in devices) Console.WriteLine($"{d.Key}: {d.Value}");

This prints

device1: X { property1_name = 12, property2_name = 13 }
device2: X { property1_name = 22, property2_name = 23 }

I'm not sure if this is faster/better than calling ToJsonString():

JsonNode root = JsonNode.Parse(json)!;

Dictionary<string, X> devices = new();
foreach(string name in root["names"]!.AsArray()) {
var innerJson = root[name][name].ToJsonString();
devices[name] = JsonSerializer.Deserialize<X>(innerJson);
}

foreach(var d in devices) Console.WriteLine($"{d.Key}: {d.Value}")

If you're after fancy you could go full LINQ:

JsonNode root = JsonNode.Parse(json)!;

Dictionary<string, X> devices = root["names"]!.AsArray()
.Select(name => (string)name)
.ToDictionary(
keySelector: name => name,
elementSelector: name => System.Text.Json.JsonSerializer.Deserialize<X>(root[name][name].ToJsonString()));

foreach(var d in devices) Console.WriteLine($"{d.Key}: {d.Value}");

Both print

Parse JSON with dynamic property name

You don't need ErrorKeyValue. ErrorDetails should just be:

public Dictionary<string, List<PropertyFailureInformation>> ErrorDetails { get; set; }

That is:

public class ZendeskError
{
[JsonProperty("details")]
public Dictionary<string, List<PropertyFailureInformation>> ErrorDetails { get; set; }

[JsonProperty("description")]
public string ErrorDescription { get; set; }

[JsonProperty("error")]
public string Error { get; set; }
}

public class PropertyFailureInformation
{
[JsonProperty("description")]
public string Description { get; set; }

[JsonProperty("error")]
public string Error { get; set; }
}

See DotNetFiddle

dynamically change the json property name and serialize

You can create a custom JsonConverter.

Using custom JsonConverter in order to alter the serialization of the portion of an object

Example

public class Customer
{
public string Name { get; set; }
}

public class Client
{
public string Name { get; set; }
}

public class URequest<T>
{

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string userName { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string password { get; set; }

[JsonIgnore]
public IList<T> requestList { get; set; }

}

public class URequestConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(URequest<T>));
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var objectType = value.GetType().GetGenericArguments()[0];
URequest<T> typedValue = (URequest<T>) value;

JObject containerObj = JObject.FromObject(value);

containerObj.Add($"{objectType.Name.ToLower()}List", JToken.FromObject(typedValue.requestList));
containerObj.WriteTo(writer);
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

You can use it like this

    [TestMethod]
public void TestMethod1()
{
URequest<Customer> request = new URequest<Customer>();
request.password = "test";
request.userName = "user";
request.requestList = new List<Customer>();

request.requestList.Add(new Customer() { Name = "customer" });

JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Formatting = Formatting.Indented;
settings.Converters.Add(new URequestConverter<Customer>());

Console.WriteLine(JsonConvert.SerializeObject(request, settings));
}


Related Topics



Leave a reply



Submit