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

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();

Related

deserialize json with array of enum

Using the enum:
namespace AppGlobals
{
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum BoardSymbols
{
[EnumMember(Value = "X")]
First = 'X',
[EnumMember(Value = "O")]
Second = 'O',
[EnumMember(Value = "?")]
EMPTY = '?'
}
}
I would like to define a model for my api:
using System;
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace Assignment_1
{
public class MyRequest
{
//...
[Required]
[MinLength(9)]
[MaxLength(9)]
[JsonProperty("changeTypes", ItemConverterType = typeof(JsonStringEnumConverter))]
public AppGlobals.BoardSymbols[] GameBoard { get; set; }
}
}
Where GameBoard should serialize to JSON as an array of strings with names specified by the EnumMember attributes. This approach is adapted from Deserialize json character as enumeration. However, it does not work. This does works if I change the enum to:
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum BoardSymbols
{
X='X',
Y='Y'
}
But I obviously hit a limit on the 'empty' enumeration. How can I do this?
update 2:
I did not have AddNewtonsoftJson() in my startup, converting over fully to Newtonsoft. Now my error is perhaps more actionable:
System.InvalidCastException: Unable to cast object of type 'CustomJsonStringEnumConverter' to type 'Newtonsoft.Json.JsonConverter'.
at Newtonsoft.Json.Serialization.JsonTypeReflector.CreateJsonConverterInstance(Type converterType, Object[] args)
This makes sense, the solution prescribed to me here specified a JsonConverterFactory .. I just need the raw JsonConverter for my use case instead.
TL/DR: You have two basic problems here:
.NET Core 3.0+ has a new built-in JSON serializer System.Text.Json, and you are mixing up attributes and classes between this new serializer and Json.NET. This is very easy to do when both are installed because they share some class names, such as JsonSerializer and JsonConverter.
The new serializer is used by default but does not yet support serialization of enums as strings with custom value names; see System.Text.Json: How do I specify a custom name for an enum value? for details.
The easiest way to solve your problem is to switch back to Json.NET as shown here and use attributes, converters and namespaces exclusively from this serializer.
First let's break down the differences and similarities between the two serializers:
System.Text.Json:
Built into .NET Core 3.0+ automatically and used for JSON serialization by ASP.NET Core 3.0+ by default.
Namespaces System.Text.Json and System.Text.Json.Serialization.
Classes including System.Text.Json.Serialization.JsonConverter, System.Text.Json.Serialization.JsonConverter<T> and System.Text.Json.JsonSerializer.
Attributes including System.Text.Json.Serialization.JsonPropertyNameAttribute, System.Text.Json.Serialization.JsonConverterAttribute and System.Text.Json.Serialization.JsonExtensionDataAttribute.
Serialization of enums as strings is supported by System.Text.Json.Serialization.JsonStringEnumConverter, however renaming via attributes is not implemented.
See this answer to System.Text.Json: How do I specify a custom name for an enum value? for potential workarounds.
Json.NET:
A 3rd-party library that can be used for serialization in ASP.NET Core 3.0+ by adding a NuGet reference to Microsoft.AspNetCore.Mvc.NewtonsoftJson and then calling AddNewtonsoftJson() in Startup.ConfigureServices.
For details see this answer to Where did IMvcBuilder AddJsonOptions go in .Net Core 3.0? by poke.
Namespaces including Newtonsoft.Json, Newtonsoft.Json.Converters, Newtonsoft.Json.Linq and Newtonsoft.Json.Serialization among others.
Classes including Newtonsoft.Json.JsonConverter, Newtonsoft.Json.JsonConverter<T> and Newtonsoft.Json.JsonSerializer
Attributes including Newtonsoft.Json.JsonPropertyAttribute, Newtonsoft.Json.JsonConverterAttribute and Newtonsoft.Json.JsonExtensionDataAttribute among others.
Serialization of enums as renamed strings is supported automatically by Newtonsoft.Json.Converters.StringEnumConverter when the EnumMemberAttribute attribute is applied.
With this in mind, which serializer are you using in your code? Since you helpfully included the namespaces in your question, we can check:
using System.Text.Json.Serialization; // System.Text.Json
using Newtonsoft.Json; // Json.NET
namespace Assignment_1
{
public class MyRequest
{
//...
[JsonProperty( // JsonProperty from Newtonsoft
"changeTypes",
ItemConverterType = typeof(JsonStringEnumConverter)// JsonStringEnumConverter from System.Text.Json
)]
public AppGlobals.BoardSymbols[] GameBoard { get; set; }
}
}
So as you can see, you are mixing up attributes from Newtonsoft with converters from System.Text.Json, which isn't going to work. (Perhaps you selected the namespaces from a "Resolve -> using ..." right-click in Visual Studio?)
So, how to resolve the problem? Since Json.NET supports renaming of enum values out of the box, the easiest way to resolve your problem is to use this serializer. While possibly not as performant as System.Text.Json it is much more complete and full-featured.
To do this, remove the namespaces System.Text.Json.Serialization and System.Text.Json and references to the type JsonStringEnumConverter from your code, and modify MyRequest and BoardSymbols as follows:
using System.Runtime.Serialization;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json;
namespace Assignment_1
{
public class MyRequest
{
//...
[Required]
[MinLength(9)]
[MaxLength(9)]
[JsonProperty("changeTypes")] // No need to add StringEnumConverter here since it's already applied to the enum itself
public AppGlobals.BoardSymbols[] GameBoard { get; set; }
}
}
namespace AppGlobals
{
[JsonConverter(typeof(StringEnumConverter))]
public enum BoardSymbols
{
[EnumMember(Value = "X")]
First = 'X',
[EnumMember(Value = "O")]
Second = 'O',
[EnumMember(Value = "?")]
EMPTY = '?'
}
}
Then NuGet Microsoft.AspNetCore.Mvc.NewtonsoftJson and in Startup.ConfigureServices call AddNewtonsoftJson():
services.AddMvc()
.AddNewtonsoftJson();
Or if you prefer to use StringEnumConverter globally:
services.AddMvc()
.AddNewtonsoftJson(o => o.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter()));
Do take note of the following comment from the docs
Note: If the AddNewtonsoftJson method isn't available, make sure that you installed the Microsoft.AspNetCore.Mvc.NewtonsoftJson package. A common error is to install the Newtonsoft.Json package instead of the Microsoft.AspNetCore.Mvc.NewtonsoftJson package.
Mockup fiddle here.
You could create you own JsonStringEnumAttribute and decorate your enum with it.
using System.Text.Json.Serialization;
class JsonStringEnumAttribute : JsonConverterAttribute
{
public JsonStringEnumAttribute() : base(typeof(JsonStringEnumConverter))
{
}
}
Then put it on your enum:
[JsonStringEnum]
enum MyEnum
{
Value1,
Value2
}
You can then deserialize JSON like this with the string values:
{
"MyEnumProperty1": "Value1",
"MyEnumProperty2": ["Value2", "Value1"]
}
Into a class like this:
class MyClass
{
MyEnum MyEnumProperty1 { get; set; }
MyEnum[] MyEnumProperty2 { get; set; }
}
Using, for example, System.Net.Http.Json:
using HttpClient client = new();
var myObjects = await client.GetFromJsonAsync<MyClass>("/some-endpoint");
Here is a custom converter to deserialize a list of strings ( ex. from your POST payload) to a list of enums , using JsonConverterFactory.
public class ListOfEnumConverter : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert)
{
if (!typeToConvert.IsGenericType)
{
return false;
}
if (typeToConvert.GetGenericTypeDefinition() != typeof(List<>))
{
return false;
}
return typeToConvert.GetGenericArguments()[0].IsEnum;
}
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
Type enumType = typeToConvert.GetGenericArguments()[0];
JsonConverter converter = (JsonConverter)Activator.CreateInstance(
typeof(ListOfEnumConverterInner<>).MakeGenericType(
new Type[] { enumType }),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: new object[] { options },
culture: null)!;
return converter;
}
}
public class ListOfEnumConverterInner<TEnum> :
JsonConverter<List<TEnum>> where TEnum : struct, Enum
{
private readonly JsonConverter<TEnum> _itemConverter;
private readonly Type _itemType;
public ListOfEnumConverterInner(JsonSerializerOptions options)
{
// For performance, use the existing converter.
_itemConverter = (JsonConverter<TEnum>)options
.GetConverter(typeof(TEnum));
// Cache the enum types.
_itemType = typeof(TEnum);
}
public override List<TEnum>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartArray)
{
throw new JsonException();
}
var enumList = new List<TEnum>();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndArray)
{
return enumList;
}
// Get the item.
if (reader.TokenType != JsonTokenType.String)
{
throw new JsonException();
}
string? nextItem = reader.GetString();
// For performance, parse with ignoreCase:false first.
if (!Enum.TryParse(nextItem, ignoreCase: false, out TEnum item) &&
!Enum.TryParse(nextItem, ignoreCase: true, out item))
{
throw new JsonException(
$"Unable to convert \"{nextItem}\" to Enum \"{_itemType}\".");
}
//add to list now
enumList.Add(item);
}
throw new JsonException();
}
public override void Write(Utf8JsonWriter writer, List<TEnum> enumList, JsonSerializerOptions options)
{
writer.WriteStartArray();
foreach (TEnum item in enumList)
{
var nextItem = item.ToString();
writer.WriteStringValue
(options.PropertyNamingPolicy?.ConvertName(nextItem) ?? nextItem);
_itemConverter.Write(writer, item, options);
}
writer.WriteEndArray();
}
}
Note: This can also serialize a List of enums, to a list of strings.
Now, all you need to do is to decorate your input model properties with attributes, that point to this converter like this
public class ModelWithEnum
{
public int test1 { get; set; }
[json.JsonConverter(typeof(JsonStringEnumConverter))]
public ServiceType test2 { get; set; }
[json.JsonConverter(typeof(ListOfEnumConverter))]
public List<ServiceType> test3 { get; set; }
}
Hope this helps !
Give me a thumbs-up, if this saves you few hours ;)

