Determining JSON.Net property mappings based on JsonSerializerSettings - c#

Given a JsonSerializerSettings object, a Type, and a System.Reflection.PropertyInfo object, how could I determine:
1) What the JSON property name will be for that property when converted with JSON.Net with the given settings? Given the various rules and attributes that may or not be present on the property or class itself, and the various settings that might be set in the JsonSerializerSettings?
2) How to know if a given "JSON" property name will be mapped to a c# property when deserializing using JSON.Net? And if so, which property?
class Person
{
[JsonProperty("person_id")]
Guid PersonId { get; set; } // given property info for this, want to recieve "person_id"
string FirstName { get; set; } // will this serialize to "firstName"? "FirstName"? depending on the settings?
string LastName { get; set; } //given the settings, would { "LASTNAME": "Johnson" } be serialized into this property?
[JsonIgnore]
string SSN { get; set; } // determine will this property be mapped?
}
Note I'm hoping to find a solution where instead of looking at the property attributes myself (too error prone given all possibilities and contract resolvers), I would like determine based on the JsonSerializerSettings what the property mappings are?

The information you need is available from Json.NET's contract resolver. The sequence of steps to access it are as follows:
Manufacture a JsonSerializer from the settings.
Get its contract resolver.
Resolve the contract for your Person type and cast it to a JsonObjectContract.
Find the JsonProperty for your property from the Properties list. All serialization information about that specific property is there.
Thus e.g. you could create the following extension methods:
public static partial class JsonExtensions
{
static JsonProperty GetProperty(this JsonSerializerSettings settings, Type type, string underlyingName)
{
// Use JsonSerializer.Create(settings) instead if your framework ignores the global JsonConvert.DefaultSettings
var resolver = JsonSerializer.CreateDefault(settings).ContractResolver;
var contract = resolver.ResolveContract(type) as JsonObjectContract;
if (contract == null)
throw new ArgumentException(string.Format("{0} is not a JSON object", type));
return contract.Properties.Where(p => p.UnderlyingName == underlyingName).SingleOrDefault();
}
public static string GetPropertyName(this JsonSerializerSettings settings, Type type, string underlyingName)
{
var property = settings.GetProperty(type, underlyingName);
// The property might be null if it is nonpublic and not marked with [JsonProperty]
return property == null ? null : property.PropertyName;
}
public static bool GetIsIgnored(this JsonSerializerSettings settings, Type type, string underlyingName)
{
var property = settings.GetProperty(type, underlyingName);
// The property might be null if it is nonpublic and not marked with [JsonProperty]
return property == null ? true : property.Ignored;
}
}
Then, if you use the methods as follows:
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
};
foreach (var property in typeof(Person).GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
Console.WriteLine("Property {0}: Json name = \"{1}\", IsIgnored = {2}", property, settings.GetPropertyName(typeof(Person), property.Name), settings.GetIsIgnored(typeof(Person), property.Name));
}
The output will be
Property System.Guid PersonId: Json name = "person_id", IsIgnored = False
Property System.String FirstName: Json name = "firstName", IsIgnored = False
Property System.String LastName: Json name = "lastName", IsIgnored = False
Property System.String SSN: Json name = "ssn", IsIgnored = True
Notes:
Json.NET maintains global default JsonSerializerSettings that can be accessed via JsonConvert.DefaultSettings. This property
Gets or sets a function that creates default JsonSerializerSettings. Default settings are automatically used by serialization methods on JsonConvert, and ToObject<T> () and FromObject(Object) on JToken. To serialize without using any default settings create a JsonSerializer with Create().
If your framework ignores the global default settings you should replace JsonSerializer.CreateDefault(settings) with JsonSerializer.Create(settings) in the above methods.
For improved performance you could manufacture the serializer once and add the extension methods to JsonSerializer rather than JsonSerializerSettings. And if you need multiple attributes for a given property you could just fetch the JsonProperty and access it directly.
Json.NET can serialize fields as well as properties so requiring a PropertyInfo to be passed to the extension methods might limit access to some necessary serialization information.
Sample working .Net fiddle.

Related

NewtonSoft - Property being serialize as an integer - source is char(2) [duplicate]

