'Json.NET: how to remove assembly info from types in the resulting json string?
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;
}
}
Solution 1:[1]
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.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |