Как сериализовать тип класса, но не пространство имен, в строку Json, используя DataContractJsonSerializer - PullRequest
11 голосов
/ 13 апреля 2011

Я пытаюсь сериализовать иерархию классов в строку Json, используя DataContractJsonSerializer, в службе WCF.Поведение по умолчанию для сериализации производного класса заключается в добавлении к объекту следующей пары значений ключа:

"__type":"ClassName:#Namespace"

Моя проблема в том, что пространства имен длинные и они раздувают строку Json.Я хотел бы как-то вмешаться в сериализацию и вывести это вместо этого:

"__type":"ClassName"

и при десериализации вмешаться снова, чтобы указать на правильное пространство имен (которое я знаю во время выполнения).

Есть ли способ сделать такую ​​вещь?

Ответы [ 6 ]

12 голосов
/ 13 апреля 2011

Эта страница описывает обстоятельства, при которых испускается свойство __type.Короче говоря, в WCF, если вы используете производный тип и KnownTypeAttribute, то вы получите свойство __type.

Пример:

Предположим,

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

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

Этот код генерирует свойство __type:

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

Но этот код не:

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

Обратите внимание, что второй фрагмент использует DCJS с тем же типом, что и сериализуемый объект.

Чтобы избежать использования __type, не используйте производные типы или, если быть точным, используйте типизированный сериализаторк типу, который вы на самом деле сериализуете.Если сериализация выполняется неявно с помощью метода WCF, то метод должен быть напечатан соответствующим образом.В моем примере это означает, что вы должны использовать возвращаемый тип «Подписчик», а не родительский тип «Персона».

__type отправляется в поток JSON (частным) методом WriteServerTypeAttribute (внутреннего) класса System.Runtime.Serialization.Json.XmlJsonWriter.Насколько я могу судить, публичного, документированного и поддерживаемого способа его изменить не существует.

Чтобы избежать этого, вам, возможно, потребуется вернуть строку из метода WCF, выполнить сериализацию самостоятельно и постобработать выданный JSON.


Если вы не возражаете против __type, но просто хотите удалить подходящее пространство имен из значения, тогда поместите ваши типы в глобальное пространство имен.Другими словами, поместите их вне любого объявления namespace в коде.

Пример: когда типы данных находятся в пространстве имен и когда я использовал производный тип, сериализованный JSON выглядит следующим образом:

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

Когда типы данных находятся в глобальном пространстве имен, это выглядит так:

{
  "__type":"Subscriber:#",
  "Index":708759,
  "Name":"Fleming",
  "Id":675323
}
8 голосов
/ 03 мая 2011

Добавление параметра пространства имен в контракт данных делает свое дело. [DataContract(Namespace = "")]

6 голосов
/ 29 августа 2011

Ответ Чизо был превосходным.Я обнаружил, что есть возможность улучшить поле __type:

Вместо того, чтобы удалять свой подкласс из его пространства имен, вы можете добавить свойство, подобное следующему:

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

Вы все еще застрялибезобразное имя "__type", но я обнаружил, что, поскольку я возвращал список подтипов, я все равно хотел указать имя типа.Вы можете даже вернуть значение "", чтобы еще больше уменьшить размер ответа.Вы также можете просто объявить свойство как:

public string __type

, но я обнаружил, что для усиления хака, поэтому я выбрал подходящее имя свойства, а затем переименовал его.

0 голосов
/ 08 мая 2015

Несколько раз назад я решил эту проблему.Я использую DataContractJsonSerializer У вас будет __type в json, если ваш метод для сериализации имеет параметр Base class, но вы задаете ему subClass в качестве параметра.Подробнее:

[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());
    }
}

У вас есть два метода теста:

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

и

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

В первом тесте у вас будет

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

Во втором:

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

0 голосов
/ 06 марта 2015

@ Cheeso wrote:

Чтобы избежать этого, вам, возможно, потребуется вернуть строку из метода WCF, выполнить сериализацию самостоятельно и постобработать выданный JSON.

Вот как я реализовал эту постобработку.Я решил опубликовать его здесь, JIC, это может помочь кому-то еще.

Сначала несколько шаблонов, чтобы показать, как я генерирую мою строку JSON:

// 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();

(Сериализация основана на примерах из https://msdn.microsoft.com/en-us/library/bb412179%28v=vs.110%29.aspx)

Обратите внимание, что [DataContract] на SomeClass включает , а не включает в себя синтаксис (name=""), который я видел в других местах.Это только удаляет пространство имен из __type за счет необходимости украшать ALL ваши атрибуты DataContract, что загромождает ваш код.Вместо этого мой постпроцессор обрабатывает имя сборки в поле __type.

И теперь строка json содержит текст JSON, но, к сожалению, включает в себя весь этот "__type" мусор, который вы не хотите, но можетене подавлять.

Так вот мой код постобработки, который удаляет его:

// 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, "");
    }
}

Это делает несколько предположений, в частности, что поле __type заканчивается запятой, что предполагает, чтоза ним следует другое поле, хотя (а) мои объекты всегда имеют по крайней мере 1 поле и (б) я обнаружил, что поле __type всегда 1-е в выходных данных сериализованного объекта.

Как всегда, вам, возможно, придется что-то приспособить к вашей ситуации, но я считаю, что это хорошо работает для моей.

0 голосов
/ 16 апреля 2011

Примечание: я напечатал этот ответ ниже и позже понял, что DataContractResolver в настоящее время не поддерживается с DataContractJsonSerializer.Однако это может произойти в ближайшее время.Это также полезно, если вы смотрите не только на JSON.

**

Это можно сделать с помощью DataContractResolver, который позволяет сопоставлять типы с информацией xsi: type (__type) ии наоборот.

Чтобы сделать это, прочитайте этот пост в блоге на DataContractResolver , плюс эту концептуальную тему , плюс этот пример

...