Merge two objects during serialization using json.net? - c#

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

Related

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

JSonConverter how to make generic deserialization

I have been able to do a custom Converter that transforms to a list of interfaces.
Here my custom converter:
public class InvoiceDetailConverter : JsonConverter {
public override bool CanConvert(Type objectType) {
//assume we can convert to anything for now
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
List<InvoiceDetail> data = serializer.Deserialize<List<InvoiceDetail>>(reader);
// now loop to make the proper list
List<IInvoiceDetail> result = new List<IInvoiceDetail>();
foreach (IInvoiceDetail d in data) {
result.Add(d);
}
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
//use the default serialization - it works fine
serializer.Serialize(writer, value);
}
}
What I would like is to make this generic like
public class InvoiceDetailConverter <TConcrete, TInterface> : JsonConverter {
public override bool CanConvert(Type objectType) {
//assume we can convert to anything for now
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
List<TConcrete> data = serializer.Deserialize<List<TConcrete>>(reader);
// now loop to make the proper list
List<TInterface> result = new List<TInterface>();
foreach (TInterface d in data) {
result.Add(d);
}
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
//use the default serialization - it works fine
serializer.Serialize(writer, value);
}
}
At the End in my object it will be used like this:
public class test{
[JsonConverter(typeof(InvoiceDetailConverter<InvoiceDetail, IInvoiceDetail>))]
public List<IInvoiceDetail> InvoiceDetail { get; set; }
}
Is it possible to convert like this, as the above code won't compile. I'm using .NET Core.
I believe you're almost there, just specify dependency between TConcrete and TInterface:
public class InvoiceDetailConverter<TConcrete, TInterface> : JsonConverter
where TConcrete : TInterface // <------------------------------------------ add this
{
// ..........
}
Here you can find a demo of successful deserialization using the generic converter class for the following object:
public class test
{
[JsonConverter(typeof(InvoiceDetailConverter<InvoiceDetail, IInvoiceDetail>))]
public List<IInvoiceDetail> InvoiceDetail { get; set; }
[JsonConverter(typeof(InvoiceDetailConverter<VatDetail, IVatDetail>))]
public List<IVatDetail> VatDetail { 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

Change default null value in JSON.NET

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

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