How to serialize class type but not the namespace to a Json string using DataContractJsonSerializer
Asked Answered
T

6

13

I'm trying to serialize a class hierarchy to a Json string using DataContractJsonSerializer, in a WCF service. the default behaviour for serializing a derived class is to add the following key value pair to the object:

"__type":"ClassName:#Namespace"

My problem is that namespaces are long and they bloat the Json string. I would like to somehow intervene with the serialization and output this instead:

"__type":"ClassName"

and on deserialization intervene again to point to the correct namespace (which i know in runtime).

Is there any way to do such a thing?

Triumphant answered 13/4, 2011 at 8:30 Comment(2)
Sending without the namespace seems awfully hacky, though.I'd either just leave it out altogether, or not at all...Its
@Its it kinda depends on your priorities... if for example you're communicating with an extremely low bandwidth device you have to make some tradeoffs...Triumphant
T
8

Adding the namespace parameter to the data contract does the trick. [DataContract(Namespace = "")]

Triumphant answered 3/5, 2011 at 7:53 Comment(1)
This suppressed the namespace part of the attribute, but not the colon that trails the classname. So, +1 for some savings, but still not a complete solution.Slumberous
H
13

This page describes the circumstances under which the __type property is emitted. In short, in WCF, if you use a derived type, and a KnownTypeAttribute, then you're going to get a __type property.

Example:

Assume

[DataContract]
[KnownType(typeof(Subscriber))]
public class Person { ... }

[DataContract]
public class Subscriber : Person { ... } 

This code generates a __type property:

    var o = new Subscriber("Fleming");
    var serializer = new DataContractJsonSerializer(typeof(Person));
    serializer.WriteObject(Console.OpenStandardOutput(), o);

But this code does not:

    var o = new Subscriber("Fleming");
    var serializer = new DataContractJsonSerializer(typeof(Subscriber));
    serializer.WriteObject(Console.OpenStandardOutput(), o);

Notice that the second snip uses a DCJS with the same type as the object being serialized.

To avoid the __type, don't use derived types, or to be precise, use a serializer typed to the type you are actually serializing. If the serialization is being performed implicitly by a WCF method, then the method must be typed appropriately. In my example, it means you must use a return type of "Subscriber", and not the parent type, "Person".

The __type is emitted into the JSON stream by the (private) WriteServerTypeAttribute method on the (internal) System.Runtime.Serialization.Json.XmlJsonWriter class. There is no public, documented, supported way to modify that, as far as I can tell.

To avoid this, you'd maybe need to return a string from the WCF method, perform the serialization yourself, and post-process the emitted JSON.


If you don't mind the __type thing, but just want to remove the qualifying namespace from the value, then put your types in the global namespace. In other words, put them outside of any namespace declaration in code.

Example: When the data types reside in a namespace, and when I used a derived type, the serialized JSON looks like this:

{
  "__type":"Subscriber:#My.Custom.Namespace",
  "Index":604455,
  "Name":"Fleming",
  "Id":580540
}

When the data types reside in the global namespace, it looks like this:

{
  "__type":"Subscriber:#",
  "Index":708759,
  "Name":"Fleming",
  "Id":675323
}
Heathenry answered 13/4, 2011 at 14:42 Comment(0)
T
8

Adding the namespace parameter to the data contract does the trick. [DataContract(Namespace = "")]

Triumphant answered 3/5, 2011 at 7:53 Comment(1)
This suppressed the namespace part of the attribute, but not the colon that trails the classname. So, +1 for some savings, but still not a complete solution.Slumberous
R
6

Cheeso's answer was excellent. I did discover a refinement to cleaning up the __type field though:

Rather than removing your subclass from its namespace you can add a property like the following:

[DataMember(Name = "__type")]
public string SubclassType
{
    get
    {
        return "Subscriber";
    }
    set { }
}

You still get stuck with the ugly name "__type" but I found that because I was returning a list of subtypes I wanted to specify the type name anyway. You could even return a value of "" to further reduce response size. You could also just declare the property as:

public string __type

but I found that to accentuate the hack so I stuck with an appropriate property name and then renamed it.

-Joey