I have a class that contains an enum property, and upon serializing the object using JavaScriptSerializer, my json result contains the integer value of the enumeration rather than its string "name". Is there a way to get the enum as a string in my json without having to create a custom JavaScriptConverter? Perhaps there's an attribute that I could decorate the enum definition, or object property, with?
As an example:
enum Gender { Male, Female }
class Person
{
int Age { get; set; }
Gender Gender { get; set; }
}
Desired JSON result:
{ "Age": 35, "Gender": "Male" }
Ideally looking for answer with built-in .NET framework classes, if not possible alternatives (like Json.net) are welcome.
I have found that Json.NET provides the exact functionality I'm looking for with a StringEnumConverter attribute:
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
[JsonConverter(typeof(StringEnumConverter))]
public Gender Gender { get; set; }
More details at available on StringEnumConverter documentation.
There are other places to configure this converter more globally:
enum itself if you want enum always be serialized/deserialized as string:
[JsonConverter(typeof(StringEnumConverter))]
enum Gender { Male, Female }
In case anyone wants to avoid attribute decoration, you can add the converter to your JsonSerializer (suggested by Bjørn Egil):
serializer.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
and it will work for every enum it sees during that serialization (suggested by Travis).
or JsonConverter (suggested by banana):
JsonConvert.SerializeObject(MyObject,
new Newtonsoft.Json.Converters.StringEnumConverter());
Additionally you can control casing and whether numbers are still accepted by using StringEnumConverter(NamingStrategy, Boolean) constructor.
No there is no special attribute you can use. JavaScriptSerializer serializes enums to their numeric values and not their string representation. You would need to use custom serialization to serialize the enum as its name instead of numeric value.
If you can use JSON.Net instead of JavaScriptSerializer than see answer on this question provided by Omer Bokhari: JSON.net covers this use case (via the attribute [JsonConverter(typeof(StringEnumConverter))]) and many others not handled by the built in .net serializers. Here is a link comparing features and functionalities of the serializers.
Add the below to your global.asax for JSON serialization of c# enum as string
HttpConfiguration config = GlobalConfiguration.Configuration;
config.Formatters.JsonFormatter.SerializerSettings.Formatting =
Newtonsoft.Json.Formatting.Indented;
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add
(new Newtonsoft.Json.Converters.StringEnumConverter());
#Iggy answer sets JSON serialization of c# enum as string only for ASP.NET (Web API and so).
But to make it work also with ad hoc serialization, add following to your start class (like Global.asax Application_Start)
//convert Enums to Strings (instead of Integer) globally
JsonConvert.DefaultSettings = (() =>
{
var settings = new JsonSerializerSettings();
settings.Converters.Add(new StringEnumConverter { CamelCaseText = true });
return settings;
});
More information on the Json.NET page
Additionally, to have your enum member to serialize/deserialize to/from specific text, use the
System.Runtime.Serialization.EnumMember
attribute, like this:
public enum time_zone_enum
{
[EnumMember(Value = "Europe/London")]
EuropeLondon,
[EnumMember(Value = "US/Alaska")]
USAlaska
}
In .net core 3 this is now possible with the built-in classes in System.Text.Json (edit: System.Text.Json is also available as a NuGet package for .net core 2.0 and .net framework 4.7.2 and later versions according to the docs):
var person = new Person();
// Create and add a converter which will use the string representation instead of the numeric value.
var stringEnumConverter = new System.Text.Json.Serialization.JsonStringEnumConverter();
JsonSerializerOptions opts = new JsonSerializerOptions();
opts.Converters.Add(stringEnumConverter);
// Generate json string.
var json = JsonSerializer.Serialize<Person>(person, opts);
To configure JsonStringEnumConverter with attribute decoration for the specific property:
using System.Text.Json.Serialization;
[JsonConverter(typeof(JsonStringEnumConverter))]
public Gender Gender { get; set; }
If you want to always convert the enum as string, put the attribute at the enum itself.
[JsonConverter(typeof(JsonStringEnumConverter))]
enum Gender { Male, Female }
I wasn't able to change the source model like in the top answer (of #ob.), and I didn't want to register it globally like #Iggy. So I combined https://stackoverflow.com/a/2870420/237091 and #Iggy's https://stackoverflow.com/a/18152942/237091 to allow setting up the string enum converter on during the SerializeObject command itself:
Newtonsoft.Json.JsonConvert.SerializeObject(
objectToSerialize,
Newtonsoft.Json.Formatting.None,
new Newtonsoft.Json.JsonSerializerSettings()
{
Converters = new List<Newtonsoft.Json.JsonConverter> {
new Newtonsoft.Json.Converters.StringEnumConverter()
}
})
The combination of Omer Bokhari and uri 's answers is alsways my solution since the values that I want to provide is usually different from what I have in my enum specially that I would like to be able to change my enums if I need to.
So if anyone is interested, it is something like this:
public enum Gender
{
[EnumMember(Value = "male")]
Male,
[EnumMember(Value = "female")]
Female
}
class Person
{
int Age { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
Gender Gender { get; set; }
}
ASP.NET Core way:
public class Startup
{
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
});
}
}
https://gist.github.com/regisdiogo/27f62ef83a804668eb0d9d0f63989e3e
This is easily done by adding a ScriptIgnore attribute to the Gender property, causing it to not be serialised, and adding a GenderString property which does get serialised:
class Person
{
int Age { get; set; }
[ScriptIgnore]
Gender Gender { get; set; }
string GenderString { get { return Gender.ToString(); } }
}
This version of Stephen's answer doesn't change the name in the JSON:
[DataContract(
Namespace =
"http://schemas.datacontract.org/2004/07/Whatever")]
class Person
{
[DataMember]
int Age { get; set; }
Gender Gender { get; set; }
[DataMember(Name = "Gender")]
string GenderString
{
get { return this.Gender.ToString(); }
set
{
Gender g;
this.Gender = Enum.TryParse(value, true, out g) ? g : Gender.Male;
}
}
}
Here is the answer for newtonsoft.json
enum Gender { Male, Female }
class Person
{
int Age { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
Gender Gender { get; set; }
}
Asp.Net Core 3 with System.Text.Json
public void ConfigureServices(IServiceCollection services)
{
services
.AddControllers()
.AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())
);
//...
}
You can also add a converter to your JsonSerializer if you don't want to use JsonConverter attribute:
string SerializedResponse = JsonConvert.SerializeObject(
objToSerialize,
new Newtonsoft.Json.Converters.StringEnumConverter()
);
It will work for every enum it sees during that serialization.
Here is a simple solution that serializes a server-side C# enum to JSON and uses the result to populate a client-side <select> element. This works for both simple enums and bitflag enums.
I have included the end-to-end solution because I think most people wanting to serialize a C# enum to JSON will also probably be using it to fill a <select> drop-down.
Here goes:
Example Enum
public enum Role
{
None = Permission.None,
Guest = Permission.Browse,
Reader = Permission.Browse| Permission.Help ,
Manager = Permission.Browse | Permission.Help | Permission.Customise
}
A complex enum that uses bitwise ORs to generate a permissions system. So you can't rely on the simple index [0,1,2..] for the integer value of the enum.
Server Side - C#
Get["/roles"] = _ =>
{
var type = typeof(Role);
var data = Enum
.GetNames(type)
.Select(name => new
{
Id = (int)Enum.Parse(type, name),
Name = name
})
.ToArray();
return Response.AsJson(data);
};
The code above uses the NancyFX framework to handle the Get request. It uses Nancy's Response.AsJson() helper method - but don't worry, you can use any standard JSON formatter as the enum has already been projected into a simple anonymous type ready for serialization.
Generated JSON
[
{"Id":0,"Name":"None"},
{"Id":2097155,"Name":"Guest"},
{"Id":2916367,"Name":"Reader"},
{"Id":4186095,"Name":"Manager"}
]
Client Side - CoffeeScript
fillSelect=(id, url, selectedValue=0)->
$select = $ id
$option = (item)-> $ "<option/>",
{
value:"#{item.Id}"
html:"#{item.Name}"
selected:"selected" if item.Id is selectedValue
}
$.getJSON(url).done (data)->$option(item).appendTo $select for item in data
$ ->
fillSelect "#role", "/roles", 2916367
HTML Before
<select id="role" name="role"></select>
HTML After
<select id="role" name="role">
<option value="0">None</option>
<option value="2097155">Guest</option>
<option value="2916367" selected="selected">Reader</option>
<option value="4186095">Manager</option>
</select>
For ASP.Net core Just add the following to your Startup Class:
JsonConvert.DefaultSettings = (() =>
{
var settings = new JsonSerializerSettings();
settings.Converters.Add(new StringEnumConverter { AllowIntegerValues = false });
return settings;
});
For .NET 6.0 if you want to use the built-in JsonSerializer (System.Text.Json)
Then, it comes out-of-the-box, you just need to use the built-in JsonStringEnumConverter attribute. For example:
[JsonConverter(typeof(JsonStringEnumConverter))]
public SomeEnumType EnumProperty { get; set; }
And that's it, BUT make sure your SomeEnumType contains values with the exact string values, otherwise it will throw an exception. Casing seems to be insensitive.
Reference: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-customize-properties?pivots=dotnet-6-0#enums-as-strings
You can create JsonSerializerSettings with the call to JsonConverter.SerializeObject as below:
var result = JsonConvert.SerializeObject
(
dataObject,
new JsonSerializerSettings
{
Converters = new [] {new StringEnumConverter()}
}
);
Noticed that there is no answer for serialization when there is a Description attribute.
Here is my implementation that supports the Description attribute.
public class CustomStringEnumConverter : Newtonsoft.Json.Converters.StringEnumConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Type type = value.GetType() as Type;
if (!type.IsEnum) throw new InvalidOperationException("Only type Enum is supported");
foreach (var field in type.GetFields())
{
if (field.Name == value.ToString())
{
var attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
writer.WriteValue(attribute != null ? attribute.Description : field.Name);
return;
}
}
throw new ArgumentException("Enum not found");
}
}
Enum:
public enum FooEnum
{
// Will be serialized as "Not Applicable"
[Description("Not Applicable")]
NotApplicable,
// Will be serialized as "Applicable"
Applicable
}
Usage:
[JsonConverter(typeof(CustomStringEnumConverter))]
public FooEnum test { get; set; }
For .Net Core :-
public void ConfigureServices(IServiceCollection services)
{
...
services.AddJsonFormatters(f => f.Converters.Add(new StringEnumConverter()));
...
}
Use this:
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
[Serializable]
[JsonConverter(typeof(StringEnumConverter))]
public enum Gender { Male, Female }
Just in case anybody finds the above insufficient, I ended up settling with this overload:
JsonConvert.SerializeObject(objToSerialize, Formatting.Indented, new Newtonsoft.Json.Converters.StringEnumConverter())
This is an old question but I thought I'd contribute just in case. In my projects I use separate models for any Json requests. A model would typically have same name as domain object with "Json" prefix. Models are mapped using AutoMapper. By having the json model declare a string property that is an enum on domain class, AutoMapper will resolve to it's string presentation.
In case you are wondering, I need separate models for Json serialized classes because inbuilt serializer comes up with circular references otherwise.
Hope this helps someone.
You can actually use a JavaScriptConverter to accomplish this with the built-in JavaScriptSerializer. By converting your enum to a Uri you can encode it as a string.
I've described how to do this for dates but it can be used for enums as well.
Custom DateTime JSON Format for .NET JavaScriptSerializer.
A slightly more future-proof option
Facing the same question, we determined that we needed a custom version of StringEnumConverter to make sure that our enum values could expand over time without breaking catastrophically on the deserializing side (see background below). Using the SafeEnumConverter below allows deserialization to finish even if the payload contains a value for the enum that does not have a named definition, closer to how int-to-enum conversion would work.
Usage:
[SafeEnumConverter]
public enum Colors
{
Red,
Green,
Blue,
Unsupported = -1
}
or
[SafeEnumConverter((int) Colors.Blue)]
public enum Colors
{
Red,
Green,
Blue
}
Source:
public class SafeEnumConverter : StringEnumConverter
{
private readonly int _defaultValue;
public SafeEnumConverter()
{
// if you've been careful to *always* create enums with `0` reserved
// as an unknown/default value (which you should), you could use 0 here.
_defaultValue = -1;
}
public SafeEnumConverter(int defaultValue)
{
_defaultValue = defaultValue;
}
/// <summary>
/// Reads the provided JSON and attempts to convert using StringEnumConverter. If that fails set the value to the default value.
/// </summary>
/// <returns>The deserialized value of the enum if it exists or the default value if it does not.</returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
try
{
return base.ReadJson(reader, objectType, existingValue, serializer);
}
catch
{
return Enum.Parse(objectType, $"{_defaultValue}");
}
}
public override bool CanConvert(Type objectType)
{
return base.CanConvert(objectType) && objectType.GetTypeInfo().IsEnum;
}
}
Background
When we looked at using the StringEnumConverter, the problem we had is that we also needed passivity for cases when a new enum value was added, but not every client was immediately aware of the new value. In these cases, the StringEnumConverter packaged with Newtonsoft JSON throws a JsonSerializationException similar to "Error converting value SomeString to type EnumType" and then the whole deserialization process fails. This was a deal breaker for us, because even if the client planned on ignoring/discarding the property value that it didn't understand, it still needed to be capable of deserializing the rest of the payload!
Not sure if this is still relevant but I had to write straight to a json file and I came up with the following piecing several stackoverflow answers together
public class LowercaseJsonSerializer
{
private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
ContractResolver = new LowercaseContractResolver()
};
public static void Serialize(TextWriter file, object o)
{
JsonSerializer serializer = new JsonSerializer()
{
ContractResolver = new LowercaseContractResolver(),
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore
};
serializer.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
serializer.Serialize(file, o);
}
public class LowercaseContractResolver : DefaultContractResolver
{
protected override string ResolvePropertyName(string propertyName)
{
return Char.ToLowerInvariant(propertyName[0]) + propertyName.Substring(1);
}
}
}
It assures all my json keys are lowercase starting according to json "rules". Formats it cleanly indented and ignores nulls in the output. Aslo by adding a StringEnumConverter it prints enums with their string value.
Personally I find this the cleanest I could come up with, without having to dirty the model with annotations.
usage:
internal void SaveJson(string fileName)
{
// serialize JSON directly to a file
using (StreamWriter file = File.CreateText(#fileName))
{
LowercaseJsonSerializer.Serialize(file, jsonobject);
}
}
And for VB.net I found the following works:
Dim sec = New Newtonsoft.Json.Converters.StringEnumConverter()
sec.NamingStrategy() = New Serialization.CamelCaseNamingStrategy
Dim JSON_s As New JsonSerializer
JSON_s.Converters.Add(sec)
Dim jsonObject As JObject
jsonObject = JObject.FromObject(SomeObject, JSON_s)
Dim text = jsonObject.ToString
IO.File.WriteAllText(filePath, text)
I have put together all of the pieces of this solution using the Newtonsoft.Json library. It fixes the enum issue and also makes the error handling much better, and it works in IIS hosted services. It's quite a lot of code, so you can find it on GitHub here: https://github.com/jongrant/wcfjsonserializer/blob/master/NewtonsoftJsonFormatter.cs
You have to add some entries to your Web.config to get it to work, you can see an example file here:
https://github.com/jongrant/wcfjsonserializer/blob/master/Web.config
For anyone needing a solution in May of '22 for .NET 6 and still using Newtonsoft, you can register the converter globally like this:
var builder = WebApplication.CreateBuilder(args);
...
builder.Services.AddControllers(options => options.RespectBrowserAcceptHeader = true)
.AddNewtonsoftJson(opt =>
{
opt.SerializerSettings.ContractResolver = new DefaultContractResolver();
opt.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
})
.AddXmlSerializerFormatters()
.AddXmlDataContractSerializerFormatters();
The namespace System.Text.Json.Serialization has JsonStringEnumConverter which can be used like below.
[JsonConverter(typeof(JsonStringEnumConverter))]
Person p = new Person();
p.Age = 35;
p.Gender = Gender.Male;
//1. male="Male";
string male = Gender.Male.ToString();
p.Gender = Gender.Female;
//2. female="Female";
string female = Enum.GetName(typeof(Gender), p.Gender);
JObject jobj = new JObject();
jobj["Age"] = p.Age;
jobj["Gender"] = male;
jobj["Gender2"] = female;
//you result: josn= {"Age": 35,"Gender": "Male","Gender2": "Female"}
string json = jobj.ToString();

Json.NET C#: Serialising a collection based on whether or not the collection field has been decorated with a certain attribute

I'm building a serialisation system using Json.NET in Unity. The basic ideas is that classes which need to have their references serialised are decorated with [Ref], like this:
// Wine can be referenced, and these references need to be serialised as a numeric ID.
[Ref]
public class Wine
{
private string name = "";
public Wine(string _name)
{
name = _name;
}
}
Any other instance of this class is automatically considered a reference, unless that field has been decorated with [Def].
public class FoodExample
{
// This central script defines all foods and wines in the program.
[Def]
List<Food> foods = new List<Food>();
[Def]
List<Wine> wines = new List<Wine>();
}
[Ref]
public class Food
{
private string name = "";
// Food references a wine that pairs well with it, but it does not define one. When serialised, this should be a numeric ID instead of a Json definition.
private Wine pairsWellWith = null;
public Food(string _name, Wine _pairsWellWith)
{
name = _name;
pairsWellWith = _pairsWellWith;
}
}
I can then use either some Custom Converters (one which serialises the class instance as its definition, the other as numeric IDs), or even a Custom Contract Resolver that assigns these Converters manually. This system works great when I'm just dealing with singular fields. I can say:
[Def]
Wine myFavouriteWine = null;
Wine refToFavouriteWine = null;
... and the former will become a bunch of Json fields, while the latter will become a simple numeric reference, avoiding reference duplication. This works well because I can read field info attributes in a Custom Contract Resolver, by using the CreateProperty() override. Or I can just use
[CustomConverter(typeof(RefConverter/DefConverter))]
But this doesn't work for collections. I can apply my custom behaviour to a collection itself, but not to its elements. I could make a custom converter for the collection to operate over its children, but I'm not sure how.
It's a bit of a complicated scenario, so I'll refrain from swamping everyone with piles of my code for now, but if more detail is needed please say so.
Finally: The reason I'm not using Json.NET's native reference handling system is because we have a specific need to serialise some objects as numbers, and others as legible string names (such as "PinotGris_WINE").
Thanks.
Answering my own question:
Two solutions are currently available. The first was recommended in dbc's comment: simply use JsonPropertyAttribute.ItemConverterType to tell Json to use DefinitionJConverter or ReferenceJConverter on the elements of the decorated collection.
Simply because this solution is inconsistent with the rest of my current approach, and because I prefer shorter Attribute names, I'll also show my own solution:
Create two more custom converters called DefCollectionJConverter and RefCollectionJConverter. In the contract resolver, attach these to any discovered collections which contain objects decorated with [Ref]. Furthermore, if the collection itself has been decorated with [Def], pass it to the DefCollectionJConverter, like so:
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
// Do not handle properties (it's difficult to concieve of a time when properties should be serialised at all).
if(member is PropertyInfo)
{
return property;
}
else if(member is FieldInfo)
{
FieldInfo field = ((FieldInfo)member);
// Does this field implement IList? This Utility function gets all member interfaces and checks if any of them equal the argued interface.
if (field.FieldType.ImplementsInterface<IList>())
{
// Are the elements of this IList decorated with [Ref]?
if (field.FieldType.GetGenericArguments()[0].HasAttribute<RefAttribute>())
{
// Does this collection field have [Def]?
if (field.HasAttribute<DefAttribute>())
{
// This is a collection of definitions.
property.Converter = new DefCollectionJConverter();
}
else
{
// This is a collection of references.
property.Converter = new RefCollectionJConverter();
}
}
}
else
{
// If the field's type class has been decorated with Ref.
if (field.FieldType.HasAttribute<RefAttribute>())
{
if (member.HasAttribute<DefAttribute>())
{
// If this particular field also has Def, it's a definition.
property.Converter = new DefinitionJConverter();
}
else
{
// Else it's a reference.
property.Converter = new ReferenceJConverter();
}
}
}
}
return property;
}
Inside the Collection converters themselves, populate a JArray and then call JArray.WriteTo(). Construct the JTokens using either the default serialiser for definitions, or the custom reference serialiser for references:
public class DefCollectionJConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JArray array = new JArray();
IList list = (IList)value;
if (list.Count > 0)
{
// Populate the JArray with JTokens from the list elements. Pass the current serializer, which should contain default settings and converters.
// This should handle child objects appropriately, too.
JsonSerializer defSerializer = new JsonSerializer();
defSerializer.Converters.Add(new DefinitionJConverter());
for (int e = 0; e < list.Count; e++)
{
JToken first = JToken.FromObject(list[e], serializer);
array.Add(first);
}
array.WriteTo(writer);
}
else
{
Debug.LogError("List was empty.");
}
}
}
public class RefCollectionJConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JArray array = new JArray();
IList list = (IList)value;
if(list.Count > 0)
{
// Populate the JArray with JTokens of the list elements, using a serialiser that only contains ReferenceJConverter.
// Note that we don't need to worry about this token's children: only definitions can describe children.
JsonSerializer refSerializer = new JsonSerializer();
refSerializer.Converters.Add(new ReferenceJConverter());
for(int e = 0; e < list.Count; e++)
{
JToken first = JToken.FromObject(list[e], refSerializer);
array.Add(first);
}
array.WriteTo(writer);
}
else
{
Debug.LogError("List was empty.");
}
}
}
For these converters, I have CanConvert() returning true always, since I'm assigning these converters manually.
I haven't tested this with deserialisation yet, but I don't see why it wouldn't work.

