How to change property names when serializing to Json in asp.Net - c#

I have an MVC Web API Controller that exposes a method:
[HttpPost]
public JsonResult<CustomClass> Details(RequestClass request)
{
var encoding = Encoding.GetEncoding("iso-8859-1");
var settings = new Newtonsoft.Json.JsonSerializerSettings();
return Json(_InputService.CustomClassGet(request.Id), settings, encoding);
}
CustomClass is a really complex class wich contains several levels of objects of different classes. Objects from these classes are somehow built in another part of the code using a mapper that relies on Newtonsoft Json.
I just got a request to modify my code to change some of CustomClass property names (along the whole tree). My first approach was to create another set of classes, so I could have one for receiving data and other for exposing data with a converter in the middle but there are so many classes in the structure and they are so complex that it would consume a lot of effort. Also, during the process of converting from input to output classes, it would require twice the memory to hold 2 copies of the same exact data.
My second approach was using JsonProperty(PropertyName ="X") to change the resulting json BUT, as the input also relies in Newtonsoft Json, I completely broke the input process.
My next approach was to create a custom serializer and user [JsonConverter(typeof(CustomCoverter))] attribute BUT it changes the way CustomClass is serialized everywhere and I can't change the way the rest of the API responds, just some specific methods.
So, the question is... does anyone imagine a way to change the way my CustomClass is serialized just in certain methods?

This is the final solution:
1) Created a custom Attribute and decorated the properties that I needed its name changed in the serialized Json:
[AttributeUsage(AttributeTargets.Property)]
class MyCustomJsonPropertyAttribute : Attribute
{
public string PropertyName { get; protected set; }
public MyCustomJsonPropertyAttribute(string propertyName)
{
PropertyName = propertyName;
}
}
2) Decordated the properties accordingly
class MyCustomClass
{
[MyCustomJsonProperty("RenamedProperty")]
public string OriginalNameProperty { get; set; }
}
3) Implemented a Custom Resolver using reflection to remap the property name for those properties decorated with MyCustomJsonPropertyAttribute
class MyCustomResolver : DefaultContractResolver
{
public MyCustomResolver()
{
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty prop = base.CreateProperty(member, memberSerialization);
var attr = member.GetCustomAttribute(typeof(MyCustomJsonPropertyAttribute));
if (attr!=null)
{
string jsonName = ((MyCustomJsonPropertyAttribute)attr).PropertyName;
prop.PropertyName = jsonName;
}
return prop;
}
}
4) Implemented the method in the controller like this:
[HttpPost]
public JsonResult<MyCustomClass> Details(RequestClass request)
{
var encoding = Encoding.GetEncoding("iso-8859-1");
var settings = new Newtonsoft.Json.JsonSerializerSettings()
{
ContractResolver = new MyCustomResolver()
};
return Json(_InputService.CustomClassGet(request.Id), settings, encoding);
}
Thanks a lot to dbc for pointing me the way.

Related

C# JSON.NET How to ignore a property in deserialization but not in serialization