Converting newtonsoft code to System.Text.Json in .net core 3. what's equivalent of JObject.Parse and JsonProperty

I am converting my newtonsoft implementation to new JSON library in .net core 3.0. I have the following code
public static bool IsValidJson(string json)
{
try
{
JObject.Parse(json);
return true;
}
catch (Exception ex)
{
Logger.ErrorFormat("Invalid Json Received {0}", json);
Logger.Fatal(ex.Message);
return false;
}
}
I am not able to find any equivalent for JObject.Parse(json);
Also what will be the attribute JsonProperty equivalent
public class ResponseJson
{
[JsonProperty(PropertyName = "status")]
public bool Status { get; set; }
[JsonProperty(PropertyName = "message")]
public string Message { get; set; }
[JsonProperty(PropertyName = "Log_id")]
public string LogId { get; set; }
[JsonProperty(PropertyName = "Log_status")]
public string LogStatus { get; set; }
public string FailureReason { get; set; }
}
One more thing i will be looking for the equivalent of Formating.None.
You are asking a few questions here:
I am not able to find any equivalent for JObject.Parse(json);
You can use JsonDocument to parse and examine any JSON, starting with its RootElement. The root element is of type JsonElement which represents any JSON value (primitive or not) and corresponds to Newtonsoft's JToken.
But do take note of this documentation remark:
This class utilizes resources from pooled memory to minimize the impact of the garbage collector (GC) in high-usage scenarios. Failure to properly dispose this object will result in the memory not being returned to the pool, which will increase GC impact across various parts of the framework.
When you need to use a JsonElement outside the lifetime of its document, you must clone it:
Gets a JsonElement that can be safely stored beyond the lifetime of the original JsonDocument.
Also note that JsonDocument is currently read-only and does not provide an API for creating or modifying JSON. There is an open issue Issue #39922: Writable Json DOM tracking this.
An example of use is as follows:
//https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8#using-declarations
using var doc = JsonDocument.Parse(json);
//Print the property names.
var names = doc.RootElement.EnumerateObject().Select(p => p.Name);
Console.WriteLine("Property names: {0}", string.Join(",", names)); // Property names: status,message,Log_id,Log_status,FailureReason
//Re-serialize with indentation.
using var ms = new MemoryStream();
using (var writer = new Utf8JsonWriter(ms, new JsonWriterOptions { Indented = true }))
{
doc.WriteTo(writer);
}
var json2 = Encoding.UTF8.GetString(ms.GetBuffer(), 0, checked((int)ms.Length));
Console.WriteLine(json2);
Also what will be the attribute JsonProperty equivalent?
Attributes that can control JsonSerializer are placed in the System.Text.Json.Serialization namespace and inherit from an abstract base class JsonAttribute. Unlike JsonProperty, there is no omnibus attribute that can control all aspects of property serialization. Instead there are specific attributes to control specific aspects.
As of .NET Core 3 these include:
[JsonPropertyNameAttribute(string)]:
Specifies the property name that is present in the JSON when serializing and deserializing. This overrides any naming policy specified by JsonNamingPolicy.
This is attribute you want to use to control the serialized names of your ResponseJson class:
public class ResponseJson
{
[JsonPropertyName("status")]
public bool Status { get; set; }
[JsonPropertyName("message")]
public string Message { get; set; }
[JsonPropertyName("Log_id")]
public string LogId { get; set; }
[JsonPropertyName("Log_status")]
public string LogStatus { get; set; }
public string FailureReason { get; set; }
}
[JsonConverterAttribute(Type)]:
When placed on a type, the specified converter will be used unless a compatible converter is added to the JsonSerializerOptions.Converters collection or there is another JsonConverterAttribute on a property of the same type.
Note that the documented priority of converters -- Attribute on property, then the Converters collection in options, then the Attribute on type -- differs from the documented order for Newtonsoft converters, which is the JsonConverter defined by attribute on a member, then the JsonConverter defined by an attribute on a class, and finally any converters passed to the JsonSerializer.
[JsonExtensionDataAttribute] - corresponds to Newtonsoft's [JsonExtensionData].
[JsonIgnoreAttribute] - corresponds to Newtonsoft's [JsonIgnore].
When writing JSON via Utf8JsonWriter, indentation can be controlled by setting JsonWriterOptions.Indented to true or false.
When serializing to JSON via JsonSerializer.Serialize, indentation can be controlled by setting JsonSerializerOptions.WriteIndented to true or false.
Demo fiddle here showing serialization with JsonSerializer and parsing with JsonDocument.
This link should get you going, snippets of which I copied below.
https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-apis/
WeatherForecast Deserialize(string json)
{
var options = new JsonSerializerOptions
{
AllowTrailingCommas = true
};
return JsonSerializer.Parse<WeatherForecast>(json, options);
}
class WeatherForecast {
public DateTimeOffset Date { get; set; }
// Always in Celsius.
[JsonPropertyName("temp")]
public int TemperatureC { get; set; }
public string Summary { get; set; }
// Don't serialize this property.
[JsonIgnore]
public bool IsHot => TemperatureC >= 30;
}

Decimal properties are serialized even are not populated

I had to generate some classes from an xsd file. The classes and properties are generated correct with xml serialization annotation. The problem is that the decimal properties of a class are serialized with Newtonsoft.Json even are not populated. I would like to serialize only the decimal properties that are properly populated. Amount is part of SaleMessage
For example:
class Amount
{
[System.Xml.Serialization.XmlAttributeAttribute()]
public decimal RequestedAmount;
[System.Xml.Serialization.XmlAttributeAttribute()]
public decimal CashBackAmount;
[System.Xml.Serialization.XmlAttributeAttribute()]
public decimal TipAmount;
}
//Usage
var amount = new Amount()
{
RequestedAmount = 12.0
}
Using this structure it will always serialize all the properties
like this
{"RequestedAmount":12.0,"CashBackAmount":0.0,"TipAmount":0.0}
Which is not the expected behaviour.
The question is how can I modify the serialization to not parse the not set properties
static string Serialize(SaleMessage saleMessage)
{
var serialize= JsonConvert.SerializeObject(saleToPoiMessage,
new StringEnumConverter(),
new IsoDateTimeConverter() { DateTimeFormat = DateTimeFormat });
return serialize;
}
Any help is appreciated :)
You can set the DefaultValueHandling setting to Ignore to suppress serialization of values that are equal to their default value.
var settings = new JsonSerializerSettings
{
Converters = new List<JsonConverter>
{
new StringEnumConverter(),
new IsoDateTimeConverter() { DateTimeFormat = DateTimeFormat }
},
DefaultValueHandling = DefaultValueHandling.Ignore
};
var json = JsonConvert.SerializeObject(saleMessage, settings);
Fiddle: https://dotnetfiddle.net/o32k0U
Since it's of type decimal primitive it will have some value by default. I think you need to implement the serializer utility Newtonsoft.Json uses - on your own. Where you will not include the decimal values of 0.0 (if this suits the business logic).
Another option is not to use the primitive class and then set up the property of removing null values while serializing. I believe you can set up this config parameter in Newtonsoft.
Check this: https://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm
In my case I changed the primitive types to nullable.
public decimal? CashBackAmount {get; set;}
This worked for me. I prefer the #Brian Rogers answer. :)