ASP.NET Web API 2 and partial updates

We are using ASP.NET Web API 2 and want to expose ability to partially edit some object in the following fashion:
HTTP PATCH /customers/1
{
"firstName": "John",
"lastName": null
}
... to set firstName to "John" and lastName to null.
HTTP PATCH /customers/1
{
"firstName": "John"
}
... in order just to update firstName to "John" and do not touch lastName at all. Suppose we have a lot of properties that we want to update with such semantic.
This is quite convenient behavior that is exercised by OData for instance.
The problem is that default JSON serializer will just come up with null in both cases, so it's impossible to distinguish.
I'm looking for some way to annotate model with some kind of wrappers (with value and flag set/unset inside) that would allow to see this difference. Any existing solutions for this?
I know that answers which are already given cover all aspects already, but just want to share concise summary of what we ended up doing and what seems to work for us pretty well.
Created a generic data contract
[DataContract]
public class RQFieldPatch<T>
{
[DataMember(Name = "value")]
public T Value { get; set; }
}
Created ad-hoc data cotnracts for patch requests
Sample is below.
[DataContract]
public class PatchSomethingRequest
{
[DataMember(Name = "prop1")]
public RQFieldPatch<EnumTypeHere> Prop1 { get; set; }
[DataMember(Name = "prop2")]
public RQFieldPatch<ComplexTypeContractHere> Prop2 { get; set; }
[DataMember(Name = "prop3")]
public RQFieldPatch<string> Prop3 { get; set; }
[DataMember(Name = "prop4")]
public RQFieldPatch<int> Prop4 { get; set; }
[DataMember(Name = "prop5")]
public RQFieldPatch<int?> Prop5 { get; set; }
}
Business Logic
Simple.
if (request.Prop1 != null)
{
// update code for Prop1, the value is stored in request.Prop1.Value
}
Json format
Simple. Not that extensive as "JSON Patch" standard, but covers all our needs.
{
"prop1": null, // will be skipped
// "prop2": null // skipped props also skipped as they will get default (null) value
"prop3": { "value": "test" } // value update requested
}
Properties
Simple contracts, simple logic
No serialization customization
Support for null values assignment
Covers any types: value, reference, complex custom types, whatever
At first I misunderstood the problem. As I was working with Xml I thought it was quite easy. Just add an attribute to the property and leave the property empty. But as I found out, Json doesn't work like that. Since I was looking for a solution that works for both xml and json, you'll find xml references in this answer. Another thing, I wrote this with a C# client in mind.
The first step is to create two classes for serialization.
public class ChangeType
{
[JsonProperty("#text")]
[XmlText]
public string Text { get; set; }
}
public class GenericChangeType<T> : ChangeType
{
}
I've chosen for a generic and a non-generic class because it is hard to cast to a generic type while this is not important. Also, for xml implementation it is necessary that XmlText is string.
XmlText is the actual value of the property. The advantage is that you can add attributes to this object and the fact that this is an object, not just string. In Xml it looks like: <Firstname>John</Firstname>
For Json this doesn't work. Json doesn't know attributes. So for Json this is just a class with properties. To implement the idea of the xml value (I will get to that later), I've renamed the property to #text. This is just a convention.
As XmlText is string (and we want to serialize to string), this is fine to store the value disregard the type. But in case of serialization, I want to know the actual type.
The drawback is that the viewmodel needs to reference these types, the advantage is that the properties are strongly typed for serialization:
public class CustomerViewModel
{
public GenericChangeType<int> Id { get; set; }
public ChangeType Firstname { get; set; }
public ChangeType Lastname { get; set; }
public ChangeType Reference { get; set; }
}
Suppose I set the values:
var customerViewModel = new CustomerViewModel
{
// Where int needs to be saved as string.
Id = new GenericeChangeType<int> { Text = "12" },
Firstname = new ChangeType { Text = "John" },
Lastname = new ChangeType { },
Reference = null // May also be omitted.
}
In xml this will look like:
<CustomerViewModel>
<Id>12</Id>
<Firstname>John</Firstname>
<Lastname />
</CustomerViewModel>
Which is enough for the server to detect the changes. But with json it will generate the following:
{
"id": { "#text": "12" },
"firstname": { "#text": "John" },
"lastname": { "#text": null }
}
It can work, because in my implementation the receiving viewmodel has the same definition. But since you are talking about serialization only and in case you use another implementation you would want:
{
"id": 12,
"firstname": "John",
"lastname": null
}
That is where we need to add a custom json converter to produce this result. The relevant code is in WriteJson, assuming you would add this converter to the serializer settings only. But for the sake of completeness I've added the readJson code as well.
public class ChangeTypeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
// This is important, we can use this converter for ChangeType only
return typeof(ChangeType).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = JToken.Load(reader);
// Types match, it can be deserialized without problems.
if (value.Type == JTokenType.Object)
return JsonConvert.DeserializeObject(value.ToString(), objectType);
// Convert to ChangeType and set the value, if not null:
var t = (ChangeType)Activator.CreateInstance(objectType);
if (value.Type != JTokenType.Null)
t.Text = value.ToString();
return t;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var d = value.GetType();
if (typeof(ChangeType).IsAssignableFrom(d))
{
var changeObject = (ChangeType)value;
// e.g. GenericChangeType<int>
if (value.GetType().IsGenericType)
{
try
{
// type - int
var type = value.GetType().GetGenericArguments()[0];
var c = Convert.ChangeType(changeObject.Text, type);
// write the int value
writer.WriteValue(c);
}
catch
{
// Ignore the exception, just write null.
writer.WriteNull();
}
}
else
{
// ChangeType object. Write the inner string (like xmlText value)
writer.WriteValue(changeObject.Text);
}
// Done writing.
return;
}
// Another object that is derived from ChangeType.
// Do not add the current converter here because this will result in a loop.
var s = new JsonSerializer
{
NullValueHandling = serializer.NullValueHandling,
DefaultValueHandling = serializer.DefaultValueHandling,
ContractResolver = serializer.ContractResolver
};
JToken.FromObject(value, s).WriteTo(writer);
}
}
At first I tried to add the converter to the class: [JsonConverter(ChangeTypeConverter)]. But the problem is that the converter will be used at all times, which creates a reference loop (as also mentioned in the comment in the code above). Also you may want to use this converter for serialization only. That is why I've added it to the serializer only:
var serializerSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate,
Converters = new List<JsonConverter> { new ChangeTypeConverter() },
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
};
var s = JsonConvert.SerializeObject(customerViewModel, serializerSettings);
This will generate the json I was looking for and should be enough to let the server detect the changes.
-- update --
As this answer focusses on serialization, the most important thing is that lastname is part of the serialization string. It then depends on the receiving party how to deserialize the string into an object again.
Serialization and deserialization use different settings. In order to deserialize again you can use:
var deserializerSettings = new JsonSerializerSettings
{
//NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate,
Converters = new List<JsonConverter> { new Converters.NoChangeTypeConverter() },
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
};
var obj = JsonConvert.DeserializeObject<CustomerViewModel>(s, deserializerSettings);
If you use the same classes for deserialization then Request.Lastname should be of ChangeType, with Text = null.
I'm not sure why removing the NullValueHandling from the deserialization settings causes problems in your case. But you can overcome this by writing an empty object as value instead of null. In the converter the current ReadJson can already handle this. But in WriteJson there has to be a modification. Instead of writer.WriteValue(changeObject.Text); you need something like:
if (changeObject.Text == null)
JToken.FromObject(new ChangeType(), s).WriteTo(writer);
else
writer.WriteValue(changeObject.Text);
This would result in:
{
"id": 12,
"firstname": "John",
"lastname": {}
}
Here's my quick and inexpensive solution...
public static ObjectType Patch<ObjectType>(ObjectType source, JObject document)
where ObjectType : class
{
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
try
{
String currentEntry = JsonConvert.SerializeObject(source, settings);
JObject currentObj = JObject.Parse(currentEntry);
foreach (KeyValuePair<String, JToken> property in document)
{
currentObj[property.Key] = property.Value;
}
String updatedObj = currentObj.ToString();
return JsonConvert.DeserializeObject<ObjectType>(updatedObj);
}
catch (Exception ex)
{
throw ex;
}
}
When fetching the request body from your PATCH based method, make sure to take the argument as a type such as JObject. JObject during iteration returns a KeyValuePair struct which inherently simplifies the modification process. This allows you to get your request body content without receiving a deserialized result of your desired type.
This is beneficial due to the fact that you don't need any additional verification for nullified properties. If you want your values to be nullified that also works because the Patch<ObjectType>() method only loops through properties given in the partial JSON document.
With the Patch<ObjectType>() method, you only need to pass your source or target instance, and the partial JSON document that will update your object. This method will apply camelCase based contract resolver to prevent incompatible and inaccurate property names from being made. This method will then serialize your passed instance of a certain type and turned into a JObject.
The method then replaces all properties from the new JSON document to the current and serialized document without any unnecessary if statements.
The method stringifies the current document which now is modified, and deserializes the modified JSON document to your desired generic type.
If an exception occur, the method will simply throw it. Yes, it is rather unspecific but you are the programmer, you need to know what to expect...
This can all be done on a single and simple syntax with the following:
Entity entity = AtomicModifier.Patch<Entity>(entity, partialDocument);
This is what the operation would normally look like:
// Partial JSON document (originates from controller).
JObject newData = new { role = 9001 };
// Current entity from EF persistence medium.
User user = await context.Users.FindAsync(id);
// Output:
//
// Username : engineer-186f
// Role : 1
//
Debug.WriteLine($"Username : {0}", user.Username);
Debug.WriteLine($"Role : {0}", user.Role);
// Partially updated entity.
user = AtomicModifier.Patch<User>(user, newData);
// Output:
//
// Username : engineer-186f
// Role : 9001
//
Debug.WriteLine($"Username : {0}", user.Username);
Debug.WriteLine($"Role : {0}", user.Role);
// Setting the new values to the context.
context.Entry(user).State = EntityState.Modified;
This method will work well if you can correctly map your two documents with the camelCase contract resolver.
Enjoy...
Update
I updated the Patch<T>() method with the following code...
public static T PatchObject<T>(T source, JObject document) where T : class
{
Type type = typeof(T);
IDictionary<String, Object> dict =
type
.GetProperties()
.ToDictionary(e => e.Name, e => e.GetValue(source));
string json = document.ToString();
var patchedObject = JsonConvert.DeserializeObject<T>(json);
foreach (KeyValuePair<String, Object> pair in dict)
{
foreach (KeyValuePair<String, JToken> node in document)
{
string propertyName = char.ToUpper(node.Key[0]) +
node.Key.Substring(1);
if (propertyName == pair.Key)
{
PropertyInfo property = type.GetProperty(propertyName);
property.SetValue(source, property.GetValue(patchedObject));
break;
}
}
}
return source;
}
I know I'm a little bit late on this answer, but I think I have a solution that doesn't require having to change serialization and also doesn't include reflection (This article refers you to a JsonPatch library that someone wrote that uses reflection).
Basically create a generic class representing a property that could be patched
public class PatchProperty<T> where T : class
{
public bool Include { get; set; }
public T Value { get; set; }
}
And then create models representing the objects that you want to patch where each of the properties is a PatchProperty
public class CustomerPatchModel
{
public PatchProperty<string> FirstName { get; set; }
public PatchProperty<string> LastName { get; set; }
public PatchProperty<int> IntProperty { get; set; }
}
Then your WebApi method would look like
public void PatchCustomer(CustomerPatchModel customerPatchModel)
{
if (customerPatchModel.FirstName?.Include == true)
{
// update first name
string firstName = customerPatchModel.FirstName.Value;
}
if (customerPatchModel.LastName?.Include == true)
{
// update last name
string lastName = customerPatchModel.LastName.Value;
}
if (customerPatchModel.IntProperty?.Include == true)
{
// update int property
int intProperty = customerPatchModel.IntProperty.Value;
}
}
And you could send a request with some Json that looks like
{
"LastName": { "Include": true, "Value": null },
"OtherProperty": { "Include": true, "Value": 7 }
}
Then we would know to ignore FirstName but still set the other properties to null and 7 respectively.
Note that I haven't tested this and I'm not 100% sure it would work. It would basically rely on .NET's ability to serialize the generic PatchProperty. But since the properties on the model specify the type of the generic T, I would think it would be able to. Also since we have "where T : class" on the PatchProperty declaration, the Value should be nullable. I'd be interested to know if this actually works though. Worst case you could implement a StringPatchProperty, IntPatchProperty, etc. for all your property types.