I have an object I am serializing into JSON then deserializing back.
The structure of one of the properties has changed and now deserializing crashes, so I need to ignore deserializing that property for now.
I can ignore the property completely with [JsonIgnore, JsonProperty(Required = Required.Default)], but that also ignores the property from serialization - which needs to stay so no data is lost, even if it isn't' being serialized at this moment.
There is an answer here, although it's a bit old: https://stackoverflow.com/a/31732029/12431728 However, it still seems viable to me, I'm not aware of a better / different way to do it. That answer suggests marking the real property with JsonIgnore and creating a "get-only proxy property."
Then it goes on to suggest creating a custom ContractResolver if you need this functionality for many properties (AKA reusable solution).
You could use a JsonContractResolver to set the JsonProperty.ShouldDeserialize property as seen in one of the test suites of Newtonsoft.Json.
For Example,
public class ShouldDeserializeContractResolver : DefaultContractResolver
{
public static new readonly ShouldDeserializeContractResolver Instance = new ShouldDeserializeContractResolver();
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
MethodInfo shouldDeserializeMethodInfo = member.DeclaringType.GetMethod("ShouldDeserialize" + member.Name);
if (shouldDeserializeMethodInfo != null)
{
property.ShouldDeserialize = o => { return (bool)shouldDeserializeMethodInfo.Invoke(o, null); };
}
return property;
}
}
Example Code
var instance = new RootObject { ID = 2, DisplayName = "John Doe" };
var json = JsonConvert.SerializeObject(instance);
var settings = new JsonSerializerSettings
{
ContractResolver = ShouldDeserializeContractResolver.Instance
};
Console.WriteLine(json);
var deserializedInstance = JsonConvert.DeserializeObject<RootObject>(json, settings);
Console.WriteLine($"Deserialized => Id={deserializedInstance.ID}, Name={deserializedInstance.DisplayName} ");
Where RootObject is defined as
public class RootObject
{
public int ID { get; set; }
public string DisplayName { get; set; }
public bool ShouldDeserializeDisplayName() => false;
}
Output
{"ID":2,"DisplayName":"John Doe"} //During Serialization
Deserialized => Id=2, Name= // During Deserialization
You could serialise and deserialise from a JObject as in the link from Amadeu Antunes, although this solution is rather inelegant, as suddenly you've thrown loose typing into the mix.
Another potentially easier possibility is that if you have access to all of the json objects that you are deserialising from, you could do a bulk update in notepad++, ssms or whatever and just add in some default value into the json files.

How do I use the generated JsonProperty Name = name from JSON file to access a single element in a corresponding C# Class

