简体   繁体   中英

How to group properties together when serializing to JSON

I have a flat object that I would like to group into sections in order to make parsing it easier. This is the basic idea of what my class currently looks like:

class Populations {
  public string US { get; set; }
  public string Canada { get; set; }

  public string Germany { get; set; }
  public string England { get; set; }
}

But this is what I want it to be serialized into when populated with data:

{
  "Populations": {
    "North America": {
      "US": "318 million",
      "Canada": "35 million" 
    },
    "Europe": {
      "Germany": "80 million",
      "England": "53 million"
    }
  }
}

What I am trying to do is wrap my countries into continents without actually creating new continent classes. Is this possible with a tool like Json.Net or do I simply have to create a NorthAmerica class with two properties and then create a Europe class with two properties? Is it possible an annotation exists to allow me group together some of these properties?

There is no built-in mechanism in Json.Net to do this grouping as you describe; however, you could make a custom JsonConverter to do it if that's what you really want. Something like this might work:

class GroupAttribute : Attribute
{
    public string Name { get; set; }
    public GroupAttribute(string name)
    {
        Name = name;
    }
}

class GroupingConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        JObject obj = new JObject();
        Type type = value.GetType();
        foreach (PropertyInfo pi in type.GetProperties())
        {
            JToken propVal = JToken.FromObject(pi.GetValue(value));
            GroupAttribute group = pi.GetCustomAttribute<GroupAttribute>();
            if (group != null)
            {
                JObject groupObj = (JObject)obj[group.Name];
                if (groupObj == null)
                {
                    groupObj = new JObject();
                    obj.Add(group.Name, groupObj);
                }
                groupObj.Add(pi.Name, propVal);
            }
            else
            {
                obj.Add(pi.Name, propVal);
            }
        }

        JObject wrapper = new JObject(new JProperty(type.Name, obj));
        wrapper.WriteTo(writer);
    }

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

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

    public override bool CanConvert(Type objectType)
    {
        // CanConvert is not called when a [JsonConverter] attribute is applied
        return false;
    }
}

You would then mark up your Populations class like this:

[JsonConverter(typeof(GroupingConverter))]
class Populations 
{
    [Group("North America")]
    public string US { get; set; }
    [Group("North America")]
    public string Canada { get; set; }

    [Group("Europe")]
    public string Germany { get; set; }
    [Group("Europe")]
    public string England { get; set; }
}

Finally, serialize like this:

string json = JsonConvert.SerializeObject(populations, Formatting.Indented);

Fiddle: https://dotnetfiddle.net/EPiJue

You could create classes NorthAmerica and Europe , or you could do something like:

class Continent{
    string Type { get; set; }
    ICollection<Country> Countries { get; set; }
}

This would of course necessitate that all your countries have a common base class or interface.

Seeing as how your JSON doesn't have arrays, you're current classes make this json impossible to create unless you create another class with the exact structure you would want or Select it into a dynamic object.

If you must strictly abide by this JSON, I would encourage you to make a different population class that has a structure like

class Populations {
    public NorthAmerica NorthAmerica { get; set; }
    public Europe Europe { get; set; }
}

class NorthAmerica{
    public string US { get; set; }
    public string Canada { get; set; }
}

class Europe{
    public string Germany{ get; set; }
    public string England{ get; set; }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM