32

When using Json.Net, I understand how to get the $type property into the rendered json, but is there a way to change that field name? I need to use "__type" instead of "$type".

Brian McCord
  • 4,943
  • 7
  • 32
  • 44

8 Answers8

20

http://json.codeplex.com/workitem/22429

"I would rather keep $type hard coded and consistent."

Consistent with what I wonder?

http://json.codeplex.com/workitem/21989

I would rather not - I think this is too specific to me and I don't want to go overboard with settings. At some point I will probably implement this - http://json.codeplex.com/workitem/21856 - allowing people to read/write there own meta properties in the JSON and you could reimplement type name handling with a new property name. The other option is just to modify the source code for yourself to have that property name.

And more recently, Issue #36: Customizable $type property name feature:

I'd rather not

This is my solution...

json.Replace("\"$type\": \"", "\"type\": \"");
dbc
  • 104,963
  • 20
  • 228
  • 340
Ian Warburton
  • 15,170
  • 23
  • 107
  • 189
  • 1
    Your solution is awesome! ^_^ – Hendy Irawan Jan 09 '15 at 04:03
  • And still more recently (JamesNK closed this Jun 11, 2017): [Be able to support custom inheritance discriminator field #1331](https://github.com/JamesNK/Newtonsoft.Json/issues/1331). – dbc Jun 11 '21 at 14:02
17

Looks like this is hardcoded as public const string TypePropertyName = "$type"; in Newtonsoft.Json.Serialization.JsonTypeReflector which is internal static class unfortunately.

I needed this myself, and the only thing I can think of is having custom modified version of json.net itself. Which is of course is a major pita.

driushkin
  • 3,531
  • 1
  • 24
  • 25
  • 4
    Unfortunately, I have pretty much had to completely abandon the path I was headed down because of how hard Json.Net is to use in many cases. This is just one example. – Brian McCord Feb 29 '12 at 19:24
  • I am myself looking of how I on the server-side, that is written i C# and uses Json.NET, can deserialize JSON sent from an Android app using Jackson. In Jackson I can setup to use "$type", but it doesnt work anyway. – Ted Jan 06 '13 at 22:59
8

I had to do this for my UI REST API as Angular.js disregards fields names starting with a dollar sign ($).

So here's a solution that renames $type to __type for the whole Web API and works both for serialization and deserialization.

In order to be able to use a custom JsonWriter and a custom JsonReader (as proposed in the other answers to this question), we have to inherit the JsonMediaTypeFormatter and override the corresponding methods:

internal class CustomJsonNetFormatter : JsonMediaTypeFormatter
{
    public override JsonReader CreateJsonReader(Type type, Stream readStream, Encoding effectiveEncoding)
    {
        return new CustomJsonReader(readStream, effectiveEncoding);
    }

    public override JsonWriter CreateJsonWriter(Type type, Stream writeStream, Encoding effectiveEncoding)
    {
        return new CustomJsonWriter(writeStream, effectiveEncoding);
    }

    private class CustomJsonWriter : JsonTextWriter
    {
        public CustomJsonWriter(Stream writeStream, Encoding effectiveEncoding)
            : base(new StreamWriter(writeStream, effectiveEncoding))
        {
        }

        public override void WritePropertyName(string name, bool escape)
        {
            if (name == "$type") name = "__type";
            base.WritePropertyName(name, escape);
        }
    }

    private class CustomJsonReader : JsonTextReader
    {
        public CustomJsonReader(Stream readStream, Encoding effectiveEncoding)
            : base(new StreamReader(readStream, effectiveEncoding))
        {
        }

        public override bool Read()
        {
            var hasToken = base.Read();
            if (hasToken && TokenType == JsonToken.PropertyName && Value != null && Value.Equals("__type"))
            {
                SetToken(JsonToken.PropertyName, "$type");
            }
            return hasToken;
        }
    }
}

Of course you need to register the custom formatter in your WebApiConfig. So we replace the default Json.NET formatter with our custom one:

config.Formatters.Remove(config.Formatters.JsonFormatter);
config.Formatters.Add(new CustomJsonNetFormatter());

Done.

Darrel K.
  • 1,611
  • 18
  • 28
Dejan
  • 9,150
  • 8
  • 69
  • 117
  • I like this solution! With a small customization I no longer have to use the lengthy types names _Namespace.Type, AssemblyName_ but can also rewrite this ;-) – t3chb0t Jun 30 '18 at 07:44
7

when serializing, there is a nice way to override the property name:

public class CustomJsonWriter : JsonTextWriter
{
    public CustomJsonWriter(TextWriter writer) : base(writer)
    {
    }

    public override void WritePropertyName(string name, bool escape)
    {
        if (name == "$type") name = "__type";
        base.WritePropertyName(name, escape);
    }
}

var serializer = new JsonSerializer();
var writer = new StreamWriter(stream) { AutoFlush = true };
serializer.Serialize(new CustomJsonWriter(writer), objectToSerialize);