How do I use the (generated) JsonProperty Name = name from JSON file to access a single element in a corresponding C# Class?
Here is what I am trying to do …
I have a Xamarin “classic” Newtonsoft application where I load a JSON file into a C# class MagicTime.
Here is the first part of my class - This was generated automatically by the newtonsoft web site using my JSON file
public partial class MagicTime
{
[JsonProperty("Accel Delay")]
public long AccelDelay { get; set; }
[JsonProperty("Accel Period")]
public long AccelPeriod { get; set; }
[JsonProperty("Alt DT Step")]
public long AltDtStep { get; set; }
[JsonProperty("Beep On Command")]
public bool BeepOnCommand { get; set; }
[JsonProperty("Bunt Step")]
public long BuntStep { get; set; }
[JsonProperty("Bunt Timeout")]
This is how load/deserialize an instant where loadp is string variable containing the contents of the JSON file
MagicTime MT = JsonConvert.DeserializeObject<MagicTime>(loadp );
This works fine
In the application I modify the values of some data element e.g
Looking at the above – MT.AccelDelay = 21;
I then reverse the process and write /Serialize I out
That work too.
Now I have an new requirement to use the JsonProperty name to access the corresponding C# data item
It the example above I want to use [JsonProperty("Accel Delay")] to access the corresponding c# element MT.AccelDelay.
I have seen examples where a JSON string is loaded into JObject to do this but not my case where is no (that I can see) JObject
You can use Newtonsoft's ContractResolver for this purpose, as it defines how Json.NET maps from c# objects to JSON objects.
First, define the following extension method:
public static partial class JsonExtensions
{
static readonly IContractResolver defaultResolver = new JsonSerializer().ContractResolver;
public static T GetJsonPropertyValue<T>(object obj, string propertyName, IContractResolver resolver = null)
{
resolver = resolver ?? defaultResolver;
var contract = resolver.ResolveContract(obj.GetType());
if (contract is JsonObjectContract objectContract)
{
var property = objectContract.Properties.GetClosestMatchProperty(propertyName);
if (property == null)
throw new JsonException(string.Format("Unknown property {0}", propertyName));
return (T)property.ValueProvider.GetValue(obj);
}
throw new JsonException(string.Format("Invalid contract {0}", contract));
}
}
And now you can do:
var accelDelay = JsonExtensions.GetJsonPropertyValue<long>(MT, "Accel Delay");
If you don't know the type in advance, you can just do:
var accelDelay = JsonExtensions.GetJsonPropertyValue<object>(MT, "Accel Delay");
And if you are using camel casing, do:
var resolver = new CamelCasePropertyNamesContractResolver();
var accelDelay = JsonExtensions.GetJsonPropertyValue<object>(MT, "Accel Delay", resolver);
Notes:
If the incoming obj maps to something other than a JSON object (e.g. an array or primitive) then GetJsonPropertyValue() will throw an exception. It will also throw if the property is set-only.
If you need a way to discover all the JSON property names for a given type, see Get a list of JSON property names from a class to use in a query string, especially this answer.
Demo fiddle here.

c# how to use system reserved names as properties [duplicate]

I have never used Web API before, but I need a web service that will accept/return JSON objects and using this seemed like a reasonable thing. It looked pretty simple (if not a bit of overkill for my purposes), but a data structure I need to deal with looks something like:
{
"values":["foo", "bar"],
"default":"bar"
}
And so I went to make a Model object:
class DropDownValues {
public string[] values { get; set; }
public string default { get; set; }
}
Problem is that default seems to be a protected keyword. There must be some way to get around that, right?
You can use keywords in C# as identifiers by prepending # in front of them.
I would suggest to go different way. Keep your C# object model as much standard as possible (I wouldn't use # sign and C# keywords as property name).
We can separate the serialized (JSON) world and C# objects - just by using the Json.NET features.
One of the simpliest to use is decoration with Attribute:
[JsonProperty(PropertyName = "default")]
public string DefaultValue { get; set; }
In this case we have to reference Newtonsoft.Json in the project. If it must be POCO, we can introduce CustomResolver derrived from DefaultContractResolver and define these conversions there...
But separation of concern in this case is a bit more pure solution, I would say
EDIT: JSON Contract Resolver draft (see comments)
Important NOTE: Newtonsoft.Json is part of the Web API. Not only it is an open source, but even MS team bet on that as a core JSON serializer.
1) Newtonsoft.Json (as a part of the Web.API) is already installed in your solution. So you do not have to downloaded (nuget) separately. It would always be in your packages folder. So, to use the attribute is just adding the reference. It is there...
2) There is a small draft how to do the attribute stuff, while keeping the POCO. As I've tried explain here: POCO's, behavior and Peristance Igorance, to keep POCO (e.g. we do profit from layered Architecture with NHibernate on a data layer), we can replace attributes with a Contract Resolver. Our POCO library does not have to reference anything
We just have to do extend the service layer:
public class MyResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(
MemberInfo member,
MemberSerialization memberSerialization)
{
var jProperty = base.CreateProperty(member, memberSerialization);
var propertyInfo = member as PropertyInfo;
if (propertyInfo == null)
{
return jProperty;
}
// just adjust in case if Property name is DefaultValue
var isDefaultValueProeprty =
propertyInfo.Name.Equals("DefaultValue");
if(isDefaultValueProeprty)
{
jProperty.PropertyName = "default";
}
return jProperty;
}
...
This way we've provided the same information to serailizer as with the [JsonPropertyAttribute].
Now, we just have to use it. There are many ways (e.g. global) but we can do it for a controller only:
protected override void Initialize(HttpControllerContext context)
{
base.Initialize(context);
var jSettings = context.Configuration.Formatters.JsonFormatter.SerializerSettings;
jSettings.ContractResolver = MyResolver;
}
The class DropDownValues using camel convention:
class DropDownValues {
public string[] values { get; set; }
public string default { get; set; }
}
You can use prefix # to passby but it is still not following C# coding convention.
The better solution which you can both avoid reserved keyword and still use C# coding convention is using CamelCasePropertyNamesContractResolver:
class DropDownValues {
public string[] Values { get; set; }
public string Default { get; set; }
}
And customize JsonFormatter to avoid convention mismatch between C# and json object as below:
var jsonFormatter = configuration.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings = new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};