Raisaraise answered 28/8, 2011 at 21:31 Comment(1)
I found that this solution requires the "__type" field to be the first element in the JSON serialization. This breaks with the JSON specification, but more importantly, I now have to find a way to order the fields in the serialization.Beastings
M
0

Note: I typed up this answer below and later realized that DataContractResolver is currently not supported with DataContractJsonSerializer. It may soon be with the next release of the framework, however. This is also useful if you are looking at more than just JSON.

**

You can do this with a DataContractResolver, which lets you map types to xsi:type (__type) information and vice-versa in a custom manner.

To do this, check out this blog post on DataContractResolver, plus this conceptual topic, plus this sample

Marquita answered 16/4, 2011 at 5:5 Comment(0)
S
0

@Cheeso wrote:

To avoid this, you'd maybe need to return a string from the WCF method, perform the serialization yourself, and post-process the emitted JSON.

Here's how I implemented that post-processing. I thought I'd post it here JIC it might help someone else.

First some boilerplate to show how I generate my JSON string:

// Instantiate & populate the object to be serialized to JSON
SomeClass xyz = new SomeClass();
... populate it ...

// Now serialize it
DataContractJsonSerializer ser = new DataContractJsonSerializer(xyz.GetType()); // Note xyz.GetType()
... serialize the object to json, many steps omitted here for brevity ...
string json = sr.ReadToEnd();

(Serialization is based on examples from https://msdn.microsoft.com/en-us/library/bb412179%28v=vs.110%29.aspx )

Note that the [DataContract] on SomeClass does not include the (name="") syntax that I've seen suggested elsewhere. That only removes the namespace from the __type at the cost of needing to adorn ALL your DataContract attrs, which clutters your code. Instead, my post-processor handles the assembly name in the __type field.

And now the string json contains the JSON text, but unfortunately includes all that "__type" junk that you don't want but can't suppress.

So here's my post-processing code that removes it:

// This strips out that unsuppressable __type clutter generated by the KnownType attributes
Attribute[] attrs = Attribute.GetCustomAttributes(xyz.GetType());
foreach (Attribute attr in attrs)
{
    if (attr is KnownTypeAttribute)
    {
        KnownTypeAttribute a = (KnownTypeAttribute)attr;
        string find = "\"__type\":\"" + a.Type.ReflectedType.Name + "." + a.Type.Name + ":#" + a.Type.Namespace + "\",";
        json = json.Replace(find, "");
    }
}

This makes a few assumptions, most notably that the __type field ends with a comma, which assumes that another field follows it, though (a) my objects always have at least 1 field and (b) I've found that the __type field is always 1st in the serialized object's output.

As always, you may have to adjust something to your situation, but I find it works well for mine.

Slumberous answered 6/3, 2015 at 14:21 Comment(1)
Note that you need to call it recursively if you have more than one level of custom objects in the full file. In the end, I just stripped it with a regex: "\"__type\"\\s*:\\s*\"[^\"]+\"\\s*,?\\s*"Its
V
0

Some times ago i decided this problem. I use DataContractJsonSerializer You will have __type in json, if your method for serialization have Base class parameter, but you give it subClass as parameter. More details:

[DataContract]
[KnownType(typeof(B))]
public abstract class A
{
    [DataMember]
    public String S { get; set; }
}

[DataContract]
public class B : A
{
    [DataMember]
    public Int32 Age { get; set; }
}

public static String ToJson<T>(this T value)
{
    var serializer = new DataContractJsonSerializer(typeof(T));
    using (var stream = new MemoryStream())
    {
        serializer.WriteObject(stream, value);
        return Encoding.UTF8.GetString(stream.ToArray());
    }
}

You have two methods for test:

public static void ReadTypeDerived(A type)
{
    Console.WriteLine(ToJson(type));
}

and

public static void ReadType<T>(T type)
{
    Console.WriteLine(ToJson(type));
}

In first test you wiil have

"{\"__type\":\"B:#ConsoleApplication1\",\"S\":\"Vv\",\"Age\":10}"

In second:

"{\"S\":\"Vv\",\"Age\":10}"

Volcanism answered 7/5, 2015 at 22:27 Comment(1)
What if the problem is subtypes used inside that object, though?Its

© 2022 - 2024 — McMap. All rights reserved.