I would like to serialize all nulls from string and nullable types to empty strings. I looked into using a custom JsonConverter but properties with nulls are not passed into the converter. I looked into using a contract resolver and value provider but it won't let me set the value of an int? to an empty string.
What seems most promising is using a custom JsonWriter that overrides the WriteNull method, but when I instantiate that in the converter's WriteJson method, it doesn't write anything out.
public class NullJsonWriter : JsonTextWriter
{
public NullJsonWriter(TextWriter writer) : base(writer)
{
}
public override void WriteNull()
{
base.WriteValue(string.Empty);
}
}
public class GamesJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(IEnumerable<IGame>).IsAssignableFrom(objectType);
}
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 = new NullJsonWriter(new StringWriter(new StringBuilder()));
writer.WriteStartObject();
if (typeof (IEnumerable).IsAssignableFrom(value.GetType()))
{
var list = (IEnumerable<IGame>)value;
if (!list.Any())
{
writer.WriteEndObject();
return;
}
var properties = list.First().GetType().GetProperties();
PropertyInfo gameId = null;
foreach (var prop in properties)
{
if (prop.Name == "GameId")
{
gameId = prop;
break;
}
}
for (int i = 0; i < list.Count(); i++)
{
writer.WritePropertyName(string.Format("{0}", gameId.GetValue(list.ElementAt(i))));
serializer.Serialize(writer, list.ElementAt(i));
}
}
writer.WriteEndObject();
}
I can see JSON text in the StringBuilder of the writer but it doesn't actually get written out to my webpage.
I'm not sure I understand the reasoning of why you want to write out an empty string in place of all nulls, even those that might represent a Nullable<int>, or other object. The problem with that strategy is it makes the JSON more difficult to deal with during deserialization: if the consumer is expecting a Nullable<int> and they get a string instead, it leads to errors, confusion, and essentially forces the consuming code to use a special converter or weakly typed objects (e.g. JObject, dynamic) to get around the mismatch. But let's put that aside for now and say that you have your reasons.
You can definitely do what you want by subclassing the JsonTextWriter class and overriding the WriteNull method to write an empty string instead. The trick is that you must instantiate your own JsonSerializer instead of using JsonConvert.SerializeObject, since the latter does not have any overloads that accept a JsonWriter. Here is a short proof-of-concept that shows that this idea will work:
class Program
{
static void Main(string[] args)
{
// Set up a dummy object with some data. The object also has a bunch
// of properties that are never set, so they have null values by default.
Foo foo = new Foo
{
String = "test",
Int = 123,
Decimal = 3.14M,
Object = new Bar { Name = "obj1" },
Array = new Bar[]
{
new Bar { Name = "obj2" }
}
};
// The serializer writes to the custom NullJsonWriter, which
// writes to the StringWriter, which writes to the StringBuilder.
// We could also use a StreamWriter in place of the StringWriter
// if we wanted to write to a file or web response or some other stream.
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
using (NullJsonWriter njw = new NullJsonWriter(sw))
{
JsonSerializer ser = new JsonSerializer();
ser.Formatting = Formatting.Indented;
ser.Serialize(njw, foo);
}
// Get the JSON result from the StringBuilder and write it to the console.
Console.WriteLine(sb.ToString());
}
}
class Foo
{
public string String { get; set; }
public string NullString { get; set; }
public int? Int { get; set; }
public int? NullInt { get; set; }
public decimal? Decimal { get; set; }
public decimal? NullDecimal { get; set; }
public Bar Object { get; set; }
public Bar NullObject { get; set; }
public Bar[] Array { get; set; }
public Bar[] NullArray { get; set; }
}
class Bar
{
public string Name { get; set; }
}
public class NullJsonWriter : JsonTextWriter
{
public NullJsonWriter(TextWriter writer) : base(writer)
{
}
public override void WriteNull()
{
base.WriteValue(string.Empty);
}
}
Here is the output:
{
"String": "test",
"NullString": "",
"Int": 123,
"NullInt": "",
"Decimal": 3.14,
"NullDecimal": "",
"Object": {
"Name": "obj1"
},
"NullObject": "",
"Array": [
{
"Name": "obj2"
}
],
"NullArray": ""
}
Fiddle: https://dotnetfiddle.net/QpfG8D
Now let's talk about why this approach did not work as expected inside your converter. When JSON.Net calls into your converter, it already has a JsonWriter instantiated, which gets passed into the writer parameter of the WriteJson method. Instead of writing to that instance, what you have done is overwritten this variable with a new NullJsonWriter instance. But remember, replacing the contents of a reference variable does not replace the original instance that variable refers to. You are successfully writing to your new writer instance in the converter, but all the output that you are writing is going into the StringBuilder which you instantiated at the beginning of the WriteJson method. Since you never take the JSON result from the StringBuilder and do something with it, it is simply thrown away at the end of the method. Meanwhile, Json.Net continues to use its original JsonWriter instance, to which you have not written anything inside your converter. So, that is why you are not seeing your customized JSON in the final output.
There are a couple of ways to fix your code. One approach is just to use a new variable when instantiating the NullJsonWriter in your converter instead of hijacking the writer variable. Then, at the end of the WriteJson method, get the JSON from the StringBuilder and write it to the original writer using WriteRawValue method on the original writer.
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
StringBuilder sb = new StringBuilder();
using (var innerWriter = new NullJsonWriter(new StringWriter(sb))
{
innerWriter.WriteStartObject();
// ... (snip) ...
innerWriter.WriteEndObject();
}
writer.WriteRawValue(sb.ToString());
}
Another approach, depending on your intended result, is not to use a separate writer inside your converter at all, but instead to create the NullJsonWriter instance at the very beginning of serialization and pass it to the outer serializer (as I did in the proof-of-concept earlier in the post). When Json.Net calls into your converter, the writer that is passed to WriteJson will be your custom NullJsonWriter, so at that point you can write to it naturally without having to jump through hoops. Keep in mind, however, that with this approach your entire JSON will have the nulls replaced with empty strings, not just the IEnumerable<IGame> that your converter handles. If you are trying to confine the special behavior to just your converter, then this approach will not work for you. It really depends on what you are trying to accomplish-- do you want the nulls replaced everywhere or just in part of the JSON? I did not get a good sense of this from your question.
Anyway, hope this helps.
JsonSerializer _jsonWriter = new JsonSerializer
{
NullValueHandling = NullValueHandling.Include
};
Use this to make the serializer not ignore null values, then you should be able to override the serialization with the code you already have :)
Related
As the title mentions, I am trying to deserialize a JSON but am having some trouble. I think below includes the necessary information.
public class Variable<T> : IVariable where T : IConvertible
{
//...
}
public class ArrayVariable<T> : IVariable where T : IConvertible
{
//...
}
So I have a list of IVariable which I then serialize successfully (all of the information is in the json):
JsonConvert.SerializeObject(myIVariableList)
Now I am trying to deserialize it but I am having trouble determining the correct way to go about doing it as it involves finding the generic type T in addition to the type Variable or ArrayVariable. I have already tried
JsonConvert.DeserializeObject<List<IVariable>>(result.newValues)
but obviously, you can create instances of an interface. Any help would be much appreciated.
You can use TypeNameHandling.All but I would strongly recommend you avoid it due to it being very dangerous and allows attackers to compromise your code.
Another safer option is to use a custom converter. Here's a very trivial (and fragile) example that should get you started:
First lets make some basic classes that share an interface:
public interface IVariable { }
public class Foo : IVariable
{
public int A { get; set; }
}
public class Bar : IVariable
{
public int B { get; set; }
}
Now we can make our converter:
public class IVariableConverter : JsonConverter<IVariable>
{
public override IVariable ReadJson(JsonReader reader, Type objectType,
IVariable existingValue, bool hasExistingValue, JsonSerializer serializer)
{
// First load the JSON into a JObject
var variable = JObject.Load(reader);
// If the JSON had a property called A, it must be a Foo:
if (variable.ContainsKey("A"))
{
return variable.ToObject<Foo>();
}
// If the JSON had a property called B, it must be a Bar:
if (variable.ContainsKey("B"))
{
return variable.ToObject<Bar>();
}
// And who knows what was passed in if it was missing both of those properties?!
throw new Exception("Er, no idea what that JSON was supposed to be!");
}
public override void WriteJson(JsonWriter writer, IVariable value,
JsonSerializer serializer)
{
// Feel free to write your own code here if you need it
throw new NotImplementedException();
}
}
And now we can do some actual deserialising:
// A basic JSON example:
var json = "[{\"A\":1},{\"B\":2}]";
// The settings to tell the serialiser how to process an IVariable object
var settings = new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new IVariableConverter() }
};
// And deserialise with the defined settings
var result = JsonConvert.DeserializeObject<List<IVariable>>(json, settings);
You will need to be a bit more creative with how you identify each type, but this is a safe way to achieve what you need.
You can use TypeNameHandling.All to add type information to your serialiazed json and then utilize it during parsing:
var variables = new List<IVariable>()
{
new Variable<int>()
};
var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
var serializeObject = JsonConvert.SerializeObject(variables, settings);
var list = JsonConvert.DeserializeObject<List<IVariable>>(serializeObject, settings);
I have a custom JSON converter, because 3rd party API accept specific structure only.
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var bodyRequest = (IRequest) value;
writer.WriteStartObject();
writer.WritePropertyName(bodyRequest.RequestName);
writer.WriteStartObject();
writer.WritePropertyName("-xmlns");
writer.WriteValue("http://example.com");
writer.WritePropertyName(bodyRequest.WrapperName);
writer.WriteStartObject();
var contractResolver = new CamelCasePropertyNamesContractResolver {
IgnoreSerializableAttribute = false
};
var properties = value.GetType().GetProperties();
foreach(var propertyInfo in properties) {
if (HasAttribute(propertyInfo, typeof(JsonIgnoreAttribute))) {
continue;
}
var propValue = propertyInfo.GetValue(value);
var propertyName = contractResolver.GetResolvedPropertyName(propertyInfo.Name);
writer.WritePropertyName(propertyName);
writer.WriteValue(propValue);
}
writer.WriteEndObject();
writer.WriteEndObject();
writer.WriteEndObject();
}
So, I create strcuture with help of WriteXXX methods, then I get all properties and serialize them. All works fine, but I need to handle Enums. For example, we have the following request model:
public class ExampleRequest : IRequest
{
public long Id{ get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public CarType CarType { get; set; }
public string RequestName => "Request";
public string WrapperName => "SendData";
}
public enum CarType
{
[EnumMember(Value = "NEW_CAR")
New,
[EnumMember(Value = "OLD_CAR")
Old
}
Currently, after serialization I see that CarType has numeric value 0 or 1,I understand that I use reflection and StringEnumConverter is ignored. How to properly serialize in this case?
Inside your loop, check whether the property has a JsonConverterAttribute applied. If so, get the type of converter, instantiate it and then call its WriteJson method instead of writing the value directly. Otherwise, just write the value as you are doing now.
In other words, replace this line:
writer.WriteValue(propValue);
with this:
var att = propertyInfo.GetCustomAttribute<JsonConverterAttribute>();
if (att != null)
{
var converter = (JsonConverter)Activator.CreateInstance(att.ConverterType);
converter.WriteJson(writer, propValue, serializer);
}
else
{
writer.WriteValue(propValue);
}
Temporary note: This is NOT a duplicate of the above mentioned post
Let's say I have a server-side class structure like this.
public class Test
{
// this can be any kind of "Tag"
public object Data { get; set; }
}
public class Other
{
public string Test { get; set; }
}
Now a string like this is coming from let's say the client.
{"Data": [{$type: "MyProject.Other, MyProject", "Test": "Test"}] }
When I try to deserialize this into a Test instance, I get a result where the Tag property is a JToken instead of some kind of collection, for example ArrayList or List<object>.
I understand that Json.NET cannot deserialize into a strongly typed list, but I'd expect that it respects that it's at least a list.
Here is my current deserialization code.
var settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto,
};
var str = "{\"Data\": [{\"$type\": \"MyProject.Other, MyProject\", \"Test\": \"Test\"}] }";
var test = JsonConvert.Deserialize<Test>(str, settings);
// this first assertion fails
(test.Data is IList).ShouldBeTrue();
(((IList)test.Data)[0] is Other).ShouldBeTrue();
I'm aware of the fact that if I serialize such a structure, then by default I'll get a { $type: ..., $values: [...]} structure in the JSON string instead of a pure array literal, and that will indeed properly deserialize. However, the client is sending a pure array literal, so I should be able to handle that in some way.
I managed to put together a JsonConverter to handle these kind of untyped lists. The converter applies when the target type is object. Then if the current token type is array start ([) it will force a deserialization into List<object>. In any other case it will fall back to normal deserialization.
This is a first version which passes my most important unit tests, however as I'm not a Json.NET expert, it might break some things unexpectedly. Please if anyone sees anything what I didn't, leave a comment.
public class UntypedListJsonConverter : JsonConverter
{
public override bool CanWrite => false;
public override bool CanRead => true;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.StartArray)
{
return serializer.Deserialize(reader);
}
return serializer.Deserialize<List<object>>(reader);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(object);
}
}
Usage example:
var settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto,
Converters = new[] { new UntypedListJsonConverter() }
};
var str = "{\"Data\": [{\"$type\": \"MyProject.Other, MyProject\", \"Test\": \"Test\"}] }";
var test = JsonConvert.Deserialize<Test>(str, settings);
// now these assertions pass
(test.Data is IList).ShouldBeTrue();
(((IList)test.Data)[0] is Other).ShouldBeTrue();
Try this:
public class Test
{
public Dictionary<string, List<Other>> Data { get; } = new Dictionary<string, List<Other>>();
}
You need to set up the class you are trying to fill from json data to match as closely to the json structure. From the looks of it, the json looks a dictionary where the keys are strings and the values are arrays of Other objects.
Let's say I've the following dynamic object:
public class SomeDynamicObject : DynamicObject
{
public string Text { get; set; }
}
If I serialize it using JsonConvert.SerializeObject(new SomeDynamicObject { Text = "hello world" }) it'll return {} instead of { "Text": "hello world" }.
I suspect the issue is that JSON.NET thinks it's a full dynamic object while my case is a dynamic object with declared members.
Is there any serialization settings or built-in converter that could be configured so JSON.NET can serialize both kinds of members?
To avoid confusion
Actual use case: I don't know which will be the types being serialized but I need to cover the whole use case of serializing declared properties of a dynamic object.
That is, I can't use attributes. That's why I'm asking if there's some converter or a serialization setting that can generalize this use case.
Update for non-attribute converter
Since you can't decorate, you lose a lot of power. Once the JsonWriter has converted to a JObject, the dynamic properties appear to be lost.
However, you can always use a little reflection in a custom converter's WriteJson method to serialize non-dynamic types.
public class SomeDynamicObject : DynamicObject
{
public string Text { get; set; }
public DynamicObject DynamicProperty { get; set; }
}
public class CustomDynamicConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
JObject jObject = JObject.Load(reader);
var target = Activator.CreateInstance(objectType);
//Create a new reader for this jObject, and set all properties to match the original reader.
JsonReader jObjectReader = jObject.CreateReader();
jObjectReader.Culture = reader.Culture;
jObjectReader.DateParseHandling = reader.DateParseHandling;
jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
jObjectReader.FloatParseHandling = reader.FloatParseHandling;
// Populate the object properties
serializer.Populate(jObjectReader, target);
return target;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var properties = value.GetType().GetProperties().Where(x => x.PropertyType != typeof(DynamicObject)).ToList();
JObject o = (JObject)JToken.FromObject(value);
properties.ForEach(x =>
{
o.AddFirst(new JProperty(x.Name, x.GetValue(value)));
});
o.WriteTo(writer);
}
}
If you explicitly decorate your properties with [JsonProperty], the serializer will pick them up, even if the containing type is dynamic.
public class SomeDynamicObject : DynamicObject
{
[JsonProperty]
public string Text { get; set; }
}
when serialized correctly outputs:
{"Text":"hello world"}
I am logging all requests to my WCF web services, including the arguments, to the database. This is the way I do it:
create a class WcfMethodEntry which derives from PostSharp's aspect OnMethodBoundaryAspect,
annotate all WCF methods with WcfMethodEntry attribute,
in the WcfMethodEntry I serialize the method arguments to json with the JsonConvert.SerializeObject method and save it to the database.
This works ok, but sometimes the arguments are quite large, for example a custom class with a couple of byte arrays with photo, fingerprint etc. I would like to exclude all those byte array data types from serialization, what would be the best way to do it?
Example of a serialized json:
[
{
"SaveCommand":{
"Id":5,
"PersonalData":{
"GenderId":2,
"NationalityCode":"DEU",
"FirstName":"John",
"LastName":"Doe",
},
"BiometricAttachments":[
{
"BiometricAttachmentTypeId":1,
"Parameters":null,
"Content":"large Base64 encoded string"
}
]
}
}
]
Desired output:
[
{
"SaveCommand":{
"Id":5,
"PersonalData":{
"GenderId":2,
"NationalityCode":"DEU",
"FirstName":"John",
"LastName":"Doe",
},
"BiometricAttachments":[
{
"BiometricAttachmentTypeId":1,
"Parameters":null,
"Content":"..."
}
]
}
}
]
Edit: I can't change the classes that are used as arguments for web service methods - that also means that I cannot use JsonIgnore attribute.
The following allows you to exclude a specific data-type that you want excluded from the resulting json. It's quite simple to use and implement and was adapted from the link at the bottom.
You can use this as you cant alter the actual classes:
public class DynamicContractResolver : DefaultContractResolver
{
private Type _typeToIgnore;
public DynamicContractResolver(Type typeToIgnore)
{
_typeToIgnore = typeToIgnore;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
properties = properties.Where(p => p.PropertyType != _typeToIgnore).ToList();
return properties;
}
}
Usage and sample:
public class MyClass
{
public string Name { get; set; }
public byte[] MyBytes1 { get; set; }
public byte[] MyBytes2 { get; set; }
}
MyClass m = new MyClass
{
Name = "Test",
MyBytes1 = System.Text.Encoding.Default.GetBytes("Test1"),
MyBytes2 = System.Text.Encoding.Default.GetBytes("Test2")
};
JsonConvert.SerializeObject(m, Formatting.Indented, new JsonSerializerSettings { ContractResolver = new DynamicContractResolver(typeof(byte[])) });
Output:
{
"Name": "Test"
}
More information can be found here:
Reducing Serialized JSON Size
You could just use [JsonIgnore] for this specific property.
[JsonIgnore]
public Byte[] ByteArray { get; set; }
Otherwise you can also try this: Exclude property from serialization via custom attribute (json.net)
Another way would be to use a custom type converter and have it return null, so the property is there but it will simply be null.
For example i use this so i can serialize Exceptions:
/// <summary>
/// Exception have a TargetSite property which is a methodBase.
/// This is useless to serialize, and can cause huge strings and circular references - so this converter always returns null on that part.
/// </summary>
public class MethodBaseConverter : JsonConverter<MethodBase?>
{
public override void WriteJson(JsonWriter writer, MethodBase? value, JsonSerializer serializer)
{
// We always return null so we don't object cycle.
serializer.Serialize(writer, null);
}
public override MethodBase? ReadJson(JsonReader reader, Type objectType, MethodBase? existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
return null;
}
}
Try to use the JsonIgnore attribute.