Change default null value in JSON.NET - c#

Is there some way to set what the default representation for null values should be in Json.NET?
More specifically null values inside an array.
Given the class
public class Test
{
public object[] data = new object[3] { 1, null, "a" };
}
Then doing this
Test t = new Test();
string json = JsonConvert.SerializeObject(t);
Gives
{"data":[1,null,"a"]}
Is it possible to make it look like this?
{"data":[1,,"a"]}
Without using string.Replace.

Figured it out. I had to implement a custom JsonConverter.
As others mentioned this will not produce valid/standard Json.
public class ObjectCollectionConverter : 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)
{
object[] collection = (object[])value;
writer.WriteStartArray();
foreach (var item in collection)
{
if (item == null)
{
writer.WriteRawValue(""); // This procudes "nothing"
}
else
{
writer.WriteValue(item);
}
}
writer.WriteEndArray();
}
}
Use it like this
Test t = new Test();
string json = JsonConvert.SerializeObject(t, new ObjectCollectionConverter());

Related

Newtonsoft JsonConverter to convert a string or object to object

I have a requirement for some deserialization I'm trying to handle where I could have these potential inputs:
{
"value": "a string"
}
-- or --
{
"value": {
"text": "a string"
// there are other properties, but for successful deserialization I only need text present
}
}
And I expect it to be able to successfully convert to the object, MyObject:
public class MyObject
{
[JsonProperty("text")
public string Text { get; set; }
}
So far this is what I have in my converter. This case works fine when it's a string (although not very efficient because I'm throwing an exception to catch a failed deserialization). However, when it's an object the reader throws an exception and I'm uncertain of how to handle it.
public class MyObjectConverter : JsonConverter
{
public override bool CanWrite { get => false; }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string) || objectType == typeof(MyObject);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = reader.Value?.ToString();
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
try
{
return JObject.Parse(value).ToObject<MyObject>();
}
catch (Exception)
{
return new MyObject
{
Text = value
};
}
}
}
perhaps I'm there is already a nice way to do this that I'm unaware of? If not, how can I determine whether my input is a string or an object to be able to return the object I care about?
SOLUTION:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
{
return new MyObject
{
Text = reader.Value?.ToString()
};
}
else if (reader.TokenType == JsonToken.StartObject)
{
JObject obj = JObject.Load(reader);
return obj.ToObject<MyObject>();
}
else
{
return null;
}
}
Your Object isn't structured like the input your expecting. For the second case, MyObject would need to look like this:
// this is ugly
public class MyObject
{
public Value value{get;set;}
public class Value{
public string text {get;set;}
}
}
If you want to have just an object with a single Text property like you currently do, you could do something like this:
public override object ReadJson(JsonReader reader, Type objectType, object
existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
var value = obj["value"];
if(value is JObject) // this will be true if the value property is a nested structure
return new MyObject(){Text=value["text"]}; // could also do value.ToObject<MyObject>() if you need more properties
else
return new MyObject(){Text=value};
}

Edit Json property then deserialize with json.net [duplicate]

Assuming a json string like the following:
string json = '{"string_property":"foo_bar", ... other objects here ...}';
I was wondering if there's a way to run a transformation on the parsed object so that instead of getting foo_bar, I'll get foo bar after running the following method (can be anything really)
public string Transform(string s) {
return s.Replace("_"," ");
}
I can manually alter my poco after deserializing, but wondered what would be a "cleaner" approach?
You can transform your string properties as you deserialize your root object by using a custom JsonConverter targeted at all string type values:
public class ReplacingStringConverter : JsonConverter
{
readonly string oldValue;
readonly string newValue;
public ReplacingStringConverter(string oldValue, string newValue)
{
if (string.IsNullOrEmpty(oldValue))
throw new ArgumentException("string.IsNullOrEmpty(oldValue)");
if (newValue == null)
throw new ArgumentNullException("newValue");
this.oldValue = oldValue;
this.newValue = newValue;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var s = (string)JToken.Load(reader);
return s.Replace(oldValue, newValue);
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then use it like:
var settings = new JsonSerializerSettings { Converters = new[] { new ReplacingStringConverter("_", "") } };
var result = JsonConvert.DeserializeObject<RootObject>(json, settings);
Note however that if individual string-type properties have their own converters applied directly with [JsonConverter(Type)], those converters will be used in preference to the ReplacingStringConverter in the Converters list.
I've ended up doing the following:
First, create a converter that only reads and all it does is url decode the string.
public class UrlDecoderConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var s = (string)JToken.Load(reader);
return HttpUtility.UrlDecode(s);
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, simply add the following to the POCO properties that need to be decoded:
[JsonConverter(typeof(UrlDecoderConverter))]
public string url { get; set; }

How to remove null values from IEnumerables when parsing JSON with Json.Net

I'm using Json.Net to parse JSON into my app logic.
The problem is that the external API that I get the JSON from sometimes has "null" items inside their lists.
I would like to remove those "null" items from the list (or any other IEnumerable that might have that) at parse time.
I believe the solution has to be using a JsonConverter but I was unable to get it working so far.
MyData data = new MyData();
Newtonsoft.Json.JsonSerializerSettings settings = new Newtonsoft.Json.JsonSerializerSettings
{
Converters = new List<JsonConverter>() { new TrimNullListValues() }
};
string jsonString = #"{""ListData"": [{""source"" : 10 , ""target"" : 20, ""Data"" : [{""source"" : 100 , ""target"" : 200}, null]}, null]}";
JsonConvert.PopulateObject(jsonString, data, settings);
MyData class is like this:
public class MyData {
public class MyNestedData
{
public int Source;
public int Target;
public List<MyNestedData> Data;
}
public List<MyNestedData> ListData;
}
My JsonConverter (TrimNullListValues) is like this:
public class TrimNullListValues : JsonConverter {
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Don't really know what to do in here to remove unwanted values
// From the IEnumerabes
}
public override bool CanConvert(Type objectType)
{
return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(List<>);
}
}
You could try something like this:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JArray array = JArray.Load(reader);
foreach (JToken item in array.ToList())
{
if (item.Type == JTokenType.Null)
item.Remove();
}
object list = Activator.CreateInstance(objectType);
serializer.Populate(array.CreateReader(), list);
return list;
}
Fiddle: https://dotnetfiddle.net/SESCfZ

