I'm trying to serialize my struct so that the strings that didn't get a value get their default value "" instead of null
[JsonProperty(PropertyName = "myProperty", DefaultValueHandling = DefaultValueHandling.Populate)]
[DefaultValue("")]
public string MyProperty{ get; set; }
My result in the Json string:
"myProperty": null,
what i want
"myProperty": "",
I also tried creating a converter without any effect, the can Convert and WriteJson functions aren't even firing for some reason:
[JsonProperty(PropertyName = "myProperty")]
[JsonConverter(typeof(NullToEmptyStringConverter))]
public string MyProperty{ get; set; }
class NullToEmptyStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(object[]);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
writer.WriteValue("");
}
}
This isnt helping either Json.Net How to deserialize null as empty string?
This should work:
var settings = new JsonSerializerSettings() { ContractResolver= new NullToEmptyStringResolver() };
var str = JsonConvert.SerializeObject(yourObj, settings);
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Reflection;
public class NullToEmptyStringResolver : Newtonsoft.Json.Serialization.DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
return type.GetProperties()
.Select(p=>{
var jp = base.CreateProperty(p, memberSerialization);
jp.ValueProvider = new NullToEmptyStringValueProvider(p);
return jp;
}).ToList();
}
}
public class NullToEmptyStringValueProvider : IValueProvider
{
PropertyInfo _MemberInfo;
public NullToEmptyStringValueProvider(PropertyInfo memberInfo)
{
_MemberInfo = memberInfo;
}
public object GetValue(object target)
{
object result = _MemberInfo.GetValue(target);
if (_MemberInfo.PropertyType == typeof(string) && result == null) result = "";
return result;
}
public void SetValue(object target, object value)
{
_MemberInfo.SetValue(target, value);
}
}
While the accepted answer pointed me in the right direction, it appears quite brittle. I do not want to worry about resolving the list of JsonProperty objects and implementing IValueResolver myself when there are perfectly functional tools available for doing that in Json.NET (which could have all kinds of optimizations and corner case handling built in that a basic reflection-based reimplementation won't).
My solution performs minimal overriding and resolver substitution to ensure that only parts that absolutely need to change are actually altered:
public sealed class SubstituteNullWithEmptyStringContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (property.PropertyType == typeof(string))
{
// Wrap value provider supplied by Json.NET.
property.ValueProvider = new NullToEmptyStringValueProvider(property.ValueProvider);
}
return property;
}
sealed class NullToEmptyStringValueProvider : IValueProvider
{
private readonly IValueProvider Provider;
public NullToEmptyStringValueProvider(IValueProvider provider)
{
if (provider == null) throw new ArgumentNullException("provider");
Provider = provider;
}
public object GetValue(object target)
{
return Provider.GetValue(target) ?? "";
}
public void SetValue(object target, object value)
{
Provider.SetValue(target, value);
}
}
}
Well, my solution pretty simple, but does not use JSON.NET features, just add backend field to your property:
public class Test
{
private string _myProperty = string.Empty;
[JsonProperty(PropertyName = "myProperty")]
public string MyProperty
{
get { return _myProperty; }
set { _myProperty = value; }
}
}
Edit:
In c# 6.0 property initialization will be available:
public class Test
{
[JsonProperty(PropertyName = "myProperty")]
public string MyProperty { get; set;} = "";
}
#Kirill Shlenskiy's solution is great, but it does not take the NullValueHandling attribute in consideration.
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string Remark{ get; set; }
Here is an improved version that will take care of it. If NullValueHandling.Ignore is set and the value is null, it will be skipped in the JSON output.
public sealed class SubstituteNullWithEmptyStringContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (property.PropertyType == typeof(string))
{
// Wrap value provider supplied by Json.NET.
property.ValueProvider = new NullToEmptyStringValueProvider(property.ValueProvider, property.NullValueHandling);
}
return property;
}
sealed class NullToEmptyStringValueProvider : IValueProvider
{
private readonly IValueProvider Provider;
private readonly NullValueHandling? NullHandling;
public NullToEmptyStringValueProvider(IValueProvider provider, NullValueHandling? nullValueHandling)
{
Provider = provider ?? throw new ArgumentNullException("provider");
NullHandling = nullValueHandling;
}
public object GetValue(object target)
{
if (NullHandling.HasValue
&& NullHandling.Value == NullValueHandling.Ignore
&& Provider.GetValue(target) == null )
{
return null;
}
return Provider.GetValue(target) ?? "";
}
public void SetValue(object target, object value)
{
Provider.SetValue(target, value);
}
}
}
An alternate solution (and perhaps a little cleaner). You can create your own JsonConverter class
class JsonNullToEmptyStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return existingValue ?? string.Empty;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value ?? string.Empty);
}
}
Once this has been written up, you can tack it on to your property as an attribute:
[JsonConverter(typeof(JsonNullToEmptyStringConverter))]
public string CommentType { get; set; }
Original class is not mine. Thanks in advance to the many like you who contribute!
I added and solved null problems.
Public Class JsonBooleanConverter
Inherits JsonConverter
Public Status As String
Public ErrorCode As String
<JsonProperty(NullValueHandling:=NullValueHandling.Ignore)>
Public ErrorMessage As String
Public Overrides ReadOnly Property CanWrite As Boolean
Get
Return False
End Get
End Property
Public Overrides Sub WriteJson(ByVal writer As JsonWriter, ByVal value As Object, ByVal serializer As JsonSerializer)
Throw New NotImplementedException()
End Sub
Public Overrides Function ReadJson(ByVal reader As JsonReader, ByVal objectType As Type, ByVal existingValue As Object, ByVal serializer As JsonSerializer) As Object
If IsNothing(reader.Value) Then
Return If(existingValue, String.Empty)
End If
Dim value = reader.Value.ToString().ToLower().Trim()
If objectType = GetType(Boolean) Then
Select Case value
Case "true", "yes", "y", "1"
Return True
Case Else
Return False
End Select
ElseIf objectType = GetType(DateTime) Then
Return If(existingValue, String.Empty)
End If
Return If(existingValue, String.Empty)
'Return False
End Function
Public Overrides Function CanConvert(ByVal objectType As Type) As Boolean
If objectType = GetType(Boolean) Then
Return True
ElseIf objectType = GetType(DateTime) Then
Return True
End If
Return False
End Function
End Class
USAGE:
Dim listObjs As List(Of YourClass) = JsonConvert.DeserializeObject(Of List(Of YourClass))(responseFromServer, New JsonBooleanConverter())
Or:
Dim listObjs As YourClass= JsonConvert.DeserializeObject(Of YourClass)(responseFromServer, New JsonBooleanConverter())
With System.Text.Json and .NET Core 3.0 this worked for me:
var jsonSerializerOptions = new JsonSerializerOptions()
{
IgnoreNullValues = true
};
var myJson = JsonSerializer.Serialize(myObject,
jsonSerializerOptions );
using .NET 6 this was the solution:
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
HERE https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-ignore-properties?pivots=dotnet-6-0
with Newtonsoft
https://www.newtonsoft.com/json/help/html/NullValueHandlingIgnore.htm
,
Formatting.Indented, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
}
Person person = new Person
{
Name = "Nigal Newborn",
Age = 1
};
string jsonIncludeNullValues = JsonConvert.SerializeObject(person, Formatting.Indented);
Console.WriteLine(jsonIncludeNullValues);
// {
// "Name": "Nigal Newborn",
// "Age": 1,
// "Partner": null,
// "Salary": null
// }
string jsonIgnoreNullValues = JsonConvert.SerializeObject(person,
Formatting.Indented, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
} );
Console.WriteLine(jsonIgnoreNullValues);
// {
// "Name": "Nigal Newborn",
// "Age": 1
// }
Related
I have a class PersonDto which contains a property instance of type AddressDto. I am building a custom ContractResolver named eg. ShouldSerializeContractResolver with Newtonsoft.Json marshalling .NET lib that will include only specific properties into serialization that are marked with my custom attribute eg. [ShouldSerialize]
The problem occurs when the CreateProperty method of the resolver goes into the complex / custom type of the PersonDto ie. it goes into the AddressDto and it is not aware that the property instance is tagged with the [ShouldSerialize] attribute. The resulting serialization then looks like "Address": {} instead of "Address": { "StreetNumber": 123 }
The code looks like:
class AddressDto
{
// PROBLEM 1/2: value does not get serialized, but I want it serialized as its property is [ShouldSerialize] attr tagged
public int StreetNumber { get; set; }
}
class PersonDto
{
public string Name { get; set; } // should not serialize as has not attr on it
[ShouldSerialize]
public string Id { get; set; }
[ShouldSerialize]
public AddressDto Address { get; set; }
}
// JSON contract resolver:
public class ShouldSerializeContractResolver: DefaultContractResolver
{
public ShouldSerializeContractResolver() { }
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
var attr = member.GetCustomAttribute<ShouldSerializeContractResolver>(inherit: false);
// PROBLEM 2/2: here I need the code to access the member.DeclaringType instance somehow and then
// find its AddressDto property and its GetCustomAttribute<ShouldSerializeContractResolver>
if (attr is null)
{
property.ShouldSerialize = instance => { return false; };
}
return property;
}
}
// code invoked as:
PersonDto somePerson = IrrelevantSomePersonCreateNewFactoryFn();
var jsonSettings = new JsonSerializerSettings { ContractResolver = new ShouldSerializeContractResolver() };
var strJson = JsonConvert.SerializeObject(somePerson, jsonSettings);
The serializer works in a "flat" mode, ie. it runs through all the props with the resolver and it comes to the point where the member is StreetNumber and from it I do not know how to access the "parent" MemberInfo, which would be great.
What I find as the core issue here is I do not have the "parent" / DeclaringType object instance and need to find a way on how to obtain it.
Please note that I can not solve this issue through [JsonProperty], [JsonIgnore] etc. as my attribute is complex and involves its own logic.
You would like AddressDto to be serialized differently depending upon whether it was encountered via a property marked with [ShouldSerialize], however that cannot easily be done using a custom contract resolver because Json.NET creates exactly one contract for each type no matter where it is encountered in the serialization graph. I.e. a contract resolver will generate the same contract for AddressDto for both of the following data models:
class PersonDto
{
public string Name { get; set; } // should not serialize as has not attr on it
[ShouldSerialize]
public string Id { get; set; }
[ShouldSerialize]
public AddressDto Address { get; set; } // This and its properties should get serialized.
}
class SomeOtherDto
{
[ShouldSerialize]
public string SomeOtherValue { get; set; }
public AddressDto SecretAddress { get; set; } // Should not get serialized.
}
This is why you cannot get the referring property's attributes when creating the properties for a referenced type.
Instead, you will need to track in runtime when the serializer begins and ends serialization of a [ShouldSerialize] property, setting some thread-safe state variable while inside. This can be done e.g. by using your contract resolver to inject a custom JsonConverter that sets the necessary state, disables itself temporarily to prevent recursive calls, then does a default serialization:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class ShouldSerializeAttribute : System.Attribute
{
}
public class ShouldSerializeContractResolver: DefaultContractResolver
{
static ThreadLocal<bool> inShouldSerialize = new (() => false);
static bool InShouldSerialize { get => inShouldSerialize.Value; set => inShouldSerialize.Value = value; }
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
var attr = member.GetCustomAttribute<ShouldSerializeAttribute>(inherit: false);
if (attr is null)
{
var old = property.ShouldSerialize;
property.ShouldSerialize = instance => InShouldSerialize && (old == null || old(instance));
}
else
{
var old = property.Converter;
if (old == null)
property.Converter = new InShouldSerializeConverter();
else
property.Converter = new InShouldSerializeConverterDecorator(old);
}
return property;
}
class InShouldSerializeConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var old = InShouldSerialize;
try
{
InShouldSerialize = true;
serializer.Serialize(writer, value);
}
finally
{
InShouldSerialize = old;
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();
public override bool CanRead => false;
public override bool CanConvert(Type objectType) => throw new NotImplementedException();
}
class InShouldSerializeConverterDecorator : JsonConverter
{
readonly JsonConverter innerConverter;
public InShouldSerializeConverterDecorator(JsonConverter innerConverter) => this.innerConverter = innerConverter ?? throw new ArgumentNullException(nameof(innerConverter));
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var old = InShouldSerialize;
try
{
InShouldSerialize = true;
if (innerConverter.CanWrite)
innerConverter.WriteJson(writer, value, serializer);
else
serializer.Serialize(writer, value);
}
finally
{
InShouldSerialize = old;
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var old = InShouldSerialize;
try
{
InShouldSerialize = true;
if (innerConverter.CanRead)
return innerConverter.ReadJson(reader, objectType, existingValue, serializer);
else
return serializer.Deserialize(reader, objectType);
}
finally
{
InShouldSerialize = old;
}
}
public override bool CanConvert(Type objectType) => throw new NotImplementedException();
}
}
Then serialize as follows:
IContractResolver resolver = new ShouldSerializeContractResolver(); // Cache statically & reuse for best performance
var settings = new JsonSerializerSettings
{
ContractResolver = resolver,
};
var json = JsonConvert.SerializeObject(person, Formatting.Indented, settings);
Notes:
Newtonsoft recommends caching and reusing your contract resolver for best performance.
The above implementation has the limitation that, if your [ShouldSerialize] is also marked with JsonPropertyAttribute, fields that control serialization of the property value such as ItemConverterType and IsReference will be ignored.
Demo fiddle here.
I'm trying to fix my SendGridPlus library to deal with SendGrid events, but I'm having some trouble with the inconsistent treatment of categories in the API.
In the following example payload taken from the SendGrid API reference, you'll notice that the category property for each item can either be a single string or an array of strings.
[
{
"email": "john.doe#sendgrid.com",
"timestamp": 1337966815,
"category": [
"newuser",
"transactional"
],
"event": "open"
},
{
"email": "jane.doe#sendgrid.com",
"timestamp": 1337966815,
"category": "olduser",
"event": "open"
}
]
It seems my options to make JSON.NET like this are fixing the string before it comes in, or configuring JSON.NET to accept the incorrect data. I'd rather not do any string parsing if I can get away with it.
Is there any other way I can handle this using Json.Net?
The best way to handle this situation is to use a custom JsonConverter.
Before we get to the converter, we'll need to define a class to deserialize the data into. For the Categories property that can vary between a single item and an array, define it as a List<string> and mark it with a [JsonConverter] attribute so that JSON.Net will know to use the custom converter for that property. I would also recommend using [JsonProperty] attributes so that the member properties can be given meaningful names independent of what is defined in the JSON.
class Item
{
[JsonProperty("email")]
public string Email { get; set; }
[JsonProperty("timestamp")]
public int Timestamp { get; set; }
[JsonProperty("event")]
public string Event { get; set; }
[JsonProperty("category")]
[JsonConverter(typeof(SingleOrArrayConverter<string>))]
public List<string> Categories { get; set; }
}
Here is how I would implement the converter. Notice I've made the converter generic so that it can be used with strings or other types of objects as needed.
class SingleOrArrayConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(List<T>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<T>>();
}
return new List<T> { token.ToObject<T>() };
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Here is an short program demonstrating the converter in action with your sample data:
class Program
{
static void Main(string[] args)
{
string json = #"
[
{
""email"": ""john.doe#sendgrid.com"",
""timestamp"": 1337966815,
""category"": [
""newuser"",
""transactional""
],
""event"": ""open""
},
{
""email"": ""jane.doe#sendgrid.com"",
""timestamp"": 1337966815,
""category"": ""olduser"",
""event"": ""open""
}
]";
List<Item> list = JsonConvert.DeserializeObject<List<Item>>(json);
foreach (Item obj in list)
{
Console.WriteLine("email: " + obj.Email);
Console.WriteLine("timestamp: " + obj.Timestamp);
Console.WriteLine("event: " + obj.Event);
Console.WriteLine("categories: " + string.Join(", ", obj.Categories));
Console.WriteLine();
}
}
}
And finally, here is the output of the above:
email: john.doe#sendgrid.com
timestamp: 1337966815
event: open
categories: newuser, transactional
email: jane.doe#sendgrid.com
timestamp: 1337966815
event: open
categories: olduser
Fiddle: https://dotnetfiddle.net/lERrmu
EDIT
If you need to go the other way, i.e. serialize, while keeping the same format, you can implement the WriteJson() method of the converter as shown below. (Be sure to remove the CanWrite override or change it to return true, or else WriteJson() will never be called.)
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
List<T> list = (List<T>)value;
if (list.Count == 1)
{
value = list[0];
}
serializer.Serialize(writer, value);
}
Fiddle: https://dotnetfiddle.net/XG3eRy
I was working on this for ages, and thanks to Brian for his answer.
All I am adding is the vb.net answer!:
Public Class SingleValueArrayConverter(Of T)
sometimes-array-and-sometimes-object
Inherits JsonConverter
Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
Throw New NotImplementedException()
End Sub
Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim retVal As Object = New [Object]()
If reader.TokenType = JsonToken.StartObject Then
Dim instance As T = DirectCast(serializer.Deserialize(reader, GetType(T)), T)
retVal = New List(Of T)() From { _
instance _
}
ElseIf reader.TokenType = JsonToken.StartArray Then
retVal = serializer.Deserialize(reader, objectType)
End If
Return retVal
End Function
Public Overrides Function CanConvert(objectType As Type) As Boolean
Return False
End Function
End Class
then in your class:
<JsonProperty(PropertyName:="JsonName)> _
<JsonConverter(GetType(SingleValueArrayConverter(Of YourObject)))> _
Public Property YourLocalName As List(Of YourObject)
Hope this saves you some time
As a minor variation to the great answer by Brian Rogers, here are two tweaked versions of SingleOrArrayConverter<T>.
Firstly, here is a version that works for all List<T> for every type T that is not itself a collection:
public class SingleOrArrayListConverter : JsonConverter
{
// Adapted from this answer https://stackoverflow.com/a/18997172
// to https://stackoverflow.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n
// by Brian Rogers https://stackoverflow.com/users/10263/brian-rogers
readonly bool canWrite;
readonly IContractResolver resolver;
public SingleOrArrayListConverter() : this(false) { }
public SingleOrArrayListConverter(bool canWrite) : this(canWrite, null) { }
public SingleOrArrayListConverter(bool canWrite, IContractResolver resolver)
{
this.canWrite = canWrite;
// Use the global default resolver if none is passed in.
this.resolver = resolver ?? new JsonSerializer().ContractResolver;
}
static bool CanConvert(Type objectType, IContractResolver resolver)
{
Type itemType;
JsonArrayContract contract;
return CanConvert(objectType, resolver, out itemType, out contract);
}
static bool CanConvert(Type objectType, IContractResolver resolver, out Type itemType, out JsonArrayContract contract)
{
if ((itemType = objectType.GetListItemType()) == null)
{
itemType = null;
contract = null;
return false;
}
// Ensure that [JsonObject] is not applied to the type.
if ((contract = resolver.ResolveContract(objectType) as JsonArrayContract) == null)
return false;
var itemContract = resolver.ResolveContract(itemType);
// Not implemented for jagged arrays.
if (itemContract is JsonArrayContract)
return false;
return true;
}
public override bool CanConvert(Type objectType) { return CanConvert(objectType, resolver); }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
Type itemType;
JsonArrayContract contract;
if (!CanConvert(objectType, serializer.ContractResolver, out itemType, out contract))
throw new JsonSerializationException(string.Format("Invalid type for {0}: {1}", GetType(), objectType));
if (reader.MoveToContent().TokenType == JsonToken.Null)
return null;
var list = (IList)(existingValue ?? contract.DefaultCreator());
if (reader.TokenType == JsonToken.StartArray)
serializer.Populate(reader, list);
else
// Here we take advantage of the fact that List<T> implements IList to avoid having to use reflection to call the generic Add<T> method.
list.Add(serializer.Deserialize(reader, itemType));
return list;
}
public override bool CanWrite { get { return canWrite; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var list = value as ICollection;
if (list == null)
throw new JsonSerializationException(string.Format("Invalid type for {0}: {1}", GetType(), value.GetType()));
// Here we take advantage of the fact that List<T> implements IList to avoid having to use reflection to call the generic Count method.
if (list.Count == 1)
{
foreach (var item in list)
{
serializer.Serialize(writer, item);
break;
}
}
else
{
writer.WriteStartArray();
foreach (var item in list)
serializer.Serialize(writer, item);
writer.WriteEndArray();
}
}
}
public static partial class JsonExtensions
{
public static JsonReader MoveToContent(this JsonReader reader)
{
while ((reader.TokenType == JsonToken.Comment || reader.TokenType == JsonToken.None) && reader.Read())
;
return reader;
}
internal static Type GetListItemType(this Type type)
{
// Quick reject for performance
if (type.IsPrimitive || type.IsArray || type == typeof(string))
return null;
while (type != null)
{
if (type.IsGenericType)
{
var genType = type.GetGenericTypeDefinition();
if (genType == typeof(List<>))
return type.GetGenericArguments()[0];
}
type = type.BaseType;
}
return null;
}
}
It can be used as follows:
var settings = new JsonSerializerSettings
{
// Pass true if you want single-item lists to be reserialized as single items
Converters = { new SingleOrArrayListConverter(true) },
};
var list = JsonConvert.DeserializeObject<List<Item>>(json, settings);
Notes:
The converter avoids the need to pre-load the entire JSON value into memory as a JToken hierarchy.
The converter does not apply to lists whose items are also serialized as collections, e.g. List<string []>
The Boolean canWrite argument passed to the constructor controls whether to re-serialize single-element lists as JSON values or as JSON arrays.
The converter's ReadJson() uses the existingValue if pre-allocated so as to support populating of get-only list members.
Secondly, here is a version that works with other generic collections such as ObservableCollection<T>:
public class SingleOrArrayCollectionConverter<TCollection, TItem> : JsonConverter
where TCollection : ICollection<TItem>
{
// Adapted from this answer https://stackoverflow.com/a/18997172
// to https://stackoverflow.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n
// by Brian Rogers https://stackoverflow.com/users/10263/brian-rogers
readonly bool canWrite;
public SingleOrArrayCollectionConverter() : this(false) { }
public SingleOrArrayCollectionConverter(bool canWrite) { this.canWrite = canWrite; }
public override bool CanConvert(Type objectType)
{
return typeof(TCollection).IsAssignableFrom(objectType);
}
static void ValidateItemContract(IContractResolver resolver)
{
var itemContract = resolver.ResolveContract(typeof(TItem));
if (itemContract is JsonArrayContract)
throw new JsonSerializationException(string.Format("Item contract type {0} not supported.", itemContract));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
ValidateItemContract(serializer.ContractResolver);
if (reader.MoveToContent().TokenType == JsonToken.Null)
return null;
var list = (ICollection<TItem>)(existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator());
if (reader.TokenType == JsonToken.StartArray)
serializer.Populate(reader, list);
else
list.Add(serializer.Deserialize<TItem>(reader));
return list;
}
public override bool CanWrite { get { return canWrite; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
ValidateItemContract(serializer.ContractResolver);
var list = value as ICollection<TItem>;
if (list == null)
throw new JsonSerializationException(string.Format("Invalid type for {0}: {1}", GetType(), value.GetType()));
if (list.Count == 1)
{
foreach (var item in list)
{
serializer.Serialize(writer, item);
break;
}
}
else
{
writer.WriteStartArray();
foreach (var item in list)
serializer.Serialize(writer, item);
writer.WriteEndArray();
}
}
}
Then, if your model is using, say, an ObservableCollection<T> for some T, you could apply it as follows:
class Item
{
public string Email { get; set; }
public int Timestamp { get; set; }
public string Event { get; set; }
[JsonConverter(typeof(SingleOrArrayCollectionConverter<ObservableCollection<string>, string>))]
public ObservableCollection<string> Category { get; set; }
}
Notes:
In addition to the notes and restrictions for SingleOrArrayListConverter, the TCollection type must be read/write and have a parameterless constructor.
Demo fiddle with basic unit tests here.
To handle this you have to use a custom JsonConverter. But you probably already had that in mind.
You are just looking for a converter that you can use immediately. And this offers more than just a solution for the situation described.
I give an example with the question asked.
How to use my converter:
Place a JsonConverter Attribute above the property. JsonConverter(typeof(SafeCollectionConverter))
public class SendGridEvent
{
[JsonProperty("email")]
public string Email { get; set; }
[JsonProperty("timestamp")]
public long Timestamp { get; set; }
[JsonProperty("category"), JsonConverter(typeof(SafeCollectionConverter))]
public string[] Category { get; set; }
[JsonProperty("event")]
public string Event { get; set; }
}
And this is my converter:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
namespace stackoverflow.question18994685
{
public class SafeCollectionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
//This not works for Populate (on existingValue)
return serializer.Deserialize<JToken>(reader).ToObjectCollectionSafe(objectType, serializer);
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
And this converter uses the following class:
using System;
namespace Newtonsoft.Json.Linq
{
public static class SafeJsonConvertExtensions
{
public static object ToObjectCollectionSafe(this JToken jToken, Type objectType)
{
return ToObjectCollectionSafe(jToken, objectType, JsonSerializer.CreateDefault());
}
public static object ToObjectCollectionSafe(this JToken jToken, Type objectType, JsonSerializer jsonSerializer)
{
var expectArray = typeof(System.Collections.IEnumerable).IsAssignableFrom(objectType);
if (jToken is JArray jArray)
{
if (!expectArray)
{
//to object via singel
if (jArray.Count == 0)
return JValue.CreateNull().ToObject(objectType, jsonSerializer);
if (jArray.Count == 1)
return jArray.First.ToObject(objectType, jsonSerializer);
}
}
else if (expectArray)
{
//to object via JArray
return new JArray(jToken).ToObject(objectType, jsonSerializer);
}
return jToken.ToObject(objectType, jsonSerializer);
}
public static T ToObjectCollectionSafe<T>(this JToken jToken)
{
return (T)ToObjectCollectionSafe(jToken, typeof(T));
}
public static T ToObjectCollectionSafe<T>(this JToken jToken, JsonSerializer jsonSerializer)
{
return (T)ToObjectCollectionSafe(jToken, typeof(T), jsonSerializer);
}
}
}
What does it do exactly?
If you place the converter attribute the converter will be used for this property. You can use it on a normal object if you expect a json array with 1 or no result. Or you use it on an IEnumerable where you expect a json object or json array. (Know that an array -object[]- is an IEnumerable)
A disadvantage is that this converter can only be placed above a property because he thinks he can convert everything. And be warned. A string is also an IEnumerable.
And it offers more than an answer to the question:
If you search for something by id you know that you will get an array back with one or no result.
The ToObjectCollectionSafe<TResult>() method can handle that for you.
This is usable for Single Result vs Array using JSON.net
and handle both a single item and an array for the same property
and can convert an array to a single object.
I made this for REST requests on a server with a filter that returned one result in an array but wanted to get the result back as a single object in my code. And also for a OData result response with expanded result with one item in an array.
Have fun with it.
Just wanted to add to #dbc excellent response above on the SingleOrArrayCollectionConverter. I was able to modify it to use with a stream from an HTTP client. Here is a snippet (you will have to set up the requestUrl (string) and the httpClient (using System.Net.Http;).
public async Task<IList<T>> HttpRequest<T>(HttpClient httpClient, string requestedUrl, CancellationToken cancellationToken)
{
using (var request = new HttpRequestMessage(HttpMethod.Get, requestedUrl))
using (var httpResponseMessage = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
{
if (httpResponseMessage.IsSuccessStatusCode)
{
using var stream = await httpResponseMessage.Content.ReadAsStreamAsync();
using var streamReader = new StreamReader(stream);
using var jsonTextReader = new JsonTextReader(streamReader );
var settings = new JsonSerializerSettings
{
// Pass true if you want single-item lists to be reserialized as single items
Converters = { new SingleOrArrayCollectionConverter(true) },
};
var jsonSerializer = JsonSerializer.Create(settings);
return jsonSerializer.Deserialize<List<T>>(jsonTextReader);
}
I apologize if there are missing brackets or misspellings, it was not easy to paste code in here.
I had a very similar Problem.
My Json Request was completly unknown for me.
I only knew.
There will be an objectId in it and some anonym key value pairs AND arrays.
I used it for an EAV Model i did:
My JSON Request:
{objectId": 2,
"firstName": "Hans",
"email" :[ "a#b.de","a#c.de"],
"name": "Andre",
"something" :["232","123"]
}
My Class i defined:
[JsonConverter(typeof(AnonyObjectConverter))]
public class AnonymObject
{
public AnonymObject()
{
fields = new Dictionary<string, string>();
list = new List<string>();
}
public string objectid { get; set; }
public Dictionary<string, string> fields { get; set; }
public List<string> list { get; set; }
}
and now that i want to deserialize unknown attributes with its value and arrays in it my Converter looks like that:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
AnonymObject anonym = existingValue as AnonymObject ?? new AnonymObject();
bool isList = false;
StringBuilder listValues = new StringBuilder();
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndObject) continue;
if (isList)
{
while (reader.TokenType != JsonToken.EndArray)
{
listValues.Append(reader.Value.ToString() + ", ");
reader.Read();
}
anonym.list.Add(listValues.ToString());
isList = false;
continue;
}
var value = reader.Value.ToString();
switch (value.ToLower())
{
case "objectid":
anonym.objectid = reader.ReadAsString();
break;
default:
string val;
reader.Read();
if(reader.TokenType == JsonToken.StartArray)
{
isList = true;
val = "ValueDummyForEAV";
}
else
{
val = reader.Value.ToString();
}
try
{
anonym.fields.Add(value, val);
}
catch(ArgumentException e)
{
throw new ArgumentException("Multiple Attribute found");
}
break;
}
}
return anonym;
}
So now everytime i get an AnonymObject i can iterate through the Dictionary and everytime there is my Flag "ValueDummyForEAV" i switch to the list, read the first line and split the values. After that i delete the first entry from the list and go on with iteration from the Dictionary.
Maybe someone has the same problem and can use this :)
Regards
Andre
You can use a JSONConverterAttribute as found here: http://james.newtonking.com/projects/json/help/
Presuming you have a class that looks like
public class RootObject
{
public string email { get; set; }
public int timestamp { get; set; }
public string smtpid { get; set; }
public string #event { get; set; }
public string category[] { get; set; }
}
You'd decorate the category property as seen here:
[JsonConverter(typeof(SendGridCategoryConverter))]
public string category { get; set; }
public class SendGridCategoryConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true; // add your own logic
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// do work here to handle returning the array regardless of the number of objects in
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// Left as an exercise to the reader :)
throw new NotImplementedException();
}
}
You don't need any custom converters, in this case I am usually creating a very simple JsonConstructor
public partial class Item
{
// ... all class properties
[JsonConstructor]
public Item(JToken category)
{
if (category.GetType().Name == "JArray")
Category = category.ToObject<List<string>>();
else
Category = new List<string> { category.ToString() };
}
public Item() { }
}
after this you can deserialize your json using common code
List<Item> items = JsonConvert.DeserializeObject<List<Item>>(json);
I found another solution that can handle the category as string or array by using object. This way I don´t need to mess up with the json serializer.
Please give it a look if you have the time and tell me what you think. https://github.com/MarcelloCarreira/sendgrid-csharp-eventwebhook
It´s based on the solution at https://sendgrid.com/blog/tracking-email-using-azure-sendgrid-event-webhook-part-1/ but I also added date conversion from timestamp, upgraded the variables to reflect current SendGrid model (and made categories work).
I also created a handler with basic auth as option. See the ashx files and the examples.
Thank you!
I'm trying to fix my SendGridPlus library to deal with SendGrid events, but I'm having some trouble with the inconsistent treatment of categories in the API.
In the following example payload taken from the SendGrid API reference, you'll notice that the category property for each item can either be a single string or an array of strings.
[
{
"email": "john.doe#sendgrid.com",
"timestamp": 1337966815,
"category": [
"newuser",
"transactional"
],
"event": "open"
},
{
"email": "jane.doe#sendgrid.com",
"timestamp": 1337966815,
"category": "olduser",
"event": "open"
}
]
It seems my options to make JSON.NET like this are fixing the string before it comes in, or configuring JSON.NET to accept the incorrect data. I'd rather not do any string parsing if I can get away with it.
Is there any other way I can handle this using Json.Net?
The best way to handle this situation is to use a custom JsonConverter.
Before we get to the converter, we'll need to define a class to deserialize the data into. For the Categories property that can vary between a single item and an array, define it as a List<string> and mark it with a [JsonConverter] attribute so that JSON.Net will know to use the custom converter for that property. I would also recommend using [JsonProperty] attributes so that the member properties can be given meaningful names independent of what is defined in the JSON.
class Item
{
[JsonProperty("email")]
public string Email { get; set; }
[JsonProperty("timestamp")]
public int Timestamp { get; set; }
[JsonProperty("event")]
public string Event { get; set; }
[JsonProperty("category")]
[JsonConverter(typeof(SingleOrArrayConverter<string>))]
public List<string> Categories { get; set; }
}
Here is how I would implement the converter. Notice I've made the converter generic so that it can be used with strings or other types of objects as needed.
class SingleOrArrayConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(List<T>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<T>>();
}
return new List<T> { token.ToObject<T>() };
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Here is an short program demonstrating the converter in action with your sample data:
class Program
{
static void Main(string[] args)
{
string json = #"
[
{
""email"": ""john.doe#sendgrid.com"",
""timestamp"": 1337966815,
""category"": [
""newuser"",
""transactional""
],
""event"": ""open""
},
{
""email"": ""jane.doe#sendgrid.com"",
""timestamp"": 1337966815,
""category"": ""olduser"",
""event"": ""open""
}
]";
List<Item> list = JsonConvert.DeserializeObject<List<Item>>(json);
foreach (Item obj in list)
{
Console.WriteLine("email: " + obj.Email);
Console.WriteLine("timestamp: " + obj.Timestamp);
Console.WriteLine("event: " + obj.Event);
Console.WriteLine("categories: " + string.Join(", ", obj.Categories));
Console.WriteLine();
}
}
}
And finally, here is the output of the above:
email: john.doe#sendgrid.com
timestamp: 1337966815
event: open
categories: newuser, transactional
email: jane.doe#sendgrid.com
timestamp: 1337966815
event: open
categories: olduser
Fiddle: https://dotnetfiddle.net/lERrmu
EDIT
If you need to go the other way, i.e. serialize, while keeping the same format, you can implement the WriteJson() method of the converter as shown below. (Be sure to remove the CanWrite override or change it to return true, or else WriteJson() will never be called.)
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
List<T> list = (List<T>)value;
if (list.Count == 1)
{
value = list[0];
}
serializer.Serialize(writer, value);
}
Fiddle: https://dotnetfiddle.net/XG3eRy
I was working on this for ages, and thanks to Brian for his answer.
All I am adding is the vb.net answer!:
Public Class SingleValueArrayConverter(Of T)
sometimes-array-and-sometimes-object
Inherits JsonConverter
Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
Throw New NotImplementedException()
End Sub
Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim retVal As Object = New [Object]()
If reader.TokenType = JsonToken.StartObject Then
Dim instance As T = DirectCast(serializer.Deserialize(reader, GetType(T)), T)
retVal = New List(Of T)() From { _
instance _
}
ElseIf reader.TokenType = JsonToken.StartArray Then
retVal = serializer.Deserialize(reader, objectType)
End If
Return retVal
End Function
Public Overrides Function CanConvert(objectType As Type) As Boolean
Return False
End Function
End Class
then in your class:
<JsonProperty(PropertyName:="JsonName)> _
<JsonConverter(GetType(SingleValueArrayConverter(Of YourObject)))> _
Public Property YourLocalName As List(Of YourObject)
Hope this saves you some time
As a minor variation to the great answer by Brian Rogers, here are two tweaked versions of SingleOrArrayConverter<T>.
Firstly, here is a version that works for all List<T> for every type T that is not itself a collection:
public class SingleOrArrayListConverter : JsonConverter
{
// Adapted from this answer https://stackoverflow.com/a/18997172
// to https://stackoverflow.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n
// by Brian Rogers https://stackoverflow.com/users/10263/brian-rogers
readonly bool canWrite;
readonly IContractResolver resolver;
public SingleOrArrayListConverter() : this(false) { }
public SingleOrArrayListConverter(bool canWrite) : this(canWrite, null) { }
public SingleOrArrayListConverter(bool canWrite, IContractResolver resolver)
{
this.canWrite = canWrite;
// Use the global default resolver if none is passed in.
this.resolver = resolver ?? new JsonSerializer().ContractResolver;
}
static bool CanConvert(Type objectType, IContractResolver resolver)
{
Type itemType;
JsonArrayContract contract;
return CanConvert(objectType, resolver, out itemType, out contract);
}
static bool CanConvert(Type objectType, IContractResolver resolver, out Type itemType, out JsonArrayContract contract)
{
if ((itemType = objectType.GetListItemType()) == null)
{
itemType = null;
contract = null;
return false;
}
// Ensure that [JsonObject] is not applied to the type.
if ((contract = resolver.ResolveContract(objectType) as JsonArrayContract) == null)
return false;
var itemContract = resolver.ResolveContract(itemType);
// Not implemented for jagged arrays.
if (itemContract is JsonArrayContract)
return false;
return true;
}
public override bool CanConvert(Type objectType) { return CanConvert(objectType, resolver); }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
Type itemType;
JsonArrayContract contract;
if (!CanConvert(objectType, serializer.ContractResolver, out itemType, out contract))
throw new JsonSerializationException(string.Format("Invalid type for {0}: {1}", GetType(), objectType));
if (reader.MoveToContent().TokenType == JsonToken.Null)
return null;
var list = (IList)(existingValue ?? contract.DefaultCreator());
if (reader.TokenType == JsonToken.StartArray)
serializer.Populate(reader, list);
else
// Here we take advantage of the fact that List<T> implements IList to avoid having to use reflection to call the generic Add<T> method.
list.Add(serializer.Deserialize(reader, itemType));
return list;
}
public override bool CanWrite { get { return canWrite; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var list = value as ICollection;
if (list == null)
throw new JsonSerializationException(string.Format("Invalid type for {0}: {1}", GetType(), value.GetType()));
// Here we take advantage of the fact that List<T> implements IList to avoid having to use reflection to call the generic Count method.
if (list.Count == 1)
{
foreach (var item in list)
{
serializer.Serialize(writer, item);
break;
}
}
else
{
writer.WriteStartArray();
foreach (var item in list)
serializer.Serialize(writer, item);
writer.WriteEndArray();
}
}
}
public static partial class JsonExtensions
{
public static JsonReader MoveToContent(this JsonReader reader)
{
while ((reader.TokenType == JsonToken.Comment || reader.TokenType == JsonToken.None) && reader.Read())
;
return reader;
}
internal static Type GetListItemType(this Type type)
{
// Quick reject for performance
if (type.IsPrimitive || type.IsArray || type == typeof(string))
return null;
while (type != null)
{
if (type.IsGenericType)
{
var genType = type.GetGenericTypeDefinition();
if (genType == typeof(List<>))
return type.GetGenericArguments()[0];
}
type = type.BaseType;
}
return null;
}
}
It can be used as follows:
var settings = new JsonSerializerSettings
{
// Pass true if you want single-item lists to be reserialized as single items
Converters = { new SingleOrArrayListConverter(true) },
};
var list = JsonConvert.DeserializeObject<List<Item>>(json, settings);
Notes:
The converter avoids the need to pre-load the entire JSON value into memory as a JToken hierarchy.
The converter does not apply to lists whose items are also serialized as collections, e.g. List<string []>
The Boolean canWrite argument passed to the constructor controls whether to re-serialize single-element lists as JSON values or as JSON arrays.
The converter's ReadJson() uses the existingValue if pre-allocated so as to support populating of get-only list members.
Secondly, here is a version that works with other generic collections such as ObservableCollection<T>:
public class SingleOrArrayCollectionConverter<TCollection, TItem> : JsonConverter
where TCollection : ICollection<TItem>
{
// Adapted from this answer https://stackoverflow.com/a/18997172
// to https://stackoverflow.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n
// by Brian Rogers https://stackoverflow.com/users/10263/brian-rogers
readonly bool canWrite;
public SingleOrArrayCollectionConverter() : this(false) { }
public SingleOrArrayCollectionConverter(bool canWrite) { this.canWrite = canWrite; }
public override bool CanConvert(Type objectType)
{
return typeof(TCollection).IsAssignableFrom(objectType);
}
static void ValidateItemContract(IContractResolver resolver)
{
var itemContract = resolver.ResolveContract(typeof(TItem));
if (itemContract is JsonArrayContract)
throw new JsonSerializationException(string.Format("Item contract type {0} not supported.", itemContract));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
ValidateItemContract(serializer.ContractResolver);
if (reader.MoveToContent().TokenType == JsonToken.Null)
return null;
var list = (ICollection<TItem>)(existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator());
if (reader.TokenType == JsonToken.StartArray)
serializer.Populate(reader, list);
else
list.Add(serializer.Deserialize<TItem>(reader));
return list;
}
public override bool CanWrite { get { return canWrite; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
ValidateItemContract(serializer.ContractResolver);
var list = value as ICollection<TItem>;
if (list == null)
throw new JsonSerializationException(string.Format("Invalid type for {0}: {1}", GetType(), value.GetType()));
if (list.Count == 1)
{
foreach (var item in list)
{
serializer.Serialize(writer, item);
break;
}
}
else
{
writer.WriteStartArray();
foreach (var item in list)
serializer.Serialize(writer, item);
writer.WriteEndArray();
}
}
}
Then, if your model is using, say, an ObservableCollection<T> for some T, you could apply it as follows:
class Item
{
public string Email { get; set; }
public int Timestamp { get; set; }
public string Event { get; set; }
[JsonConverter(typeof(SingleOrArrayCollectionConverter<ObservableCollection<string>, string>))]
public ObservableCollection<string> Category { get; set; }
}
Notes:
In addition to the notes and restrictions for SingleOrArrayListConverter, the TCollection type must be read/write and have a parameterless constructor.
Demo fiddle with basic unit tests here.
To handle this you have to use a custom JsonConverter. But you probably already had that in mind.
You are just looking for a converter that you can use immediately. And this offers more than just a solution for the situation described.
I give an example with the question asked.
How to use my converter:
Place a JsonConverter Attribute above the property. JsonConverter(typeof(SafeCollectionConverter))
public class SendGridEvent
{
[JsonProperty("email")]
public string Email { get; set; }
[JsonProperty("timestamp")]
public long Timestamp { get; set; }
[JsonProperty("category"), JsonConverter(typeof(SafeCollectionConverter))]
public string[] Category { get; set; }
[JsonProperty("event")]
public string Event { get; set; }
}
And this is my converter:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
namespace stackoverflow.question18994685
{
public class SafeCollectionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
//This not works for Populate (on existingValue)
return serializer.Deserialize<JToken>(reader).ToObjectCollectionSafe(objectType, serializer);
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
And this converter uses the following class:
using System;
namespace Newtonsoft.Json.Linq
{
public static class SafeJsonConvertExtensions
{
public static object ToObjectCollectionSafe(this JToken jToken, Type objectType)
{
return ToObjectCollectionSafe(jToken, objectType, JsonSerializer.CreateDefault());
}
public static object ToObjectCollectionSafe(this JToken jToken, Type objectType, JsonSerializer jsonSerializer)
{
var expectArray = typeof(System.Collections.IEnumerable).IsAssignableFrom(objectType);
if (jToken is JArray jArray)
{
if (!expectArray)
{
//to object via singel
if (jArray.Count == 0)
return JValue.CreateNull().ToObject(objectType, jsonSerializer);
if (jArray.Count == 1)
return jArray.First.ToObject(objectType, jsonSerializer);
}
}
else if (expectArray)
{
//to object via JArray
return new JArray(jToken).ToObject(objectType, jsonSerializer);
}
return jToken.ToObject(objectType, jsonSerializer);
}
public static T ToObjectCollectionSafe<T>(this JToken jToken)
{
return (T)ToObjectCollectionSafe(jToken, typeof(T));
}
public static T ToObjectCollectionSafe<T>(this JToken jToken, JsonSerializer jsonSerializer)
{
return (T)ToObjectCollectionSafe(jToken, typeof(T), jsonSerializer);
}
}
}
What does it do exactly?
If you place the converter attribute the converter will be used for this property. You can use it on a normal object if you expect a json array with 1 or no result. Or you use it on an IEnumerable where you expect a json object or json array. (Know that an array -object[]- is an IEnumerable)
A disadvantage is that this converter can only be placed above a property because he thinks he can convert everything. And be warned. A string is also an IEnumerable.
And it offers more than an answer to the question:
If you search for something by id you know that you will get an array back with one or no result.
The ToObjectCollectionSafe<TResult>() method can handle that for you.
This is usable for Single Result vs Array using JSON.net
and handle both a single item and an array for the same property
and can convert an array to a single object.
I made this for REST requests on a server with a filter that returned one result in an array but wanted to get the result back as a single object in my code. And also for a OData result response with expanded result with one item in an array.
Have fun with it.
Just wanted to add to #dbc excellent response above on the SingleOrArrayCollectionConverter. I was able to modify it to use with a stream from an HTTP client. Here is a snippet (you will have to set up the requestUrl (string) and the httpClient (using System.Net.Http;).
public async Task<IList<T>> HttpRequest<T>(HttpClient httpClient, string requestedUrl, CancellationToken cancellationToken)
{
using (var request = new HttpRequestMessage(HttpMethod.Get, requestedUrl))
using (var httpResponseMessage = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
{
if (httpResponseMessage.IsSuccessStatusCode)
{
using var stream = await httpResponseMessage.Content.ReadAsStreamAsync();
using var streamReader = new StreamReader(stream);
using var jsonTextReader = new JsonTextReader(streamReader );
var settings = new JsonSerializerSettings
{
// Pass true if you want single-item lists to be reserialized as single items
Converters = { new SingleOrArrayCollectionConverter(true) },
};
var jsonSerializer = JsonSerializer.Create(settings);
return jsonSerializer.Deserialize<List<T>>(jsonTextReader);
}
I apologize if there are missing brackets or misspellings, it was not easy to paste code in here.
I had a very similar Problem.
My Json Request was completly unknown for me.
I only knew.
There will be an objectId in it and some anonym key value pairs AND arrays.
I used it for an EAV Model i did:
My JSON Request:
{objectId": 2,
"firstName": "Hans",
"email" :[ "a#b.de","a#c.de"],
"name": "Andre",
"something" :["232","123"]
}
My Class i defined:
[JsonConverter(typeof(AnonyObjectConverter))]
public class AnonymObject
{
public AnonymObject()
{
fields = new Dictionary<string, string>();
list = new List<string>();
}
public string objectid { get; set; }
public Dictionary<string, string> fields { get; set; }
public List<string> list { get; set; }
}
and now that i want to deserialize unknown attributes with its value and arrays in it my Converter looks like that:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
AnonymObject anonym = existingValue as AnonymObject ?? new AnonymObject();
bool isList = false;
StringBuilder listValues = new StringBuilder();
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndObject) continue;
if (isList)
{
while (reader.TokenType != JsonToken.EndArray)
{
listValues.Append(reader.Value.ToString() + ", ");
reader.Read();
}
anonym.list.Add(listValues.ToString());
isList = false;
continue;
}
var value = reader.Value.ToString();
switch (value.ToLower())
{
case "objectid":
anonym.objectid = reader.ReadAsString();
break;
default:
string val;
reader.Read();
if(reader.TokenType == JsonToken.StartArray)
{
isList = true;
val = "ValueDummyForEAV";
}
else
{
val = reader.Value.ToString();
}
try
{
anonym.fields.Add(value, val);
}
catch(ArgumentException e)
{
throw new ArgumentException("Multiple Attribute found");
}
break;
}
}
return anonym;
}
So now everytime i get an AnonymObject i can iterate through the Dictionary and everytime there is my Flag "ValueDummyForEAV" i switch to the list, read the first line and split the values. After that i delete the first entry from the list and go on with iteration from the Dictionary.
Maybe someone has the same problem and can use this :)
Regards
Andre
You can use a JSONConverterAttribute as found here: http://james.newtonking.com/projects/json/help/
Presuming you have a class that looks like
public class RootObject
{
public string email { get; set; }
public int timestamp { get; set; }
public string smtpid { get; set; }
public string #event { get; set; }
public string category[] { get; set; }
}
You'd decorate the category property as seen here:
[JsonConverter(typeof(SendGridCategoryConverter))]
public string category { get; set; }
public class SendGridCategoryConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true; // add your own logic
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// do work here to handle returning the array regardless of the number of objects in
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// Left as an exercise to the reader :)
throw new NotImplementedException();
}
}
You don't need any custom converters, in this case I am usually creating a very simple JsonConstructor
public partial class Item
{
// ... all class properties
[JsonConstructor]
public Item(JToken category)
{
if (category.GetType().Name == "JArray")
Category = category.ToObject<List<string>>();
else
Category = new List<string> { category.ToString() };
}
public Item() { }
}
after this you can deserialize your json using common code
List<Item> items = JsonConvert.DeserializeObject<List<Item>>(json);
I found another solution that can handle the category as string or array by using object. This way I don´t need to mess up with the json serializer.
Please give it a look if you have the time and tell me what you think. https://github.com/MarcelloCarreira/sendgrid-csharp-eventwebhook
It´s based on the solution at https://sendgrid.com/blog/tracking-email-using-azure-sendgrid-event-webhook-part-1/ but I also added date conversion from timestamp, upgraded the variables to reflect current SendGrid model (and made categories work).
I also created a handler with basic auth as option. See the ashx files and the examples.
Thank you!
Suppose I have a class like this:
public class Example {
public int TypedProperty { get; set; }
public object UntypedProperty { get; set; }
}
And suppose someone comes along and writes:
var example = new Example
{
TypedProperty = 5,
UntypedProperty = Guid.NewGuid()
}
If I serialize this with JsonConvert.SerializeObject(example), I get
{
"TypedProperty": 5,
"UntypedProperty": "24bd733f-2ade-4374-9db6-3c9f3d97b12c"
}
Ideally, I'd like to get something like this:
{
"TypedProperty": 5,
"UntypedProperty":
{
"$type": "System.Guid,mscorlib",
"$value": "24bd733f-2ade-4374-9db6-3c9f3d97b12c"
}
}
But TypeNameHandling doesn't work in this scenario. How can I (de)serialize an untyped property?
If you serialize your class with TypeNameHandling.All or TypeNameHandling.Auto,
then when the UntypedProperty property would be serialized as a JSON container (either an object or array) Json.NET should correctly serialize and deserialize it by storing type information in the JSON file in a "$type" property. However, in cases where UntypedProperty is serialized as a JSON primitive (a string, number, or Boolean) this doesn't work because, as you have noted, a JSON primitive has no opportunity to include a "$type" property.
The solution is, when serializing a type with a property of type object, to serialize wrappers classes for primitive values that can encapsulate the type information, along the lines of this answer. Here is a custom JSON converter that injects such a wrapper:
public class UntypedToTypedValueConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException("This converter should only be applied directly via ItemConverterType, not added to JsonSerializer.Converters");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var value = serializer.Deserialize(reader, objectType);
if (value is TypeWrapper)
{
return ((TypeWrapper)value).ObjectValue;
}
return value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (serializer.TypeNameHandling == TypeNameHandling.None)
{
Console.WriteLine("ObjectItemConverter used when serializer.TypeNameHandling == TypeNameHandling.None");
serializer.Serialize(writer, value);
}
// Handle a couple of simple primitive cases where a type wrapper is not needed
else if (value is string)
{
writer.WriteValue((string)value);
}
else if (value is bool)
{
writer.WriteValue((bool)value);
}
else
{
var contract = serializer.ContractResolver.ResolveContract(value.GetType());
if (contract is JsonPrimitiveContract)
{
var wrapper = TypeWrapper.CreateWrapper(value);
serializer.Serialize(writer, wrapper, typeof(object));
}
else
{
serializer.Serialize(writer, value);
}
}
}
}
abstract class TypeWrapper
{
protected TypeWrapper() { }
[JsonIgnore]
public abstract object ObjectValue { get; }
public static TypeWrapper CreateWrapper<T>(T value)
{
if (value == null)
return new TypeWrapper<T>();
var type = value.GetType();
if (type == typeof(T))
return new TypeWrapper<T>(value);
// Return actual type of subclass
return (TypeWrapper)Activator.CreateInstance(typeof(TypeWrapper<>).MakeGenericType(type), value);
}
}
sealed class TypeWrapper<T> : TypeWrapper
{
public TypeWrapper() : base() { }
public TypeWrapper(T value)
: base()
{
this.Value = value;
}
public override object ObjectValue { get { return Value; } }
public T Value { get; set; }
}
Then apply it to your type using [JsonConverter(typeof(UntypedToTypedValueConverter))]:
public class Example
{
public int TypedProperty { get; set; }
[JsonConverter(typeof(UntypedToTypedValueConverter))]
public object UntypedProperty { get; set; }
}
If you cannot modify the Example class in any way to add this attribute (your comment The class isn't mine to change suggests as much) you could inject the converter with a custom contract resolver:
public class UntypedToTypedPropertyContractResolver : DefaultContractResolver
{
readonly UntypedToTypedValueConverter converter = new UntypedToTypedValueConverter();
// As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
// http://www.newtonsoft.com/json/help/html/ContractResolver.htm
// http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
// "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
// See also https://stackoverflow.com/questions/33557737/does-json-net-cache-types-serialization-information
static UntypedToTypedPropertyContractResolver instance;
// Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
static UntypedToTypedPropertyContractResolver() { instance = new UntypedToTypedPropertyContractResolver(); }
public static UntypedToTypedPropertyContractResolver Instance { get { return instance; } }
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
foreach (var property in contract.Properties.Concat(contract.CreatorParameters))
{
if (property.PropertyType == typeof(object)
&& property.Converter == null)
{
property.Converter = property.MemberConverter = converter;
}
}
return contract;
}
}
And use it as follows:
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
ContractResolver = UntypedToTypedPropertyContractResolver.Instance,
};
var json = JsonConvert.SerializeObject(example, Formatting.Indented, settings);
var example2 = JsonConvert.DeserializeObject<Example>(json, settings);
In both cases the JSON created looks like:
{
"TypedProperty": 5,
"UntypedProperty": {
"$type": "Question38777588.TypeWrapper`1[[System.Guid, mscorlib]], Tile",
"Value": "e2983c59-5ec4-41cc-b3fe-34d9d0a97f22"
}
}
Lookup SerializeWithJsonConverters.htm and ReadingWritingJSON.
Call: JsonConvert.SerializeObject(example, new ObjectConverter());
class ObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Example);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Example e = (Example)value;
writer.WriteStartObject();
writer.WritePropertyName("TypedProperty");
writer.WriteValue(e.TypedProperty);
writer.WritePropertyName("UntypedProperty");
writer.WriteStartObject();
writer.WritePropertyName("$type");
writer.WriteValue(e.UntypedProperty.GetType().FullName);
writer.WritePropertyName("$value");
writer.WriteValue(e.UntypedProperty.ToString());
writer.WriteEndObject();
writer.WriteEndObject();
}
}
I've pored through the docs, StackOverflow, etc., can't seem to find this...
What I want to do is serialize/deserialize a simple value-type of object as a value, not an object, as so:
public class IPAddress
{
byte[] bytes;
public override string ToString() {... etc.
}
public class SomeOuterObject
{
string stringValue;
IPAddress ipValue;
}
IPAddress ip = new IPAddress("192.168.1.2");
var obj = new SomeOuterObject() {stringValue = "Some String", ipValue = ip};
string json = JsonConverter.SerializeObject(obj);
What I want is for the json to serialize like this:
// json = {"someString":"Some String","ipValue":"192.168.1.2"} <- value serialized as value, not subobject
Not where the ip becomes a nested object, ex:
// json = {"someString":"Some String","ipValue":{"value":"192.168.1.2"}}
Does anyone know how to do this? Thanks! (P.S. I am bolting Json serialization on a large hairy legacy .NET codebase, so I can't really change any existing types, but I can augment/factor/decorate them to facilitate Json serialization.)
You can handle this using a custom JsonConverter for the IPAddress class. Here is the code you would need:
public class IPAddressConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(IPAddress));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return new IPAddress(JToken.Load(reader).ToString());
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken.FromObject(value.ToString()).WriteTo(writer);
}
}
Then, add a [JsonConverter] attribute to your IPAddress class and you're ready to go:
[JsonConverter(typeof(IPAddressConverter))]
public class IPAddress
{
byte[] bytes;
public IPAddress(string address)
{
bytes = address.Split('.').Select(s => byte.Parse(s)).ToArray();
}
public override string ToString()
{
return string.Join(".", bytes.Select(b => b.ToString()).ToArray());
}
}
Here is a working demo:
class Program
{
static void Main(string[] args)
{
IPAddress ip = new IPAddress("192.168.1.2");
var obj = new SomeOuterObject() { stringValue = "Some String", ipValue = ip };
string json = JsonConvert.SerializeObject(obj);
Console.WriteLine(json);
}
}
public class SomeOuterObject
{
public string stringValue { get; set; }
public IPAddress ipValue { get; set; }
}
Output:
{"stringValue":"Some String","ipValue":"192.168.1.2"}
This is a answer to Customise NewtonSoft.Json for Value Object serialisation, in regards to value objects in DDD. But that question is marked as duplicate to this one, which i don't think is completely true.
I borrowed the code for the ValueObjectConverter from
https://github.com/eventflow/EventFlow, I have only done minor changes.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using FluentAssertions;
using Newtonsoft.Json;
using Xunit;
namespace Serialization
{
public class ValueObjectSerializationTests
{
class SomeClass
{
public IPAddress IPAddress { get; set; }
}
[Fact]
public void FactMethodName()
{
var given = new SomeClass
{
IPAddress = new IPAddress("192.168.1.2")
};
var jsonSerializerSettings = new JsonSerializerSettings()
{
Converters = new List<JsonConverter>
{
new ValueObjectConverter()
}
};
var json = JsonConvert.SerializeObject(given, jsonSerializerSettings);
var result = JsonConvert.DeserializeObject<SomeClass>(json, jsonSerializerSettings);
var expected = new SomeClass
{
IPAddress = new IPAddress("192.168.1.2")
};
json.Should().Be("{\"IPAddress\":\"192.168.1.2\"}");
expected.ShouldBeEquivalentTo(result);
}
}
public class IPAddress:IValueObject
{
public IPAddress(string value)
{
Value = value;
}
public object GetValue()
{
return Value;
}
public string Value { get; private set; }
}
public interface IValueObject
{
object GetValue();
}
public class ValueObjectConverter : JsonConverter
{
private static readonly ConcurrentDictionary<Type, Type> ConstructorArgumenTypes = new ConcurrentDictionary<Type, Type>();
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (!(value is IValueObject valueObject))
{
return;
}
serializer.Serialize(writer, valueObject.GetValue());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var parameterType = ConstructorArgumenTypes.GetOrAdd(
objectType,
t =>
{
var constructorInfo = objectType.GetConstructors(BindingFlags.Public | BindingFlags.Instance).First();
var parameterInfo = constructorInfo.GetParameters().Single();
return parameterInfo.ParameterType;
});
var value = serializer.Deserialize(reader, parameterType);
return Activator.CreateInstance(objectType, new[] { value });
}
public override bool CanConvert(Type objectType)
{
return typeof(IValueObject).IsAssignableFrom(objectType);
}
}
}
public class IPAddress
{
byte[] bytes;
public override string ToString() {... etc.
}
IPAddress ip = new IPAddress("192.168.1.2");
var obj = new () {ipValue = ip.ToString()};
string json = JsonConverter.SerializeObject(obj);
You are serializing the whole IP address instance. Maybe just try to serialize the address as a string. (This presumes that you have implemented the ToString-method.)
There are a couple of different ways to approach this depending on the level of effort you are able to expend and the tolerance for changes to existing classes.
One approach is to define your classes as DataContract and explicitly identify the elements within the class as DataMembers. Netwonsoft recognizes and uses these attributes in its serialization. The upside to this approach is that the classes will now be serializable using other approaches that use datacontract serialization.
[DataContract]
public class IPAddress
{
private byte[] bytes;
// Added this readonly property to allow serialization
[DataMember(Name = "ipValue")]
public string Value
{
get
{
return this.ToString();
}
}
public override string ToString()
{
return "192.168.1.2";
}
}
Here is the code that I used to serialize (I may be using an older version since I didn't see the SerializeObject method):
IPAddress ip = new IPAddress();
using (StringWriter oStringWriter = new StringWriter())
{
using (JsonTextWriter oJsonWriter = new JsonTextWriter(oStringWriter))
{
JsonSerializer oSerializer = null;
JsonSerializerSettings oOptions = new JsonSerializerSettings();
// Generate the json without quotes around the name objects
oJsonWriter.QuoteName = false;
// This can be used in order to view the rendered properties "nicely"
oJsonWriter.Formatting = Formatting.Indented;
oOptions.NullValueHandling = NullValueHandling.Ignore;
oSerializer = JsonSerializer.Create(oOptions);
oSerializer.Serialize(oJsonWriter, ip);
Console.WriteLine(oStringWriter.ToString());
}
}
Here is the output:
{
ipValue: "192.168.1.2"
}
Another approach is to create your own JsonConverter inheritor that can serialize exactly what you need without modifications to the internals of the class:
public class JsonToStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartObject();
writer.WritePropertyName(value.GetType().Name);
writer.WriteValue(Convert.ToString(value));
writer.WriteEndObject();
}
}
This class just writes the tostring value of the class along with the class name. Changing the name can be accomplished through additional attributes on the class, which I have not shown.
The class would then look like:
[JsonConverter(typeof(JsonToStringConverter))]
public class IPAddress
{
private byte[] bytes;
public override string ToString()
{
return "192.168.1.2";
}
}
And the output is:
{
IPAddress: "192.168.1.2"
}
With the Cinchoo ETL - an open source library to parsing / writing JSON files, you can control the serialization of each object member via ValueConverter or with callback mechanism.
Method 1:
The sample below shows how to serialize 'SomeOuterObject' using member level ValueConverters
public class SomeOuterObject
{
public string stringValue { get; set; }
[ChoTypeConverter(typeof(ToTextConverter))]
public IPAddress ipValue { get; set; }
}
And the value converter is
public class ToTextConverter : IChoValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value.ToString();
}
}
Finally to serialize the object to file
using (var jr = new ChoJSONWriter<SomeOuterObject>("ipaddr.json")
)
{
var x1 = new SomeOuterObject { stringValue = "X1", ipValue = IPAddress.Parse("12.23.21.23") };
jr.Write(x1);
}
And the output is
[
{
"stringValue": "X1",
"ipValue": "12.23.21.23"
}
]
Method 2:
This the alternative method to hook up value converter callback to 'ipValue' property. This approach is lean and no need to create value converter for just this operation.
using (var jr = new ChoJSONWriter<SomeOuterObject>("ipaddr.json")
.WithField("stringValue")
.WithField("ipValue", valueConverter: (o) => o.ToString())
)
{
var x1 = new SomeOuterObject { stringValue = "X1", ipValue = IPAddress.Parse("12.23.21.23") };
jr.Write(x1);
}
Hope this helps.
Disclaimer: I'm the author of the library.
Here is a class for generic conversion of simple value objects that I plan to include in the next update of Activout.RestClient. A "simple value object" as an object that has:
No default constructor
A public property named Value
A constructor taking the same type as the Value property
var settings = new JsonSerializerSettings
{
Converters = new List<JsonConverter> {new SimpleValueObjectConverter()}
};
public class SimpleValueObjectConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var valueProperty = GetValueProperty(value.GetType());
serializer.Serialize(writer, valueProperty.GetValue(value));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
var valueProperty = GetValueProperty(objectType);
var value = serializer.Deserialize(reader, valueProperty.PropertyType);
return Activator.CreateInstance(objectType, value);
}
public override bool CanConvert(Type objectType)
{
if (GetDefaultConstructor(objectType) != null) return false;
var valueProperty = GetValueProperty(objectType);
if (valueProperty == null) return false;
var constructor = GetValueConstructor(objectType, valueProperty);
return constructor != null;
}
private static ConstructorInfo GetValueConstructor(Type objectType, PropertyInfo valueProperty)
{
return objectType.GetConstructor(new[] {valueProperty.PropertyType});
}
private static PropertyInfo GetValueProperty(Type objectType)
{
return objectType.GetProperty("Value");
}
private static ConstructorInfo GetDefaultConstructor(Type objectType)
{
return objectType.GetConstructor(new Type[0]);
}
}