Json.Net duplicates private list items - c#

Currently I'm trying to utilize Json.Net serializer in my projects.
One of the problems that I've faced recentry is that deserialized object gets all of it's private list items duplicated.
It can be reproduced like this:
Create class with private field of List<> type, where List<> generic parameter is some other class
Add public property, that gets/sets private field
Create instance of that class, add 1 item to inner list, using property.
Serialize with new DefaultContractResolver that is setup to see private class data;
Deserialize
Code to reproduce the problem:
public class ClassParam
{
public int? ParamOne
{
get;
set;
}
}
public class ClassWithParams
{
private List<ClassParam> _privateFieid = new List<ClassParam>();
public List<ClassParam> PropertWithBackingField
{
get { return _privateFieid; }
set { _privateFieid = value; }
}
public void AddElementToPrivateField(ClassParam classParam)
{
_privateFieid.Add(classParam);
}
}
[Test]
public void Test()
{
var instance = new ClassWithParams();
var param1 = new ClassParam { ParamOne = 1 };
instance.PropertWithBackingField.Add(param1);
var contractResolver = new DefaultContractResolver();
contractResolver.DefaultMembersSearchFlags |= BindingFlags.NonPublic;
string serializedInstance = JsonConvert.SerializeObject(instance,
Formatting.Indented,
new JsonSerializerSettings()
{
ContractResolver = contractResolver
});
var deserializeInstance = JsonConvert.DeserializeObject(serializedInstance, typeof(ClassWithParams),
new JsonSerializerSettings()
{
ContractResolver = contractResolver
});
}
When I remove public property PropertWithBackingField from ClassWithParams it's all ok.
The problem is gone as well when I don't use custom setup for ContractResolver. But I do need to serialize private data of my classes as soon as not all of it is exposed via public properties.
What's wrong with my code? Are there any subtleties, using Json.Net or is it a bug?

For this code
var s = JsonConvert.SerializeObject(instance);
var desInst = JsonConvert.DeserializeObject<ClassWithParams>(s);
Your json will be {"PropertWithBackingField":[{"ParamOne":1}]}
But you say to include private field in serialization/deserialization with
contractResolver.DefaultMembersSearchFlags |= BindingFlags.NonPublic;
and get a json
{
"_privateFieid": [
{
"ParamOne": 1
}
],
"PropertWithBackingField": [
{
"ParamOne": 1
}
]
}
So there are two deserializations, one for _privateFieid and one for PropertWithBackingField
Either use BindingFlags.Public or use my code above with is much simpler.

DeserializeInstance method receives existing instance as argument. Probably it does not creates new instance but fills in existing and returns it.
Try to put ReferenceEquals(instance, deserializedInstance) to your watch. To avoid data duplicating use overload that does not accepts existing instance or create new instance and deserialize it.

Related

How to merge a JSON object with the current class

I am trying to make a class that can save and load its own members from a json file automatically.
What I want to know is if NewtonSoft.Json provides a method to do that already or if I will have to use reflection.
class Settings
{
// this is my setting
public bool dostuff = false;
public int maxstuff = 123;
public string namestuff = "foo";
List<string> arrayofstuff = new List<string>();
private string fileLocation;
public Settings(string fileLocation)
{
this.fileLocation = fileLocation;
}
public void LoadSettings()
{
string content = System.IO.File.ReadAllText(this.fileLocation);
JObject data = JObject.Parse(content);
// Normally I would have a sub class that contains all the settings
// I would create an instance of it. Serialize into a JObject
// Then merge with the data object.
// Then use ToObject to assign the updated values
myDuplicateJObject.Merge(data, new JsonMergeSettings
{
MergeArrayHandling = MergeArrayHandling.Union
});
// However I need to apply it to the current object which is "this"
}
public void SaveSettings()
{
System.IO.File.WriteAllText(this.fileLocation, JsonConvert.SerializeObject(this));
}
}
The only two ways I can currently think of to solve this would be to use reflection to try and merge a duplicate copy of my class or to make a sub class that contains all the settings and just use that as a member.
You can use JsonConvert.PopulateObject, passing this into it.
public class Settings
{
public string Name = "foo";
public void Populate(string json)
{
JsonConvert.PopulateObject(json, this);
}
}
You can change that Populate method to read the JSON file itself.

JSON.NET deserialize object using all properties in JSON (overriding all initialized in the constructor) [duplicate]

