0

We had a legacy WCF product which serializing POCO using XML Serialization attributes.

Here is a sample of the List, with XmlArray/XmlArrayItem attributes. Bit like [How do I Set XmlArrayItem Element name for a List<Custom> implementation?

[XmlArray("Things", Order = 4), XmlArrayItem("Thing")]
public List<Thing> Things { get; set; }

This produces XML response:

<m:Things>
  <m:Thing>
  ...
  </m:Thing>
  <m:Thing>
  ...
  </m:Thing>
  <m:Thing>
  ...
  </m:Thing>
</m:Things>

And also (via a external translation) converts to this Json response (plural named object with an inner array (singular named) Json response):

"Things": {
"Thing": [
    {
        ...
    },
    {
        ...
    }
]}

Now, we have a .NET Core 2.1 app with no XML output - only Json. So I want to serialize the List into this Json response so that I don't break any clients that are expecting this response.

I realize I could write a wrapper around the List but I have about 40 of these Lists to do, and calling code will get very murky. I fact if I do a Paste Special > Paste JSON as classes, it looks like:

  class Model
  {
     public ThingsWrapper Things { get; set; }
  }

    public class ThingsWrapper
    {
        public Thing[] Thing { get; set; }
    }

    public class Thing
    {
...
    }

called with var x = Things.Thing; // (yuck)

based on https://dotnetfiddle.net/vUQKV1 - I have tried this JsonConverter. However it comes out as this, which is around the other way. I need things as a object and thing as an array.

{
  "things": [
    {
      "thing": {
        "Id": 1
      }
    },
    {
      "thing": {
        "Id": 2
      }
    }
  ]
}
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

public class Program
{
    public static void Main()
    {
        var foo = new Foo();
        foo.Things = new List<Foo.Thing>{new Foo.Thing{Id = 1}, new Foo.Thing{Id = 2}};
        Console.WriteLine(JsonConvert.SerializeObject(foo, Formatting.Indented));
    }
}

class Foo
{
    [JsonProperty("things")]
    [JsonConverter(typeof(CustomArrayConverter<Thing>), "thing")]
    public List<Thing> Things
    {
        get;
        set;
    }

    public class Thing
    {
        public int Id
        {
            get;
            set;
        }
    }
}

class CustomArrayConverter<T> : JsonConverter
{
    string PropertyName
    {
        get;
        set;
    }

    public CustomArrayConverter(string propertyName)
    {
        PropertyName = propertyName;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JArray array = new JArray(JArray.Load(reader).Select(jo => jo[PropertyName]));
        return array.ToObject(objectType, serializer);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        IEnumerable<T> items = (IEnumerable<T>)value;
        JArray array = new JArray(items.Select(i => new JObject(new JProperty(PropertyName, JToken.FromObject(i, serializer)))));
        array.WriteTo(writer);
    }

    public override bool CanConvert(Type objectType)
    {
        // CanConvert is not called when the [JsonConverter] attribute is used
        return false;
    }
}
Andrew Roberts
  • 990
  • 2
  • 12
  • 26
  • You can use a custom `JsonConverter` -- although `CustomCreationConverter` will not work. The problem is that the name of the inner property `"Thing"` will differ for each list property, right? It's currently controlled by `[XmlArrayItem(string name)]` but the name passed to the constructor will differ from property to property, how do you intend to retain that name? Are you going to keep decorating your properties with `XmlArrayItem` attributes? – dbc Dec 17 '19 at 17:09
  • The Thing inner property will be only set once (It is an array of Thing). See code sample 2 above. It is not how I would have chosen to output the json.. but it is just what I have to work with. Any examples of how to use a JsonConverter? – Andrew Roberts Dec 17 '19 at 21:41

1 Answers1

2

Thanks to @Brian on Name array elements in Json.NET? https://dotnetfiddle.net/vUQKV1, which pointed me in the right direction.

class CustomArrayConverter<T> : JsonConverter
    {
        string PropertyName { get; set; }

        public CustomArrayConverter(string propertyName)
        {
            PropertyName = propertyName;
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JArray array = new JArray(JArray.Load(reader).Select(jo => jo[PropertyName]));
            return array.ToObject(objectType, serializer);
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            IEnumerable<T> items = (IEnumerable<T>)value;
            JObject jObject = new JObject(new JProperty(PropertyName, new JArray(items.Select(i => JToken.FromObject(i, serializer)))));
            jObject.WriteTo(writer);
        }

        public override bool CanConvert(Type objectType)
        {
            // CanConvert is not called when the [JsonConverter] attribute is used
            return false;
        }
    }
Andrew Roberts
  • 990
  • 2
  • 12
  • 26