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

后端 未结 6 1560
太阳男子
太阳男子 2021-02-05 21:17

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 clas

相关标签:
6条回答
  • 2021-02-05 21:30

    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}"

    0 讨论(0)
  • 2021-02-05 21:31

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

    0 讨论(0)
  • 2021-02-05 21:31

    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

    0 讨论(0)
  • 2021-02-05 21:34

    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
    }
    
    0 讨论(0)
  • 2021-02-05 21:36

    @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.

    0 讨论(0)
  • 2021-02-05 21:43

    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

    0 讨论(0)
提交回复
热议问题