This question already has answers here:
Populate object where objects are reused and arrays are replaced?
(2 answers)
Closed 4 years ago.
I have a class with some properties, one of them a list of items. All of them are initialized in the default parameterless constructor of the class. I only want to have to have this constructor that initalizes everything.
This is a case for many of my classes.
public class ExampleClass
{
public ExampleClass()
{
this.ListProperty = new List<int> { 1, 2 };
this.APropertyToBeNull = new TypeA();
this.BPropertyToBeNull = new TypeB();
}
public List<int> ListProperty { get; set; }
public TypeA APropertyToBeNull { get; set; }
public TypeB BPropertyToBeNull { get; set; }
}
I create an instance of this class, I set some of the properties to null and I modify the list property to have 2 items.
var instance = new ExampleClass();
instance.APropertyToBeNull = null;
instance.BPropertyToBeNull = null;
instance.ListProperty = new List<int> { 3 };
var raw = JsonConvert.SerializeObject(instance);
var deserialized = JsonConvert.DeserializeObject<ExampleClass>(raw, settings);
Assert.AreEqual(1, deserialized.ListProperty.Count);
Assert.IsNull(deserialized.APropertyToBeNull);
Assert.IsNull(deserialized.BPropertyToBeNull);
When I deserialize, I don't find a way of getting the item exactly as I serialized it. I got two options:
If I set the ObjectCreationHandling to Replace, the list deserializes fine, but the null properties are not null anymore. The constructor initialized everything, the deserialization replaced the list completely but it did not set the null properties to null.
If I set the ObjectCreationHandling to Auto or Reuse, the null properties are deserialized fine as null, but the list has the items that were initialized in the constructor plus the items in the JSON. I only want those in the JSON.
How do I get the exact same item I serialized without removing the initialization of all the properties in the constructor (I still want to have all of them initialized in case a new instance is created).
I have played with all possible settings of the serializer, and I don't find a solution.
As a further constraint, I don't want to add attributes to all of my classes. I have this problem for many of them.
You can use the [JsonProperty] attribute with the DefaultValueHandling property.
Given this class:
public class Foo
{
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public string Bar { get; set; }
public List<bool> Baz { get; set; }
public Foo()
{
Bar = "Not Null";
Baz = new List<bool>
{
false
};
}
}
And this JSON:
{ "baz" : [ true ] }
This code:
string json = "{ \"baz\" : [ true ] }";
var foo = JsonConvert.DeserializeObject<Foo>(json, new JsonSerializerSettings
{
ObjectCreationHandling = ObjectCreationHandling.Replace
});
Console.WriteLine(foo.Bar ?? "(null)");
foreach (var b in foo.Baz)
{
Console.WriteLine(b);
}
Will print:
(null)
True
Meaning the Bar string will be overwritten with the value from the JSON, being null since it's missing.

Duplicates in HashSet after deserialzation C#

I have a HashSet as seen below:
public name {get; set; }
[JsonProperty(ObjectCreationHandling = ObjectCreationHandling.Replace, TypeNameHandling = TypeNameHandling.Auto)]
public ICollection<T> data { get; set; }
public MyClass(string name)
{
name = name;
data = new HashSet<T>(new CustomComparer());
}
The comparer looks like:
public class CustomComparer: EqualityComparer<T>
{
public override bool Equals(T x, T y)
{
return string.Equals(x.val, y.val, StringComparison.OrdinalIgnoreCase);
}
public override int GetHashCode(T obj)
{
return $"{obj?.val?.ToLowerInvariant()}-{obj?.val?.ToLowerInvariant()}".GetHashCode();
}
}
Now, I have a unit test where I first create an instance of MyClass as:
var obj1 = new MyClass("test");
obj1.data.Add(CustomObject1);
obj1.data.Add(CustomObject2);
The next step I do is serialize and deserialize the object.
var jsonSerializerSettings = new JsonSerializerSettings();
var serializedObject = JsonConvert.SerializeObject(obj1, jsonSerializerSettings);
var deserializedUserObject = JsonConvert.DeserializeObject<MyClass>(serializedObject, jsonSerializerSettings);
Now, when I try to add CustomObject1 back to the deserialized object (which is already present), it still adds to the set.
Not sure why this is happening.
Any leads would be helpful.
This passed when I created a default constructor and initialized the data parameter as below.
public MyClass()
{
data = new HashSet<T>(new CustomComparer());
}
Not sure if this is the correct way to achieve this.
As there will be multiple instances of the data object being created unnecessarily.
Is there any better way to get this done?
Imposing a [JsonConstructor] property on top of the parameterized constructor didn't work too.
NOTE: I removed the ObjectCreationHandling property from the JsonProperty attribute.

Passing class name as parameter to JsonConvert.DeserializeObject

I am trying to compare return values of different rest api json responses, and i would like to create a method that takes a class name as a paremeter like the following. I have tried submitting as string and typeof(). would like to know what's the right way to pass ClassName as a parameter or if i should take a different approach.
class Employee
{
//different properties
}
class Patient
{
//different properties
}
class Tests
{
public bool compareValues(ClassName)
{
string expectedValues = File.ReadAllText(filePath);
var expectedValues = JsonConvert.DeserializeObject<ClassName[]>(fileResult, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() });
//similar thing: call rest api as above.
//compare logic
}
}
Thanks for the help!
It's called generics. See example below:
public bool compareValues<T>(ClassName)
{
string expectedValues = File.ReadAllText(filePath);
var expectedValues = JsonConvert.DeserializeObject<T[]>(fileResult, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() });
//similar thing: call rest api as above.
//compare logic
}
var employeeResult = compareValues<Employee>();
var patientResult = compareValues<Patient>();
Note method signature was changed and it contains <T> - a placeholder for class name. This will work if you already know which classes are used in your method. If you have only class name as a string, you have to deserialize your json without specifying concrete class JsonConvert.DeserializeObject(jsonString) and work with JObject (see Json.Net documentation)