asp.net mvc web api partial update with OData Patch

I am using HttpPatch to partially update an object. To get that working I am using Delta and Patch method from OData (mentioned here: What's the currently recommended way of performing partial updates with Web API?). Everything seems to be working fine but noticed that mapper is case sensitive; when the following object is passed the properties are getting updated values:
{
"Title" : "New title goes here",
"ShortDescription" : "New text goes here"
}
But when I pass the same object with lower or camel-case properties, Patch doesn't work - new value is not going through, so it looks like there is a problem with deserialisation and properties mapping, ie: "shortDescription" to "ShortDescription".
Is there a config section that will ignore case sensitivity using Patch?
FYI:
On output I have camel-case properties (following REST best practices) using the following formatter:
//formatting
JsonSerializerSettings jss = new JsonSerializerSettings();
jss.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.JsonFormatter.SerializerSettings = jss;
//sample output
{
"title" : "First",
"shortDescription" : "First post!"
}
My model classes however are follwing C#/.NET formatting conventions:
public class Entry {
public string Title { get; set;}
public string ShortDescription { get; set;}
//rest of the code omitted
}
Short answer, No there is no config option to undo the case sensitiveness (as far as i know)
Long answer: I had the same problem as you today, and this is how i worked around it.
I found it incredibly annoying that it had to be case sensitive, thus i decided to do away with the whole oData part, since it is a huge library that we are abusing....
An example of this implementation can be found at my github github
I decided to implement my own patch method, since that is the muscle that we are actually lacking. I created the following abstract class:
public abstract class MyModel
{
public void Patch(Object u)
{
var props = from p in this.GetType().GetProperties()
let attr = p.GetCustomAttribute(typeof(NotPatchableAttribute))
where attr == null
select p;
foreach (var prop in props)
{
var val = prop.GetValue(this, null);
if (val != null)
prop.SetValue(u, val);
}
}
}
Then i make all my model classes inherit from *MyModel*. note the line where i use *let*, i will excplain that later. So now you can remove the Delta from you controller action, and just make it Entry again, as with the put method. e.g.
public IHttpActionResult PatchUser(int id, Entry newEntry)
You can still use the patch method the way you used to:
var entry = dbContext.Entries.SingleOrDefault(p => p.ID == id);
newEntry.Patch(entry);
dbContext.SaveChanges();
Now, let's get back to the line
let attr = p.GetCustomAttribute(typeof(NotPatchableAttribute))
I found it a security risk that just any property would be able to be updated with a patch request. For example, you might now want the an ID to be changeble by the patch. I created a custom attribute to decorate my properties with. the NotPatchable attribute:
public class NotPatchableAttribute : Attribute {}
You can use it just like any other attribute:
public class User : MyModel
{
[NotPatchable]
public int ID { get; set; }
[NotPatchable]
public bool Deleted { get; set; }
public string FirstName { get; set; }
}
This in this call the Deleted and ID properties cannot be changed though the patch method.
I hope this solve it for you as well. Do not hesitate to leave a comment if you have any questions.
I added a screenshot of me inspecting the props in a new mvc 5 project. As you can see the Result view is populated with the Title and ShortDescription.
It can be done quite easily with a custom contract resolver that inherits CamelCasePropertyNamesContractResolver and implementing CreateContract method that look at concrete type for delta and gets the actual property name instead of using the one that comes from json. Abstract is below:
public class DeltaContractResolver : CamelCasePropertyNamesContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
// This class special cases the JsonContract for just the Delta<T> class. All other types should function
// as usual.
if (objectType.IsGenericType &&
objectType.GetGenericTypeDefinition() == typeof(Delta<>) &&
objectType.GetGenericArguments().Length == 1)
{
var contract = CreateDynamicContract(objectType);
contract.Properties.Clear();
var underlyingContract = CreateObjectContract(objectType.GetGenericArguments()[0]);
var underlyingProperties =
underlyingContract.CreatedType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in underlyingContract.Properties)
{
property.DeclaringType = objectType;
property.ValueProvider = new DynamicObjectValueProvider()
{
PropertyName = this.ResolveName(underlyingProperties, property.PropertyName),
};
contract.Properties.Add(property);
}
return contract;
}
return base.CreateContract(objectType);
}
private string ResolveName(PropertyInfo[] properties, string propertyName)
{
var prop = properties.SingleOrDefault(p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase));
if (prop != null)
{
return prop.Name;
}
return propertyName;
}
}

