Json.net deserilize all empty strings as null - c#

I have an endpoint that returns all null values as empty strings, even when the type is completely different. For example, this data
[{
"field1": 3,
"field2": "bob",
"field3": ["alpha", "beta", "gamma"],
"field4": { "some": "data" }
},
{
"field1": "", // needs to be deserialized as null
"field2": "", // same
"field3": "", // ..
"field4": "" // ..
}]
would need to be serialized to (an array of) a model like:
public class Root
{
public int? Field1 { get; set; }
public string Field2 { get; set; }
public string[] Field3 { get; set; }
public JObject Field4 { get; set; }
}
But Json.Net throws an exception:
Unhandled Exception: Newtonsoft.Json.JsonSerializationException: Error setting value to 'Field4' on 'Root'. ---> System.InvalidCastException: Unable to cast object of type 'Newtonsoft.Json.Linq.JValue' to type 'Newtonsoft.Json.Linq.JObject'.
I have tried using Contracts, ValueProviders, and Converters with no luck. How could I go about doing this?
None of these links has helped me:
Customize Json.NET serialization to consider empty strings as null
Convert empty strings to null with Json.Net
Json Convert empty string instead of null
EDIT1: Fixed typo.
EDIT2: This is the code for the converter I tried using:
public class VdfNullConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// ...
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String && (reader.Value as string == ""))
return null;
// I don't know what to do here
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
My issue is that I don't know how to handle the case where the data is in fact not an empty string. In that case, I need the other converters to be called, but I have no way to "cancel" halfway through ReadJson.

I finally found a simple, but hacky solution. I used a regular JsonConverter and backtracked using code like this:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String && (reader.Value as string == ""))
return null;
skip = true;
return serializer.Deserialize(reader, objectType);
}
private bool skip = false;
public override bool CanConvert(Type objectType) // If this is ever cached, this hack won't work.
{
if (skip)
{
skip = false;
return false;
}
return true;
}

If your model really consists of just 4 fields you can consider using serialization properties
public class Root{
[JsonIgnore]
public int? Field1 {get;set;}
[JsonProperty("field1")]
protected string Field1Data{
{
get { return Field1?.ToString(); }
set {
if (string.IsNullOrEmpty(value))
Field1 = null;
else {
int parsed;
if (Int32.TryParse(value, out parsed)){
Field1 = parsed;
} else {
throw new ArgumentException($"{value} could not be parsed", nameof(Field1));
}
}
}
}
}
}
If your model has a larger variety of types and fields then you'll have to use a custom json converter.
Either way, the JObject in your sample looks suspicious. It will likely need to be of some other type, such as Object.
EDIT:
Since you have a very large variety of types you may want to consider pre-processing your json before deserializing it, since using converters would require more specificity.
You could parse your input as a JObject, select all the empty string values and delete the properties:
var jobj = JObject.Parse(File.ReadAllText("sample.json"));
var tokens = jobj.SelectTokens("$..[?(#=~/^$/)]").ToList();
tokens.ForEach(t=>t.Parent.Remove()); // you could filter some of the removals if you need to
string nowUseThisAsYourImpup = jobj.ToString();

Related

Newtonsoft Custom Json Converter String Array to Comma Separated string WebApi 2

I thought this would pretty easily to convert from JSON array to a comma-separated string and back using Newtonsoft, but I am having issues getting the ReadJson to work. I thought I would just deserialize from the reader to a string array and then call Join, but I keep getting errors: Unexpected token while deserializing object: PropertyName. Path '[0]..
Here is the code I am using:
public class myModel
{
[JsonConverter(typeof(CommaSeperatedStringJsonConverter))]
public string myString { get; set; }
public int myNumber { get; set; }
}
public class CommaSeperatedStringJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var stringArray = serializer.Deserialize(reader, typeof(string[]));
return string.Join(",", stringArray);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is null)
{
writer.WriteNull();
}
else
{
serializer.Serialize(writer, value.ToString().Split(','), typeof(string[]));
}
}
}
Try using the generic version of Deserialize instead. In other words, change this line:
var stringArray = serializer.Deserialize(reader, typeof(string[]));
To this:
var stringArray = serializer.Deserialize<string[]>(reader);
Fiddle: https://dotnetfiddle.net/KpQSiG
It shouldn't matter, but for some reason it does in this situation. If I have more time I'll try to dig into the source code to see what is happening.

Deserialise Json when an array can contain either string or complex object?

I'm trying to read a json fragment in a C# application.
In the fragment, there's an array that can contains either a simple string or a complex object. This is because the contained object has only one mandatory string, other fields are optional. Thus, to simplify the json, the array can contain a string (= default parameters) or the complex object.
Here is a sample json :
{
"Config1": [
"Simple string 1",
"Simple string 2",
{
"Data": "Complex object",
"OptionalField": "some option",
"AnotherOption": 42
}
],
"Config3": [
"Simple string 3",
"Simple string 4",
{
"Data": "Complex object 2",
"OptionalField": "some option",
"AnotherOption": 12
}
]
}
The corresponding C# model :
public class Config : Dictionary<string, ConfigItem[]>
{
}
public class ConfigItem
{
public ConfigItem()
{
}
public ConfigItem(string data)
{
this.Data = data;
}
public string Data { get; set; }
public string OptionalField { get; set; }
public int AnotherOption { get; set; }
}
In my sample, only the Data field is mandatory. When a string is supplied, the constructor with a single string parameter must be called. When a complex json object is provided, the standard deserialization must run.
For example, these two json fragments are equivalent (within the array):
"Config4": [
"This is a string",
{
"Data": "This is a string",
"OptionalField": null,
"AnotherOption": 0
}
]
How to reach my goal ?
Right now, I tried to implement a custom converter :
[JsonConverter(typeof(StringToComplexConverter))]
public class ConfigItem
{
// Properties omiited
}
public class StringToComplexConverter : JsonConverter<ConfigItem>
{
public override bool CanWrite => false;
public override ConfigItem ReadJson(JsonReader reader, Type objectType, ConfigItem existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if(reader.TokenType == JsonToken.String)
{
var ctor = objectType.GetConstructor(new[] { typeof(string) });
return (ConfigItem)ctor.Invoke(new[] { (string)reader.Value });
}
else
{
// What to put in the else ?
}
}
public override void WriteJson(JsonWriter writer, ConfigItem value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
This is actually working for strings. However, I didn't find what to put in the else statement. Is there a way to forward to standard deserialization from the ReadJson method ?
If I put return serializer.Deserialize<ConfigItem>(reader); in my else statement, it ends in a infinite loop.
Also tried return serializer.Deserialize<JObject>(reader).ToObject<ConfigItem>();, with no success.
Finally found a way. Here's my else statement:
else
{
var result = new ConfigItem();
serializer.Populate(reader, result);
return result;
}
Bonus: here's a generic variant of the converter that can be applied to any class that have a constructor with no parameter and another with a string parameter:
public class StringToComplexConverter<TObject> : JsonConverter<TObject>
{
public override bool CanWrite => false;
public override TObject ReadJson(
JsonReader reader,
Type objectType,
TObject existingValue,
bool hasExistingValue,
JsonSerializer serializer
)
{
if (reader.TokenType == JsonToken.String)
{
var ctor = objectType.GetConstructor(new[] { typeof(string) });
return (TObject)ctor.Invoke(new[] { (string)reader.Value });
}
else
{
var result = (TObject)Activator.CreateInstance(objectType);
serializer.Populate(reader, result);
return result;
}
}
public override void WriteJson(JsonWriter writer, TObject value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

Deserialize two values into the same property

I have a client which can call two different versions of a service.
One service only sends a single value:
{
"value" : { ... }
}
The second service always returns multiple values:
{
"values" : [
{ ... },
{ ... }
]
}
Ideally, I'd like to represent this with a single object in my client classes so the user never sees whether it's a single value or multiple values.
public class MyValues
{
public List<Stuff> Values { get; set; }
public Thing Other { get; set; }
}
I think that the only way I'll be able to accomplish this is with a custom JsonConverter class which I apply to MyValues, but I really only want to do something custom when I'm deserializing the property value. I can't seem to figure out if an IContractResolver would be a better way to go (e.g. somehow attach a phantom property to MyValues that deserializes value and puts it into Values.
If I create a custom converter, how to I tell it to deserialize everything else normally (e.g. if Other has an extra properties make sure they are handled appropriately, etc.)
Rather than writing a JsonConverter, you could make a set-only property Value on your MyValues, like so:
public class MyValues
{
[JsonProperty]
Stuff Value
{
set
{
(Values = Values ?? new List<Stuff>(1)).Clear();
Values.Add(value);
}
}
public List<Stuff> Values { get; set; }
public Thing Other { get; set; }
}
It could be public or private if marked with [JsonProperty]. In this case Json.NET will call the Value setter if the singleton "value" property is encountered in the JSON, and call the Values setter if the array "values" property is encountered. Since the property is set-only only the array property will be re-serialized.
To make a custom JsonConverter that has special processing for a few properties of a type but uses default processing for the remainder, you can load the JSON into a JObject, detach and process the custom properties, then populate the remainder from the JObject with JsonSerializer.Populate(), like so:
class MyValuesConverter : CustomPropertyConverterBase<MyValues>
{
protected override void ProcessCustomProperties(JObject obj, MyValues value, JsonSerializer serializer)
{
// Remove the value property for manual deserialization, and deserialize
var jValue = obj.GetValue("value", StringComparison.OrdinalIgnoreCase).RemoveFromLowestPossibleParent();
if (jValue != null)
{
(value.Values = value.Values ?? new List<Stuff>()).Clear();
value.Values.Add(jValue.ToObject<Stuff>(serializer));
}
}
}
public abstract class CustomPropertyConverterBase<T> : JsonConverter where T : class
{
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var jObj = JObject.Load(reader);
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType);
var value = existingValue as T ?? (T)contract.DefaultCreator();
ProcessCustomProperties(jObj, value, serializer);
// Populate the remaining properties.
using (var subReader = jObj.CreateReader())
{
serializer.Populate(subReader, value);
}
return value;
}
protected abstract void ProcessCustomProperties(JObject obj, T value, JsonSerializer serializer);
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static class JsonExtensions
{
public static JToken RemoveFromLowestPossibleParent(this JToken node)
{
if (node == null)
return null;
var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
if (contained != null)
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (node.Parent is JProperty)
((JProperty)node.Parent).Value = null;
return node;
}
}

JSON serialization - set default value for int type

I use the exist API and can't change it.
So I have some variable - CellProviderID.
It looks like it is an int, because when I set the int value the server returns the expected response.
"CellProviderID":5,"CellProviderID2":7,"CellProviderID3":2
The problem appears when I leave this value empty and after serialization I get:
"CellProviderID":0,"CellProviderID2":0,"CellProviderID3":0
because 0 is default value for int type. And the server returns an error:
{"ErrorCode":10,"ErrorMessage":"Cell provider was specified, but cell number is blank, Pager provider was specified, but pager number is blank"}
So it looks rather like it's some enum, and 0 hasn't a value.
It works well when I change serialized json from
"CellProviderID":0,"CellProviderID2":0,"CellProviderID3":0
to
"CellProviderID":"","CellProviderID2":"","CellProviderID3":""
How should I initialize the properties to be able to setup int values and get "" when I leave this property empty?
[JsonProperty]
public int CellProviderID { get; set; }
[JsonProperty]
public int CellProviderID2 { get; set; }
[JsonProperty]
public int CellProviderID3 { get; set; }
you can change return type of your properties from int to int?, so you will get null value as default.
It will be serialized as
"CellProviderID":null,"CellProviderID2":null,"CellProviderID3":null
so you can have a deal with that.
Next, you must change your serialization to ignore null values. If you're using Json.Net, you serialization must look like:
JsonConvert.SerializeObject(movie,Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })
So, your server will not receive non-initialized values
You could optionally write a Custom JsonConverter and handle the custom serialization/deserialization yourself.
internal class Inherited
{
public int MyProperty { get; set; }
public int MyProperty2 { get; set; }
}
internal class CustomConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Inherited);
}
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)
{
var data = value as Inherited;
if (data != null)
{
writer.WriteStartObject();
foreach (var item in typeof(Inherited).GetProperties())
{
writer.WritePropertyName(item.Name);
switch (item.PropertyType.Name)
{
case "Int32":
{
writer.WriteValue(default(int) == (int)item.GetValue(data, null) ? "" : item.GetValue(data, null).ToString());
break;
}
}
}
writer.WriteEnd();
}
}
}
JsonSerializerSettings obj = new JsonSerializerSettings();
obj.Converters.Add(new CustomConverter());
var result = JsonConvert.SerializeObject(new Inherited(0) { MyProperty = 0, MyProperty2 = 0 }, obj);
Results in -
{"MyProperty":"","MyProperty2":""}