Json.net deserializing list gives duplicate items

I have just started using Newtonsoft.Json (Json.net). In my first simple test, I ran into a problem when deserializing generic lists. In my code sample below I serialize an object, containing three types of simple integer lists (property, member var and array).
The resulting json looks fine (the lists are converted into json-arrays). However, when I deserialize the json back to a new object of the same type, all list items are duplicated, expect for the array. I've illustrated that by serializing it a second time.
From searching around, I've read that there may be a "private" backing field to the lists that the deserializer also fills.
So my question is: Is there a (preferably simple) way to avoid duplicate items in following case?
Code
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace JsonSerializeExample
{
public class Program
{
static void Main()
{
var data = new SomeData();
var json = JsonConvert.SerializeObject(data);
Console.WriteLine("First : {0}", json);
var data2 = JsonConvert.DeserializeObject<SomeData>(json);
var json2 = JsonConvert.SerializeObject(data2);
Console.WriteLine("Second: {0}", json2);
}
}
public class SomeData
{
public string SimpleField;
public int[] IntArray;
public IList<int> IntListProperty { get; set; }
public IList<int> IntListMember;
public SomeData()
{
SimpleField = "Some data";
IntArray = new[] { 7, 8, 9 };
IntListProperty = new List<int> { 1, 2, 3 };
IntListMember = new List<int> { 4, 5, 6 };
}
}
}
Resulting output
First : {"SimpleField":"Some data","IntArray":[7,8,9],"IntListMember":[4,5,6],"IntListProperty":[1,2,3]}
Second: {"SimpleField":"Some data","IntArray":[7,8,9],"IntListMember":[4,5,6,4,5,6],"IntListProperty":[1,2,3,1,2,3]}
There may be some overlap here with Json.Net duplicates private list items. However, I think my problem is even simpler, and I still haven't figured it out.
That is because you are adding items in the constructor. A common approach in deserializers when processing a list is basically:
read the list via the getter
if the list is null: create a new list and assign via the property setter, if one
deserialize each item in turn, and append (Add) to the list
this is because most list members don't have setters, i.e.
public List<Foo> Items {get {...}} // <=== no set
Contrast to arrays, which must have a setter to be useful; hence the approach is usually:
deserialize each item in turn, and append (Add) to a temporary list
convert the list to an array (ToArray), and assign via the setter
Some serializers give you options to control this behavior (others don't); and some serializers give you the ability to bypass the constructor completely (others don't).
I'm pretty sure that this post is not relevant anymore, but for future reference, here a working solution.
Just need to specify that ObjectCreationHandling is set to Replace, i.e. Always create new objects and not to Auto (which is the default) i.e. Reuse existing objects, create new objects when needed.
var data = new SomeData();
var json = JsonConvert.SerializeObject(data);
Console.WriteLine("First : {0}", json);
var data2 = JsonConvert.DeserializeObject<SomeData>(json, new JsonSerializerSettings() { ObjectCreationHandling = ObjectCreationHandling.Replace });
var json2 = JsonConvert.SerializeObject(data2);
Console.WriteLine("Second: {0}", json2);
I encountered a similar issue with a different root cause. I was serializing and deserializing a class that looked like this:
public class Appointment
{
public List<AppointmentRevision> Revisions { get; set; }
public AppointmentRevision CurrentRevision
{
get { return Revision.LastOrDefault(); }
}
public Appointment()
{
Revisions = new List<AppointmentRevision>();
}
}
public class AppointmentRevision
{
public List<Attendee> Attendees { get; set; }
}
When I serialized this, CurrentRevision was being serialized too. I'm not sure how, but when it was deserializing it was correctly keeping a single instance of the AppointmentRevision but creating duplicates in the Attendees list. The solution was to use the JsonIgnore attribute on the CurrentRevision property.
public class Appointment
{
public List<AppointmentRevision> Revisions { get; set; }
[JsonIgnore]
public AppointmentRevision CurrentRevision
{
get { return Revision.LastOrDefault(); }
}
public Appointment()
{
Revisions = new List<AppointmentRevision>();
}
}
How to apply ObjectCreationHandling.Replace to selected properties when deserializing JSON?
Turns out (I'm in 2019), you can set the list items in your constructor as you were doing in your question. I added the ObjectCreationHandling.Replace attribute above my declaration of the list, then serialising should replace anything stored in the list with the JSON.

Categories