Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to serialize a collection (with an indexer property) as a dictionary

Tags:

c#

json.net

I have a class, FooCollection let's say, which implements IEnumerable<Foo>, and also provides an indexer by which one can look up a Foo by its name. Functionally it's read-only as a dictionary. But it's not really a dictionary because users do not get to decide on the keys.

Anyway, I want JSON.NET to serialize this object as a JSON dictionary, instead of as an array, which it's doing now. Sticking JsonDictionaryAttribute on it doesn't work: then it does nothing.

Clues?

like image 829
Jerome Haltom Avatar asked Sep 02 '13 19:09

Jerome Haltom


1 Answers

You're probably going to need to create a custom JsonConverter for your FooCollection class in order to serialize it the way you want. Since you haven't posted any code at all, I'll just make something up for sake of example. Let's say your Foo and FooCollection classes look like this:

class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }
}

class FooCollection : IEnumerable<Foo>
{
    private List<Foo> list = new List<Foo>();

    public void Add(Foo foo)
    {
        list.Add(foo);
    }

    public Foo this[string name]
    {
        get { return list.Find(f => f.Name == name); }
    }

    public IEnumerator<Foo> GetEnumerator()
    {
        return list.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable)list).GetEnumerator();
    }
}

The following converter would serialize the FooCollection as if it were a dictionary. I'm assuming that you would want the converter to use the value of the Name property as the key for each Foo for purposes of serialization (to match the indexer on the collection), so that is how I implemented it. You can change it to something else by modifying the GetFooKey() method.

class FooCollectionConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(FooCollection));
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteStartObject();
        foreach (Foo foo in (FooCollection)value)
        {
            writer.WritePropertyName(GetFooKey(foo));
            serializer.Serialize(writer, foo);
        }
        writer.WriteEndObject();
    }

    // Given a Foo, return its unique key to be used during serialization
    private string GetFooKey(Foo foo)
    {
        return foo.Name;
    }

    public override bool CanRead
    {
        get { return false; }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

Here is an example program that demonstrates how to use the converter.

class Program
{
    static void Main(string[] args)
    {
        FooCollection coll = new FooCollection();
        coll.Add(new Foo { Id = 1, Name = "Moe" });
        coll.Add(new Foo { Id = 2, Name = "Larry" });
        coll.Add(new Foo { Id = 3, Name = "Curly" });

        JsonSerializerSettings settings = new JsonSerializerSettings();
        settings.Converters.Add(new FooCollectionConverter());
        settings.Formatting = Newtonsoft.Json.Formatting.Indented;

        string json = JsonConvert.SerializeObject(coll, settings);

        Console.WriteLine(json);
    }
}

And here is the output of the above program:

{
  "Moe": {
    "Id": 1,
    "Name": "Moe"
  },
  "Larry": {
    "Id": 2,
    "Name": "Larry"
  },
  "Curly": {
    "Id": 3,
    "Name": "Curly"
  }
}

Fiddle: https://dotnetfiddle.net/wI2iQG

like image 121
Brian Rogers Avatar answered Nov 15 '22 00:11

Brian Rogers