Using JSON.NET - I'm trying to serialize a mass collection of objects that can contain any other object, that can contain any number of arrays of other objects. Upon doing a serialize and deserialize, the data is not typing correctly/being destroyed. Hours of searches later, can't solve.
public class SubClass
{
public string theString;
}
public class MasterClass
{
public object theObj;
}
Sample code:
SubClass thesubclass = new SubClass(); thesubclass.theString = "TESTSTRING";
MasterClass theMaster = new MasterClass();
theMaster.theObj = thesubclass;
string jsonOut = JsonConvert.SerializeObject(theMaster, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All});
textBox1.Text = jsonOut;
//Out1: {"$type":"WindowsFormsApplication1.MasterClass, WindowsFormsApplication1","theObj":{"$type":"WindowsFormsApplication1.SubClass, WindowsFormsApplication1","theString":"TESTSTRING"}}
MasterClass testMaster = JsonConvert.DeserializeObject<MasterClass>(jsonOut);
string jsonOut2 = JsonConvert.SerializeObject(testMaster, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All});
textBox2.Text = jsonOut2;
//Out2: {"$type":"WindowsFormsApplication1.MasterClass, WindowsFormsApplication1","theObj":{"theString":"TESTSTRING"}}
Basically any object past the main object is losing it's type. //Out2 should match //Out1, but they never do. Help
You need to set the TypeNameHandling setting also when you deserialize:
MasterClass testMaster = JsonConvert.DeserializeObject<MasterClass>(jsonOut, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
Related
I have an API that returns a JSON object from a MongoDB in which one of the properties is an "open-ended" document, meaning it can be any valid JSON for that property. I don't know what the names of the properties are ahead of time, they can be any string. I only know that this particular property needs to be serialized exactly how it is stored in the database. So if the property name that was originally stored was "Someproperty", the serialized response in JSON needs to be "Someproperty", NOT "someProperty".
We have this configuration:
ContractResolver = new CamelCasePropertyNamesContractResolver();
in our CustomJsonSerializer, but it is messing with the formatting of the response when returning the "open ended" JSON. It is camel-casing all of these properties when in fact we want the response to be exactly how they are stored in MongoDB (BSON). I know the values are maintaining their case when storing/retrieving via the database, so that is not the issue.
How can I tell JSON.net to essentially bypass the CamelCasePropertyNameResolver for all of the child properties of a particular data point?
EDIT:
Just to give a bit more info, and share what I have already tried:
I thought about overriding the PropertyNameResolver like so:
protected override string ResolvePropertyName(string propertyName)
{
if (propertyName.ToLower().Equals("somedocument"))
{
return propertyName;
}
else return base.ResolvePropertyName(propertyName);
}
However, if I have a JSON structure like this:
{
"Name" : "MyObject",
"DateCreated" : "11/14/2016",
"SomeDocument" :
{
"MyFirstProperty" : "foo",
"mysecondPROPERTY" : "bar",
"another_random_subdoc" :
{
"evenmoredata" : "morestuff"
}
}
}
then I would need all of the properties any child properties' names to remain exactly as is. The above override I posted would (I believe) only ignore on an exact match to "somedocument", and would still camelcase all of the child properties.
What you can do is, for the property in question, create a custom JsonConverter that serializes the property value in question using a different JsonSerializer created with a different contract resolver, like so:
public class AlternateContractResolverConverter : JsonConverter
{
[ThreadStatic]
static Stack<Type> contractResolverTypeStack;
static Stack<Type> ContractResolverTypeStack { get { return contractResolverTypeStack = (contractResolverTypeStack ?? new Stack<Type>()); } }
readonly IContractResolver resolver;
JsonSerializerSettings ExtractAndOverrideSettings(JsonSerializer serializer)
{
var settings = serializer.ExtractSettings();
settings.ContractResolver = resolver;
settings.CheckAdditionalContent = false;
if (settings.PreserveReferencesHandling != PreserveReferencesHandling.None)
{
// Log an error throw an exception?
Debug.WriteLine(string.Format("PreserveReferencesHandling.{0} not supported", serializer.PreserveReferencesHandling));
}
return settings;
}
public AlternateContractResolverConverter(Type resolverType)
{
if (resolverType == null)
throw new ArgumentNullException("resolverType");
resolver = (IContractResolver)Activator.CreateInstance(resolverType);
if (resolver == null)
throw new ArgumentNullException(string.Format("Resolver type {0} not found", resolverType));
}
public override bool CanRead { get { return ContractResolverTypeStack.Count == 0 || ContractResolverTypeStack.Peek() != resolver.GetType(); } }
public override bool CanWrite { get { return ContractResolverTypeStack.Count == 0 || ContractResolverTypeStack.Peek() != resolver.GetType(); } }
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException("This contract resolver is intended to be applied directly with [JsonConverter(typeof(AlternateContractResolverConverter), typeof(SomeContractResolver))] or [JsonProperty(ItemConverterType = typeof(AlternateContractResolverConverter), ItemConverterParameters = ...)]");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
using (ContractResolverTypeStack.PushUsing(resolver.GetType()))
return JsonSerializer.CreateDefault(ExtractAndOverrideSettings(serializer)).Deserialize(reader, objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
using (ContractResolverTypeStack.PushUsing(resolver.GetType()))
JsonSerializer.CreateDefault(ExtractAndOverrideSettings(serializer)).Serialize(writer, value);
}
}
internal static class JsonSerializerExtensions
{
public static JsonSerializerSettings ExtractSettings(this JsonSerializer serializer)
{
// There is no built-in API to extract the settings from a JsonSerializer back into JsonSerializerSettings,
// so we have to fake it here.
if (serializer == null)
throw new ArgumentNullException("serializer");
var settings = new JsonSerializerSettings
{
CheckAdditionalContent = serializer.CheckAdditionalContent,
ConstructorHandling = serializer.ConstructorHandling,
ContractResolver = serializer.ContractResolver,
Converters = serializer.Converters,
Context = serializer.Context,
Culture = serializer.Culture,
DateFormatHandling = serializer.DateFormatHandling,
DateFormatString = serializer.DateFormatString,
DateParseHandling = serializer.DateParseHandling,
DateTimeZoneHandling = serializer.DateTimeZoneHandling,
DefaultValueHandling = serializer.DefaultValueHandling,
EqualityComparer = serializer.EqualityComparer,
// No Get access to the error event, so it cannot be copied.
// Error = += serializer.Error
FloatFormatHandling = serializer.FloatFormatHandling,
FloatParseHandling = serializer.FloatParseHandling,
Formatting = serializer.Formatting,
MaxDepth = serializer.MaxDepth,
MetadataPropertyHandling = serializer.MetadataPropertyHandling,
MissingMemberHandling = serializer.MissingMemberHandling,
NullValueHandling = serializer.NullValueHandling,
ObjectCreationHandling = serializer.ObjectCreationHandling,
ReferenceLoopHandling = serializer.ReferenceLoopHandling,
// Copying the reference resolver doesn't work in the default case, since the
// actual BidirectionalDictionary<string, object> mappings are held in the
// JsonSerializerInternalBase.
// See https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/DefaultReferenceResolver.cs
ReferenceResolverProvider = () => serializer.ReferenceResolver,
PreserveReferencesHandling = serializer.PreserveReferencesHandling,
StringEscapeHandling = serializer.StringEscapeHandling,
TraceWriter = serializer.TraceWriter,
TypeNameHandling = serializer.TypeNameHandling,
// Changes in Json.NET 10.0.1
//TypeNameAssemblyFormat was obsoleted and replaced with TypeNameAssemblyFormatHandling in Json.NET 10.0.1
//TypeNameAssemblyFormat = serializer.TypeNameAssemblyFormat,
TypeNameAssemblyFormatHandling = serializer.TypeNameAssemblyFormatHandling,
//Binder was obsoleted and replaced with SerializationBinder in Json.NET 10.0.1
//Binder = serializer.Binder,
SerializationBinder = serializer.SerializationBinder,
};
return settings;
}
}
public static class StackExtensions
{
public struct PushValue<T> : IDisposable
{
readonly Stack<T> stack;
public PushValue(T value, Stack<T> stack)
{
this.stack = stack;
stack.Push(value);
}
// By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
public void Dispose()
{
if (stack != null)
stack.Pop();
}
}
public static PushValue<T> PushUsing<T>(this Stack<T> stack, T value)
{
if (stack == null)
throw new ArgumentNullException();
return new PushValue<T>(value, stack);
}
}
Then use it like so:
public class RootObject
{
public string Name { get; set; }
public DateTime DateCreated { get; set; }
[JsonProperty(NamingStrategyType = typeof(DefaultNamingStrategy))]
[JsonConverter(typeof(AlternateContractResolverConverter), typeof(DefaultContractResolver))]
public SomeDocument SomeDocument { get; set; }
}
public class SomeDocument
{
public string MyFirstProperty { get; set; }
public string mysecondPROPERTY { get; set; }
public AnotherRandomSubdoc another_random_subdoc { get; set; }
}
public class AnotherRandomSubdoc
{
public string evenmoredata { get; set; }
public DateTime DateCreated { get; set; }
}
(Here I am assuming you want the "SomeDocument" property name to be serialized verbatim, even though it wasn't entirely clear from your question. To do that, I'm using JsonPropertyAttribute.NamingStrategyType from Json.NET 9.0.1. If you're using an earlier version, you'll need to set the property name explicitly.)
Then the resulting JSON will be:
{
"name": "Question 40597532",
"dateCreated": "2016-11-14T05:00:00Z",
"SomeDocument": {
"MyFirstProperty": "my first property",
"mysecondPROPERTY": "my second property",
"another_random_subdoc": {
"evenmoredata": "even more data",
"DateCreated": "2016-11-14T05:00:00Z"
}
}
}
Note that this solution does NOT work well with preserving object references. If you need them to work together, you may need to consider a stack-based approach similar to the one from Json.NET serialize by depth and attribute
Demo fiddle here.
Incidentally, have you considered storing this JSON as a raw string literal, as in the answer to this question?
I think you should look at this backwards.
Instead of trying to NOT touch the properties you don't know, let that be the default behavior and touch the ones you DO know.
In other words, don't use the CamelCasePropertyNamesContractResolver. Deal with the properties you know appropriately and let the other ones pass through transparently.
I'm trying to add some data into a Json file like this but I have some error :
using System.Text.Json;
void addToJson(int myData)
{
var data = JsonSerializer.Deserialize<scoreList>(File.ReadAllText("scoreData.json"));
data.scores.Add(myData);
var jsonString = JsonSerializer.Serialize<scoreList>(data);
File.WriteAllText("scoreData.json", jsonString);
}
I think my code is good, I just have an error at : data.scores.Add(myData);
And here's my class :
class scoreTest
{
public int score { get; set; }
}
class scoreList
{
public List<scoreTest> scores { get; set; }
}
There is nothing related to JSON, you should add the proper value to the list
void addToJson(int myData)
{
//...
data.scores.Add(new scoreTest { score = myData });
//...
}
scores has List<scoreTest> type, you can't simply add int value to the list, you should create a scoreTest instance first with score value set to myData
To handle nested class deserialization/serialization use:
new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
};
Try this:
void addToJson(int myData)
{
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
};
var data = JsonSerializer.Deserialize<scoreList>(File.ReadAllText("scoreData.json"), settings);
data.scores.Add(myData);
var jsonString = JsonSerializer.Serialize<scoreList>(data, settings);
File.WriteAllText("scoreData.json", jsonString);
}
It is OK, if all member's type are primitive, but if not.
You may need to constructor for a member.
This will helpful to avoid nullreference exception while deserialize.
class scoreList {
public List<scoreTest> scores { get; set; }
//We need default constructor for initialize class member.
public scoreList(){
this.scores = new List<scoreTest>();
}
}
Say i have a JRaw property where i place a Pascal cased JSON into.
public class C
{
public JRaw Prop {get;set;}
}
var a = new JRaw("{\"A\":42}");
var c = new C { Prop = a };
Now when i serialize this i can force c to be lowercase, like so:
var result = JsonConvert.SerializeObject(c, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
// i have "{\"c\":{\"A\":42}}"
However, is there any way to make JRaw also camelcased?
e.g. i want to get
// "{\"c\":{\"a\":42}}"
Here's a way to do it although it may not be the most efficient as it requires you to serialize your object, deserialize, then serialize it again. I believe the problem is the serializer is not detecting your "A" JSON property properly since it's a raw JSON string as opposed to an object property. This will convert your JRaw JSON string to a dynamic object (ExpandoObject) allowing the "A" JSON property to become an object property temporarily. This'll then be picked up by the serializer once the object is serialized, resulting in all nested property keys being camel case.
using System;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System.Dynamic;
public class Program
{
public class C
{
public JRaw Prop { get; set; }
}
public static void Main()
{
var a = new JRaw("{\"A\":42}");
var c = new C { Prop = a };
var camelCaseSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
var result = JsonConvert.SerializeObject(c, camelCaseSettings);
var interimObject = JsonConvert.DeserializeObject<ExpandoObject>(result);
result = JsonConvert.SerializeObject(interimObject, camelCaseSettings);
Console.WriteLine(result);
Console.ReadKey();
}
}
Output: {"prop":{"a":42}}
I am serializing using Json.NET, but the resulting string ends up way too long, because it includes a ton of surplus info about the assembly that I have no use for.
For example, here is what I'm getting for one of the types:
"Assets.Logic.CompGroundType, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null": {
"$type": "Assets.Logic.CompGroundType, Assembly-CSharp",
"GroundType": 1,
"EntityID": 1,
"<GroundType>k__BackingField": 1
}
"GroundType" is an enum, and "EntityID" is int.
Here is my desired result:
"Assets.Logic.CompGroundType" : {
"$type": "Assets.Logic.CompGroundType",
"GroundType": 1,
"EntityID": 1,
"<GroundType>k__BackingField": 1
}
If it's possible, I would also like to remove the "$type" field while still correctly deserializing inherited types (I'm not sure why it's necessary, since that info is duplicated from one line above, but if I remove it by setting TypeNameHandling.None, I get deserialization errors for child types). I am also not sure what the last field (k__BackingField) is for.
If it's possible, I would want to reduce it even further, to:
"Assets.Logic.CompGroundType" : {
"GroundType": 1,
"EntityID": 1,
}
I understand it's possible to manually customize the serialization scheme for each type in Json.Net, but I have hundreds of types, and so I would like to do it automatically through some global setting.
I tried changing "FormatterAssemblyStyle", but there is no option for "None" there, only "Simple" or "Full", and I'm already using "Simple".
Thanks in advance for any help.
Edit:
It's important to note that the types are keys in a dictionary. That's why the type appears twice (in the first and second row of the first example).
After implementing a custom SerializationBinder, I was able to reduce the length of the "$type" field, but not the Dictionary key field. Now I get the following:
"componentDict": {
"Assets.Logic.CompGroundType, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null": {
"$type": "Assets.Logic.CompGroundType",
"GroundType": 1,
"EntityID": 1,
"<GroundType>k__BackingField": 1
}
}
Edit 2:
The code I'm trying to serialize is an entity component system. I'll try to provide a detailed example with code samples.
All components (including CompGroundType above) inherit from the following abstract class:
abstract class Component
{
public int EntityID { get; private set; }
protected Component(int EntityID)
{
this.EntityID = EntityID;
}
}
The issue I'm encountering is in the serialization of the Entity class' componentDict:
class Entity
{
readonly public int id;
private Dictionary<Type, Component> componentDict = new Dictionary<Type, Component>();
[JsonConstructor]
private Entity(Dictionary<Type, Component> componentDict, int id)
{
this.id = id;
this.componentDict = componentDict;
}
}
componentDict contains all the components attached to the entity. In each entry <Type, Component>, the type of the value is equal to the key.
I am doing the serialization recursively, using the following JsonSerializerSettings:
JsonSerializerSettings serializerSettings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
ContractResolver = new MyContractResolver(),
TypeNameHandling = TypeNameHandling.Auto,
SerializationBinder = new TypesWithNoAssmeblyInfoBinder(),
Formatting = Formatting.Indented
}
Where MyContractResolver is identical to the one form this answer.
TypesWithNoAssmeblyInfoBinder does the change from edit 1:
private class TypesWithNoAssmeblyInfoBinder : ISerializationBinder
{
public Type BindToType(string assemblyName, string typeName)
{
return Type.GetType(typeName);
}
public void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
assemblyName = null;
typeName = serializedType.FullName;
}
}
The serialization itself is done as follows:
var jsonSerializer = JsonSerializer.Create(serializerSettings);
using (FileStream zippedFile = new FileStream(Application.persistentDataPath + fileName, FileMode.Create))
{
using (GZipStream archive = new GZipStream(zippedFile, CompressionLevel.Fastest))
{
using (StreamWriter sw = new StreamWriter(archive))
{
jsonSerializer.Serialize(sw, savedData);
}
}
}
Edit 4:
The CompGroundType class (an example of a finished component):
class CompGroundType : Component
{
public enum Type {Grass, Rock};
public Type GroundType { get; private set; }
[JsonConstructor]
private CompGroundType(Type groundType, int entityID) : base(entityID)
{
this.GroundType = groundType;
}
}
The first part is the embedded $type information which is being injected by json.net to help with deserialization later. I think this example from the documentation will do what you want.
public class KnownTypesBinder : ISerializationBinder
{
public IList<Type> KnownTypes { get; set; }
public Type BindToType(string assemblyName, string typeName)
{
return KnownTypes.SingleOrDefault(t => t.Name == typeName);
}
public void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
assemblyName = null;
typeName = serializedType.Name;
}
}
public class Car
{
public string Maker { get; set; }
public string Model { get; set; }
}
KnownTypesBinder knownTypesBinder = new KnownTypesBinder
{
KnownTypes = new List<Type> { typeof(Car) }
};
Car car = new Car
{
Maker = "Ford",
Model = "Explorer"
};
string json = JsonConvert.SerializeObject(car, Formatting.Indented, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects,
SerializationBinder = knownTypesBinder
});
Console.WriteLine(json);
// {
// "$type": "Car",
// "Maker": "Ford",
// "Model": "Explorer"
// }
object newValue = JsonConvert.DeserializeObject(json, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects,
SerializationBinder = knownTypesBinder
});
Console.WriteLine(newValue.GetType().Name);
// Car
It just needs tweaking to your particular needs.
Then the second part is dictionary key, which is coming from Type objects being serialized.
I thought you can customize that by creating a custom JsonConverter, but it turns out that dictionary keys have "special" handling, meaning a more involved workaround. I don't have an example of the more involved workaround sorry.
I'm using Newtonsoft.Json.JsonConvert to serialize a Textbox (WinForms) into json and I want the serialization to skip properties with default values or empty arrays.
I'v tried to use NullValueHandling = NullValueHandling.Ignore in JsonSerializerSettings but is doesn't seem to affect anything.
Here is the full code sample (simplified):
JsonSerializerSettings settings = new JsonSerializerSettings()
{
Formatting = Formatting.None,
DefaultValueHandling = DefaultValueHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
ObjectCreationHandling = ObjectCreationHandling.Replace,
PreserveReferencesHandling = PreserveReferencesHandling.None,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
};
string json = JsonConvert.SerializeObject(textbox, settings);
Any ideas ?
You could use the standard conditional-serialization pattern:
private int bar = 6; // default value of 6
public int Bar { get { return bar;} set { bar = value;}}
public bool ShouldSerializeBar()
{
return Bar != 6;
}
The key is a public bool ShouldSerialize*() method, where * is the member-name. This pattern is also used by XmlSerializer, protobuf-net, PropertyDescriptor, etc.
This does, of course, mean you need access to the type.