How can I use a reserved keyword as an identifier in my JSON model class?

I have never used Web API before, but I need a web service that will accept/return JSON objects and using this seemed like a reasonable thing. It looked pretty simple (if not a bit of overkill for my purposes), but a data structure I need to deal with looks something like:
{
"values":["foo", "bar"],
"default":"bar"
}
And so I went to make a Model object:
class DropDownValues {
public string[] values { get; set; }
public string default { get; set; }
}
Problem is that default seems to be a protected keyword. There must be some way to get around that, right?
You can use keywords in C# as identifiers by prepending # in front of them.
I would suggest to go different way. Keep your C# object model as much standard as possible (I wouldn't use # sign and C# keywords as property name).
We can separate the serialized (JSON) world and C# objects - just by using the Json.NET features.
One of the simpliest to use is decoration with Attribute:
[JsonProperty(PropertyName = "default")]
public string DefaultValue { get; set; }
In this case we have to reference Newtonsoft.Json in the project. If it must be POCO, we can introduce CustomResolver derrived from DefaultContractResolver and define these conversions there...
But separation of concern in this case is a bit more pure solution, I would say
EDIT: JSON Contract Resolver draft (see comments)
Important NOTE: Newtonsoft.Json is part of the Web API. Not only it is an open source, but even MS team bet on that as a core JSON serializer.
1) Newtonsoft.Json (as a part of the Web.API) is already installed in your solution. So you do not have to downloaded (nuget) separately. It would always be in your packages folder. So, to use the attribute is just adding the reference. It is there...
2) There is a small draft how to do the attribute stuff, while keeping the POCO. As I've tried explain here: POCO's, behavior and Peristance Igorance, to keep POCO (e.g. we do profit from layered Architecture with NHibernate on a data layer), we can replace attributes with a Contract Resolver. Our POCO library does not have to reference anything
We just have to do extend the service layer:
public class MyResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(
MemberInfo member,
MemberSerialization memberSerialization)
{
var jProperty = base.CreateProperty(member, memberSerialization);
var propertyInfo = member as PropertyInfo;
if (propertyInfo == null)
{
return jProperty;
}
// just adjust in case if Property name is DefaultValue
var isDefaultValueProeprty =
propertyInfo.Name.Equals("DefaultValue");
if(isDefaultValueProeprty)
{
jProperty.PropertyName = "default";
}
return jProperty;
}
...
This way we've provided the same information to serailizer as with the [JsonPropertyAttribute].
Now, we just have to use it. There are many ways (e.g. global) but we can do it for a controller only:
protected override void Initialize(HttpControllerContext context)
{
base.Initialize(context);
var jSettings = context.Configuration.Formatters.JsonFormatter.SerializerSettings;
jSettings.ContractResolver = MyResolver;
}
The class DropDownValues using camel convention:
class DropDownValues {
public string[] values { get; set; }
public string default { get; set; }
}
You can use prefix # to passby but it is still not following C# coding convention.
The better solution which you can both avoid reserved keyword and still use C# coding convention is using CamelCasePropertyNamesContractResolver:
class DropDownValues {
public string[] Values { get; set; }
public string Default { get; set; }
}
And customize JsonFormatter to avoid convention mismatch between C# and json object as below:
var jsonFormatter = configuration.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings = new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};

Categories

Resources