Newtonsoft.JSON Serialization Array, Object, or Null - c#

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; }

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

Why Newtonsoft's serializer can create json, which wouldn't be able to deserialize?

I've notice such strange thing. Why can such instance be serialized, instead of exception? Why do I get exception at the last command, the same option makes this json?
class Dto
{
[JsonProperty(Required = Required.Always)]
public List<string> Property { get; set; }
}
static void Main(string[] args)
{
var dto = new Dto
{
Property = null
};
var settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
};
var json = JsonConvert.SerializeObject(dto, settings);
JsonConvert.DeserializeObject<Dto>(json, settings);
}
The Required option for JsonPropertyAttribute is only applicable for deserialization. See https://www.newtonsoft.com/json/help/html/JsonPropertyRequired.htm.
Thus when you ignore null values in the settings, which applies to both serialization and deserialization, you allow it to be omitted when serialized - which also means that there will be nothing there to deserialize, hence the exception.
#fredrik's answer is not correct.
[JsonProperty(Required = Required.Always)] is applicable on both Serialization and Deserialization. If you want to ensure, try this code:
class MyClass
{
public int Id { get; set; }
[JsonProperty(Required = Required.Always)]
public string Data { get; set; } = "default";
}
var originObj = new MyClass() {Id = 12, Data = null};
var setting = new JsonSerializerSettings {NullValueHandling = NullValueHandling.Ignore};
var jsn1 = JsonConvert.SerializeObject(originObj); //throws
Investigating the Newtonsoft source code we can find that it is just implementation.
It allows you to skip null value if there is NullValueHandling = NullValueHandling.Ignore on Serialization - Just open JsonSerializerInternalWriter Implementation and find CalculatePropertyValues method (then find ShouldWriteProperty condition).
But it ignores any settings and throws an exception if Property is marked as Required - Find EndProcessProperty here.
JsonConvert.DeserializeObject<Dto>(json, Settings);
Looks like a typo due to the capital S of Settings when it should be settings.

Determining JSON.Net property mappings based on JsonSerializerSettings

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.

Create custom JSON converter using Newtonsoft.JSON library to get additional data without overriding the CanRead and CanWrite flags

I am currently working on a Custom JSON converter to be used in a WebAPI project. The requirement is - I have a DTO object having some properties. The APIs can be consumed by multiple clients. Depending upon a client few of my DTO Entities might have some additional data apart from the properties already present in the DTO Model. I need to create a custom JSON converter to Serialize and Deserialize this data.
//DTO
class AbcDTO
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public List<AdditionalProperty> AdditionalData { get; set; }
}
//AdditionalProperty class
class AdditionalProperty
{
public string Name { get; set; }
public object Value { get; set; }
}
//Request JSON Body
{
"Prop1": "Val1",
"Prop2": "Val2",
"AdditionalProp3": "Val3",
"AdditionalProp4": "Val4"
}
//After Deserialization the object should be as below
AbcDTO dto = {
Prop1 = "Val1",
Prop2 = "Val2",
AdditionalData = [
{ Name = "AdditionalProp3", Value = "Val3" },
{ Name = "AdditionalProp4", Value = "Val4" }]
}
//After Serialization of the above dto object the JSON should convert back to the Request JSON Body format
We don't want to use the JsonExtensionData attribute provided by Newtonsoft.JSON as we would need to keep the property as Dictionary<string, JToken> -- but we don't want to pass JToken to below layers.
Created a custom JSON converter -
class CustomJsonConverter : JsonConverter
{
bool _canWrite = true;
bool _canRead = true;
public override bool CanConvert(Type objectType)
{
return typeof(IEntity).IsAssignableFrom(objectType);
}
public override bool CanWrite
{
get
{
return _canWrite;
}
}
public override bool CanRead
{
get
{
return _canRead;
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jObject = JObject.Load(reader);
PropertyInfo[] availablePropertyNames = objectType.GetProperties();
List<AdditionalProperties> additionalData = new List<AdditionalProperties>();
IEntity obj;
_canRead = false;
obj = (IEntity)jObject.ToObject(objectType);
_canRead = true;
IEnumerable<JProperty> properties = jObject.Properties();
foreach (JProperty prop in properties)
{
if (availablePropertyNames.Count(x => x.Name.Equals(prop.Name)) == 0)
{
AdditionalProperties addProp = new AdditionalProperties
{
Name = prop.Name,
Value = prop.Value.ToObject<object>(),
};
additionalData.Add(addProp);
}
}
obj.AdditionalData = additionalData;
return obj;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
IEntity obj = (IEntity)value;
List<AdditionalProperties> additionalData = obj.AdditionalData;
JObject jObj;
_canWrite = false;
jObj = (JObject)JToken.FromObject(obj);
_canWrite = true;
jObj.Remove("AdditionalData");
foreach (AdditionalProperties data in additionalData)
{
jObj.Add(data.Name, JToken.FromObject(data.Value));
}
jObj.WriteTo(writer);
}
}
WebAPI ContractResolver creates 1 JSON converter per Entity. Now the issue is _canRead and _canWrite are not thread-safe. Need to use them to use the base implementation provided by Newtonsoft. If we don't use them, the ToObject and FromObject method again calls the custom converter methods internally resulting in infinite recursion. Using them with logs, reduces performance. Is there any way we can create a custom converter using the base implementation of Newtonsoft.JSON serialization/deserialization without using canRead and canWrite flags?
I can also have reference type child properties - say Person contains Address. I want to capture additional data for both Parent and Child entities. The additional data will not contain data of reference type.
It's possible to disable the converter using a thread static variable or ThreadLocal<T> member, as shown in JSON.Net throws StackOverflowException when using JsonConvert or Generic method of modifying JSON before being returned to client. However, I'd like to suggest a simpler way of solving your problem.
You wrote, We dont want to use the JsonExtensionData attribute provided by Newtonsoft.JSON as we need to keep the property as Dictionary and we dont want to pass JToken to below layers. It is not necessary for the extension data dictionary to have values of type JToken. Values of type object are supported for extension data dictionaries, e.g.:
class AbcDTO
{
public AbcDTO() { this.AdditionalData = new Dictionary<string, object>(); }
public string Prop1 { get; set; }
public string Prop2 { get; set; }
[JsonExtensionData]
public Dictionary<string, object> AdditionalData { get; private set; }
}
When the extension data dictionary is of type Dictionary<string, object>, Json.NET will deserialize JSON primitive values to their equivalent .Net primitives -- string, bool, long and so on -- rather than to JValue objects. Only when encountering an additional property whose value is a JSON object or array will a JToken be added to the dictionary, in which case you can use the answers from How do I use JSON.NET to deserialize into nested/recursive Dictionary and List? to convert the JToken to a conventional .Net type. (However, your question states that The additional data will not contain data of reference type, so this should not be necessary.)
Using [JsonExtensionData] in this manner completely avoids the need for a converter while also deserializing primitives as per your requirements, and thus seems much simpler than the original design shown in the question.
Sample .Net fiddle demonstrating that extension properties can be deserialized into AbcDTO and asserting that none of them are of type JToken.

Using JSON.NET, how do I serialize these inherited members?

I have the following:
public class MyClass : SuperClass {
[JsonProperty]
public virtual string Id { get; set; }
}
public abstract class SuperClass {
public int GetHashCode() {
//do things here
}
}
I cannot alter SuperClass. When I go to serialize to Json using JsonNet I'll do something like this:
JsonSerializerSettings serializer = new JsonSerializerSettings {
//serializer settings
};
var jsonNetResult = new JsonNetResult {
Data = myClass,
SerializerSettings = serializer
};
return jsonNetResult;
Obviously it will not serialize GetHashCode(). If I go:
var jsonNetResult = new JsonNetResult {
Data = myClass.GetHashCode(),
SerializerSettings = serializer
};
It will correctly serialize the value, is there some serializer setting I can use to tell it to include GetHashCode()?
Edit: I should add that right now I'm creating a property with only get to accomplish this, i.e.
[JsonProperty]
public virtual int GetHashCodeJson { get { return GetHashCode(); }
This is not so much an issue with JSON.Net as with .net serialization in general.
You need to serialize objects by their properties and you are asking to serialize the return value of a method. So there is not a way to do this with the syntax you want.
That you are able to do this:
Data = myClass.GetHashCode()
Only means that the return value of the method (an int) can be serialized and not that the serializer cares at all about what method that value is coming from.
If you think about it, there is not a lot of sense to saying that a value is the serialized return value of a method because how do you deserialize that then? You would never be able to write the value back to the method because its a return value only, not a 2-way relationship like a property with {get;set;}.

Categories