Merge two objects during serialization using json.net?

I met a situation as below could anybody help me achieve as below?
For Example, if I have the class:-
public class Sample
{
public String name {get;set;}
public MyClass myclass {get;set;}
}
My Myclass will be as follow:
public class MyClass
{
public String p1 {get;set;}
public String p2 {get;set;}
}
When I am using Json.net to Serialize the object of the class Sample,I got as below and it works well.
{
"name":"...",
"myclass":
{
"p1":"...",
"p2":"..."
}
}
Its correct but I wonder is it possible to get the json string as below?
{
"name":"...",
"p1":"...",
"p2":"..."
}
You can create anonymous object and serialize it:
var sample = new Sample {
name = "Bob",
myclass = new MyClass {
p1 = "x",
p2 = "y"
}};
string json = JsonConvert.SerializeObject(new {
sample.name,
sample.myclass.p1,
sample.myclass.p2
});
Result
{"name":"Bob","p1":"x","p2":"y"}
But I suggest you either use default serialization of your Sample class, or create class which will be serialized into your format (i.e. move MyClass properties into Sample).
UPDATE: You can use custom converter, which flattens object and serializes all inner objects properties as top level object properties:
public class FlattenJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
return;
}
JObject o = (JObject)t;
writer.WriteStartObject();
WriteJson(writer, o);
writer.WriteEndObject();
}
private void WriteJson(JsonWriter writer, JObject value)
{
foreach (var p in value.Properties())
{
if (p.Value is JObject)
WriteJson(writer, (JObject)p.Value);
else
p.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return true; // works for any type
}
}
Usage:
string json = JsonConvert.SerializeObject(sample, new FlattenJsonConverter());
Or you can simply hide anonymous type creation into custom converter, if you need this behavior for one type only:
public class SampleJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer,
object value, JsonSerializer serializer)
{
Sample sample = (Sample)value;
JToken t = JToken.FromObject(new {
sample.name,
sample.myclass.p1,
sample.myclass.p2
});
t.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Sample);
}
}

Object cannot by deserialized, because 'Collection was of a fixed size.'

I have easy example of my real code. I need serialize to JSON and deserialize back object of class TestClass, which is derived from Letters. Both classes have constructor with parameter.
public class TestClass : Letters
{
public string[] Names { get; private set; }
public TestClass(string[] names)
: base(names)
// : base(new [] { "A", "B", })
// : base(names.Select(a => a.Substring(0, 1)).ToArray())
{
Names = names;
}
}
public abstract class Letters
{
public string[] FirstLetters { get; private set; }
protected Letters(string[] letters)
{
FirstLetters = letters;
}
}
Object of TestClass is serialized to valid JSON, but when I try it deserialize back to object, NotSupportedException is throw with message Collection was of a fixed size.
Here is my test
[Fact]
public void JsonNamesTest()
{
var expected = new TestClass(new [] { "Alex", "Peter", "John", });
var serialized = JsonConvert.SerializeObject(expected);
Console.WriteLine(serialized);
Assert.False(string.IsNullOrWhiteSpace(serialized));
var actual = JsonConvert.DeserializeObject<TestClass>(serialized);
AssertEx.PrimitivePropertiesEqual(expected, actual);
}
Json.Net needs all classes to have a parameterless constructor in order to deserialize them, otherwise it doesn't know how to call the constructor. One way to get around this without changing your classes is to make a custom JsonConverter that will create the object instance from the JSON. For example:
class TestClassConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(TestClass) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
string[] names = jo["Names"].ToObject<string[]>();
return new TestClass(names);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
Then, deserialize your class like this and it should work:
var actual = JsonConvert.DeserializeObject<TestClass>(serialized, new TestClassConverter());
Thank, it works! I modified your code for more general usage in my example.
I suppose
is only one public constructor
serialized parameters have same name as constructor parameters (ignore case)
public class ParametersContructorConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Letters).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jo = JObject.Load(reader);
var contructor = objectType.GetConstructors().FirstOrDefault();
if (contructor == null)
{
return serializer.Deserialize(reader);
}
var parameters = contructor.GetParameters();
var values = parameters.Select(p => jo.GetValue(p.Name, StringComparison.InvariantCultureIgnoreCase).ToObject(p.ParameterType)).ToArray();
return contructor.Invoke(values);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}

Categories