2

I am currently working on an api wrapper of an third party XML api. I am hoping to use the same object and endpoint with web api to support both. (almost working)

The issue comes when working with the following type of object:


enum ItemsChoiceType
{
    Foo,
    Bar,
    Baz
}

...
        [XmlElement("Foo", typeof(string), Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
        [XmlElement("Bar", typeof(BarClass), Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
        [XmlElement("Baz", typeof(string), Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
        [XmlChoiceIdentifier("ItemsElementName")]
        public object[] Items
        {
            get => itemsField;
            set => itemsField = value;
        }

        /// <remarks/>
        [XmlElement("ItemsElementName")]
        [XmlIgnore()]
        public ItemsChoiceType[] ItemsElementName
        {
            get => itemsElementNameField;
            set => itemsElementNameField = value;
        }
...

When using the objects the arrays look like this:

obj.Items = new object[]{"This is Foo", "This is Baz", new BarClass()};
obj.ItemsElementName = new ItemsChoiceType[] {Foo, Baz, Bar};

The xml looks like this:

<root>
    <Foo>This is Foo</Foo>
    <Baz>This is Baz</Baz>
    <Bar>/*BarClass xml*/</Bar>
</root>

When converting to json the two arrays serialize fine but deserialization of the BarClass doesn't get the proper type of BarClass since its deserializing into an object[] and is a JObject. Reading through the newtonsoft.json documentation has been less than helpful to solve this problem (i have learned a great deal about some other things though).

TypeNameHandling wont work because of the security issues around it. The XML works because the Enum determines the type and name of the XML element in Items.

  • You could use a custom `JsonConverter` as described in [Json.Net Serialization of Type with Polymorphic Child Object](https://stackoverflow.com/q/29528648/3744182), [How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?](https://stackoverflow.com/q/8030538/3744182) and [Deserializing polymorphic json classes without type information using json.net](https://stackoverflow.com/q/19307752/3744182). – dbc May 17 '19 at 17:32
  • Or you could use [`TypeNameHandling`](https://www.newtonsoft.com/json/help/html/SerializeTypeNameHandling.htm) as described in [Json.net serialize/deserialize derived types?](https://stackoverflow.com/q/8513042/3744182) or [how to deserialize JSON into IEnumerable with Newtonsoft JSON.NET](https://stackoverflow.com/q/6348215/3744182) - but watch out for the security risks explained in [TypeNameHandling caution in Newtonsoft Json](https://stackoverflow.com/q/39565954/3744182). – dbc May 17 '19 at 17:33
  • Do those answer your question? – dbc May 17 '19 at 17:33
  • @dbc TypeNameHandling wont work. Public facing API and cant handle the security risks. Probably should have included that in the question that I had looked at that but was dismissed quickly – RubberChickenLeader May 17 '19 at 17:33
  • You could create a [Custom `SerializationBinder`](https://www.newtonsoft.com/json/help/html/SerializeSerializationBinder.htm) that permits only known types. Otherwise a custom `JsonConverter` is the way to go. There's no perfect equivalent of `XmlChoiceIdentifier` since Json.NET requires that the type be determinable from the object itself not the parent; see [here](https://stackoverflow.com/a/29531372/3744182) for an explanation. – dbc May 17 '19 at 17:37
  • @dbc issue is that my base is `object` so cant really add a property to it. The custom serialization binder may work. Would then have to figure out how to inject it into the .net core web api pipeline instead of using the default json serialization and settings. – RubberChickenLeader May 17 '19 at 17:46
  • Add it to the property `Items` itself: `[JsonProperty(ItemConverterType=typeof(SubTypeClassConverter))]`. – dbc May 17 '19 at 17:48
  • @dbc what im missing is how to figure out the type info from the enum array when only dealing with the items property. See question edit. – RubberChickenLeader May 17 '19 at 18:08
  • [Json.Net Serialization of Type with Polymorphic Child Object](https://stackoverflow.com/q/29528648/3744182) has a converter that includes an enum-to-type dictionary. Is that the direction you want to go in? – dbc May 17 '19 at 18:17
  • @dbc Serialization Callbacks (something I would have never thought to look for) solved the issue. I may try and make something generic later but solved. – RubberChickenLeader May 17 '19 at 19:13

1 Answers1

1

Well its not quite what I was looking for but I did solve this. I stumbled onto
Serialization Callbacks, specifically OnDeserialized where I can interrogate the ItemsElementName and see if an enum value exists and then convert the type.

It is nowhere near the generic I was hoping for, but it solves the issue at hand for this specific class.

...

        [OnDeserialized]
        internal void OnDeserializedMethod(StreamingContext context)
        {
            for (int i = 0; i < itemsElementNameField.Length; i++)
            {
                switch (itemsElementNameField[i])
                {
                    case ItemsChoiceType.Bar:
                        switch (Items[i])
                        {
                            case BarClass _:
                                return;
                            case JObject o:
                                Items[i] = new BarClass
                                               {
                                                   /*properties*/
                                               };
                                break;
                        }

                        break;
                        //All the other enum items we care about.
                }
            }
        }

...