EnumMemberAttribute Value is ignored by DataContractJsonSerializer

This is my code:
[DataContract] // (Name = "Type")]
public enum Purpose
{
[EnumMember(Value = "definitionTarget")]
DefinitionTarget = 0,
[EnumMember(Value = "definitionSource")]
DefinitionSource = 1,
[EnumMember(Value = "semanticRole")]
SemanticRole = 2,
[EnumMember(Value = "dataType")]
DataType = 3
}
I want the enum values to display according to the strings given, not the integer values. For some reason, the values are ignored.
The serialization code is nothing fancy:
protected string GetRuntimeValue(RuntimeValue value)
{
MemoryStream ms = new MemoryStream();
_serializer.WriteObject(ms, value);
return System.Text.Encoding.UTF8.GetString(ms.ToArray());
}
I went to the Microsoft documentation and found an example with some boilerplate code where they inherit IExtensibleDataObject (no explanation why). I added the code to my base class, no change.
What am I doing wrong? Should be something simple, no?
The bad news is that DataContractJsonSerializer ignores the EnumMember attribute and this is by design:
Enumeration member values are treated as numbers in JSON, which is
different from how they are treated in data contracts, where they are
included as member names. ... The EnumMemberAttribute and the NonSerializedAttribute attributes are ignored if used.
A Microsoft support agent also confirmed this and suggested a kludge which is to expose a property decorated with DataMember for the serializer to use instead, which calls Enum.GetName() to get the string name of the enum value.
It would need to be modified in your case to return a string with the first letter converted to lower case:
[DataContract]
public class RuntimeValue
{
public Purpose Purpose { get; set; }
[DataMember(Name = "Purpose")]
string PurposeString
{
get { return Enum.GetName(typeof(Purpose), this.Purpose).FirstCharacterToLower(); }
set { this.Purpose = (Purpose)Enum.Parse(typeof(Purpose), value, true); }
}
}
Uses an extension method borrowed from here to do the lower case stuff:
public static string FirstCharacterToLower(this string str)
{
if (String.IsNullOrEmpty(str) || Char.IsLower(str, 0))
return str;
return Char.ToLowerInvariant(str[0]) + str.Substring(1);
}
Testing:
Console.WriteLine(GetRuntimeValue(new RuntimeValue()));
Output:
{"Purpose":"definitionTarget"}
Personally I would use JSON.NET instead if possible but for the sake of my answer I am assuming that you have already considered that option and have good reason to stick with this serializer.

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.

Categories