Newtonsoft.JSON Serialization Array, Object, or Null

I have some JSON that can be a List or null. How do I create a POCO for this JSON?
Here is an example array:
http://pastebin.com/qAZF2Ug9
Here is my POCO:
http://pastebin.com/hUtgyytc
How can I tell Newtonsoft.JSON to ignore the SalesLine object, if it is null?
You can specify the settings:
var settings = new Newtonsoft.Json.JsonSerializerSettings {
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore};
and use that in various serializer constructors and serialize calls.
Alternatively, IIRC it supports conditional serialization, i.e.
public bool ShouldSerializeFoo() { return Foo != null; }
// pairs to property Foo
Try to mark this property with JsonProperty attribute
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public SaleLines SaleLines { get; set; }

Deserialize JSON using annotated property names and serialize using original property names (ignoring annotations)

I receive JSON from an external service, and said JSON can have field names that don't work well with C# naming rules. Such as
"$" : {//object definition here}
I can't name my classes as a single '$'. I'm using System.Runtime.Serialization data annotations to specify a field from which to deserialize this weirdly named object:
[DataMember(Name = "$")]
public Item Info { get; set; }
Deserialization works, but now I would like to format this a bit better so that it wouldn't get serialized back to to "$" : {//object definition here} again. Is there a way I could specify a serialization rule that says that this field needs to be serialized with property name Info while still being deserialized from a member named $? What I would like the user of my service to see is this:
"Info" : {//object definition here}
One way to do this is to use a custom IContractResolver that basically tells Json.Net to ignore your property name annotations. On deserialization, you let Json.Net work as normal, so the annotations are used. On serialization, you add the resolver to the serializer settings, which causes Json.Net to use your class property names instead of the annotated names.
Here is the code you would need for the resolver:
class OriginalNameContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
// Get the JsonProperties (with annotated names) from the base class
IList<JsonProperty> list = base.CreateProperties(type, memberSerialization);
// For each property, replace the annotated name with the real name
foreach (JsonProperty prop in list)
{
prop.PropertyName = prop.UnderlyingName;
}
return list;
}
}
Here is a demo showing how it works:
class Program
{
static void Main(string[] args)
{
string json = #"{ ""$"" : { ""$moniker"" : ""blob sprocket"" } }";
Foo foo = JsonConvert.DeserializeObject<Foo>(json);
Console.WriteLine("name from JSON = " + foo.Info.Name);
Console.WriteLine();
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ContractResolver = new OriginalNameContractResolver();
settings.Formatting = Formatting.Indented;
json = JsonConvert.SerializeObject(foo, settings);
Console.WriteLine(json);
}
}
class Foo
{
[JsonProperty("$")]
public Item Info { get; set; }
}
class Item
{
[JsonProperty("$moniker")]
public string Name { get; set; }
}
Output:
name from JSON = blob sprocket
{
"Info": {
"Name": "blob sprocket"
}
}

Categories