Parse non-array JSON object as array with Json.net

I am working with an external API that returns a property either as an array or as an object, depending on the count. What is a good way to handle this?
Returning as array:
{
"contacts": {
"address": [
{
"id": "47602070",
"type": "Work",
"street": "MyStreet",
"city": "MyCity",
"zip": "12345",
"country": "USA"
},
{
"id": "47732816",
"type": "GPS",
"street": "50.0,30.0"
}
]
}
}
Returning as object:
{
"contacts": {
"address": {
"id": "47602070",
"type": "Work",
"street": "MyStreet",
"city": "MyCity",
"zip": "12345",
"country": "USA"
}
}
}
I'm thinking a workaround would be to use a custom deserializer and return an array of length 1 for the object case, and default deserialization for the array case, but I don't know how to do that yet.
I tried deserializing the object to an array and hoping Json.net would handle this case for me, but no dice.
Based on Christophe Geers' answer, here is what I ended up doing.
Create a custom JSON converter for always parsing the JSON as an array. If the JSON is a non-array object, then deserialize the object and wrap it in an array.
Mark the corresponding properties with a custom converter attribute.
Custom converter
public class JsonToArrayConverter<T> : CustomCreationConverter<T[]>
{
public override T[] Create(Type objectType)
{
// Default value is an empty array.
return new T[0];
}
public override object ReadJson(JsonReader reader, Type objectType, object
existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
// JSON object was an array, so just deserialize it as usual.
object result = serializer.Deserialize(reader, objectType);
return result;
}
else
{
// JSON object was not an arry, so deserialize the object
// and wrap it in an array.
var resultObject = serializer.Deserialize<T>(reader);
return new T[] {resultObject};
}
}
}
Data structures for the question example
public class Organisation
{
public Contacts contacts;
}
public class Address
{
public string id;
public string street;
public string city;
public string type;
public string zip;
public string country;
}
public class Contacts
{
// Tell JSON.net to use the custom converter for this property.
[JsonConverter(typeof(JsonToArrayConverter<Address>))]
public Address[] address;
}
A custom JSON.NET converter might do the trick here. It's not that hard.
For a DateTime property you might do it as follows. Just decorate the property in question with the custom converter.
[JsonObject(MemberSerialization.OptIn)]
public class MyClass
{
[JsonProperty(PropertyName = "creation_date")]
[JsonConverter(typeof(UnixDateTimeConverter))]
public DateTime CreationDate { get; set; }
}
JSON.NET provides most of the plumbing. Just derive from a base converter.
public class UnixDateTimeConverter : DateTimeConverterBase
{
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{ ...}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{ ... }
}
All you have to do is implement the ReadJson (deserialization) and WriteJson (serialization) methods.
You can find a complete example here:
Writing a custom Json.NET DateTime Converter
For your particular problem you need a bit more control. Try the following type of converter:
public class Contact
{
private List<Address> _addresses = new List<Address>();
public IEnumerable<Address> Addresses { get { return _addresses; }
}
public class ContactConverter : CustomCreationConverter<Contact>
{
public override Contact Create(Type objectType)
{
return new Contact();
}
public override object ReadJson(JsonReader reader, Type objectType, object
existingValue, JsonSerializer serializer)
{
var mappedObj = new Contact();
// Parse JSON data here
// ...
return mappedObj;
}
}
Using a custom converter like the one above you can parse the JSON data yourself and compose the Contact object(s) as you please.
I modified an example I found here:
JSON.NET Custom Converters–A Quick Tour
In this case you need to pass the custom converter when desearilizing.
Contact contact =
JsonConvert.DeserializeObject<Contact>(json, new ContactConverter());
Note: Instead of using a CustomCreationConverter, you can just use an ordinary converter. For example I use something like this:
public class SingleToArrayConverter<T> : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var items = (IEnumerable<T>)value;
if (value == null || !items.Any())
{
writer.WriteNull();
}
else if (items.Count() == 1)
{
serializer.Serialize(writer, items.ElementAt(0));
}
else
{
serializer.Serialize(writer, items);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (!CanConvert(objectType))
{
throw new NotSupportedException();
}
if (reader.TokenType == JsonToken.Null)
{
reader.Skip();
return null;
}
else if (reader.TokenType == JsonToken.StartObject)
{
return new T[] { serializer.Deserialize<T>(reader) };
}
else
{
return serializer.Deserialize<T[]>(reader);
}
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(IEnumerable<T>);
}
}

Categories

Resources