Как сериализовать тип класса, но не пространство имен в строку Json, используя DataContractJsonSerializer

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

"__type":"ClassName:#Namespace"

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

"__type":"ClassName"

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

Есть ли способ сделать такое?

Ответ 1

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

Ответ 2

Эта страница описывает обстоятельства, при которых испускается свойство __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);

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

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

. __type передается в поток JSON методом (private) 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
}

Ответ 3

Ответ на cheeso был отличным. Я обнаружил уточнение для очистки поля __type, хотя:

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

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

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

public string __type

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

-Joey

Ответ 4

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

**

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

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

Ответ 5

@Cheeso писал (а):

Чтобы этого избежать, вам может потребоваться вернуть строку из 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] on SomeClass содержит не синтаксис (name=""), который я видел в другом месте. Это удаляет пространство имен из __type за счет необходимости ВСЕ вашего DataContract attrs, что загромождает ваш код. Вместо этого мой пост-процессор обрабатывает имя сборки в поле __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 всегда является первым в сериализованном объекте.

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

Ответ 6

Несколько раз назад я решил эту проблему. Я использую DataContractJsonSerializer У вас будет __type в json, если ваш метод для сериализации имеет параметр базового класса, но вы даете ему 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));
}

В первом тесте у вас есть

"{\" __ типа \ ": \" B: # ConsoleApplication1\ "\" S \ ":\" Vv \ ",\" \ "Возраст: 10}"

Во втором:

"{\" S\ ": \" Vv\ "\" Возраст \ ": 10}"