I haven't tried deserialization yet, but in worst case I could use:

json.Replace("\"__type": \"", "\"type\": \"$type\");
Liero
  • 25,216
  • 29
  • 151
  • 297
  • Do you have a similar solution for the reader? I am trying to deserialise objects, but the $types in my Json are pretty uggly/complicated and would like to rename them. Thanks :-) – toughQuestions Sep 11 '20 at 14:09
4

We had a need for this so I created a custom JsonReader. We're are using rest in our MS web services with complex data models and needed to replace the "__type" property with "$type."

class MSJsonReader : JsonTextReader
{
    public MSJsonTextReader(TextReader reader) : base(reader) { }

    public override bool Read()
    {
        var hasToken = base.Read();

        if (hasToken && base.TokenType == JsonToken.PropertyName && base.Value != null && base.Value.Equals("__type"))
            base.SetToken(JsonToken.PropertyName, "$type");

        return hasToken;
    }
}

Here is how we use it.

using(JsonReader jr = new MSJsonTextReader(sr))
{
    JsonSerializer s = new JsonSerializer();
    s.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;
    s.NullValueHandling = NullValueHandling.Ignore;
    s.TypeNameHandling = TypeNameHandling.Auto; // Important!
    s.Binder = new MSRestToJsonDotNetSerializationBinder("Server.DataModelsNamespace", "Client.GeneratedModelsNamespace");

    T deserialized = s.Deserialize<T>(jr);

    return deserialized;
}

Here is our MSRestToJsonDotNetSerializationBinder that completes the compatibility between MS rest and Json.Net.

class MSRestToJsonDotNetSerializationBinder : System.Runtime.Serialization.SerializationBinder
{
    public string ServiceNamespace { get; set; }
    public string LocalNamespace { get; set; }

    public MSRestToJsonDotNetSerializationBinder(string serviceNamespace, string localNamespace)
    {
        if (serviceNamespace.EndsWith("."))
            serviceNamespace = serviceNamespace.Substring(0, -1);

        if(localNamespace.EndsWith("."))
            localNamespace = localNamespace.Substring(0, -1);

        ServiceNamespace = serviceNamespace;
        LocalNamespace = localNamespace;
    }

    public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
    {
        assemblyName = null;
        typeName = string.Format("{0}:#{1}", serializedType.Name, ServiceNamespace); // MS format
    }

    public override Type BindToType(string assemblyName, string typeName)
    {
        string jsonDotNetType = string.Format("{0}.{1}", LocalNamespace, typeName.Substring(0, typeName.IndexOf(":#")));
        return Type.GetType(jsonDotNetType);
    }
}
3

You could also do it this way:

[JsonConverter(typeof(JsonSubtypes), "ClassName")]
public class Annimal
{
    public virtual string ClassName { get; }
    public string Color { get; set; }
}

You will need the JsonSubtypes converter that is not part of Newtonsoft.Json project.

manuc66
  • 2,701
  • 29
  • 28
1

There is another option that allows to serialize custom type property name in Json.NET. The idea is do not write default $type property, but introduce type name as property of class itself.

Suppose we have a Location class:

public class Location
{
    public double Latitude { get; set; }

    public double Longitude { get; set; }
}

First, we need to introduce type property name and modify the class as demonstrated below:

public class Location
{
    [JsonProperty("__type")]
    public string EntityTypeName
    {

        get
        {
            var typeName = string.Format("{0}, {1}", GetType().FullName, GetType().Namespace);
            return typeName;
        }
    }

    public double Latitude { get; set; }

    public double Longitude { get; set; }
}

Then, set JsonSerializerSettings.TypeNameHandling to TypeNameHandling.None in order the deserializer to skip the rendering of default $type attribute.

That's it.

Example

var point = new Location() { Latitude = 51.5033630, Longitude = -0.1276250 };

var jsonLocation = JsonConvert.SerializeObject(point, new JsonSerializerSettings
{
     TypeNameHandling = TypeNameHandling.None,   //do not write type property(!)
});
Console.WriteLine(jsonLocation);

Result

{"__type":"Namespace.Location, Namespace","Latitude":51.503363,"Longitude":-0.127625}
Vadim Gremyachev
  • 57,952
  • 20
  • 129
  • 193
  • I don't see how deserializing will determine the correct type with your solution. Can you explain? – Dio F May 31 '15 at 15:06
  • To deserialize, try `JsonCreationConverter`, e.g. see http://stackoverflow.com/questions/8030538 – xmedeko Mar 04 '16 at 08:58
1

Using a custom converter should get the job done.

public CustomConverter : JsonConverter
{
    public override bool CanWrite => true;
    public override bool CanRead => true;

    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)
    {
        var jOjbect = (JObject)JToken.FromObject(value);
        jOjbect.Add(new JProperty("type", value.GetType().Name));
        jOjbect.WriteTo(writer);
    }
}
Thierry Prost
  • 1,005
  • 11
  • 22