如何在序列化为JSON时将属性分组为子对象

时间:2014-09-11 19:03:56

标签: c# serialization json.net

鉴于此课程:

public class Thing
{
    public string Alpha { get; set; }
    public string Beta { get; set; }            
}

我需要序列化Thing的任意子类,哪些子类本身可以添加Thing属性。例如......

public class SomeThing : Thing
{
  public string Delta {get; set; }

  public Thing ThisThing { get; set; }
  public Thing ThatThing { get; set; }
}

很容易,使用Newtonsoft Json.NET将SomeThing类序列化为:

{
  alpha: "x",
  beta: "x",
  delta: "x",

  thisThing: {
    alpha: "y",
    beta: "y"
  },
  thatThing: {
    alpha: "z",
    beta: "z"
  }
}

想要做的事情是这样的(没有更改Thing或SomeThing类):

{
  alpha: "x",
  beta: "x",
  delta: "x",

  things: {
    thisThing: {
      alpha: "y",
      beta: "y"
    },
    thatThing: {
      alpha: "z",
      beta: "z"
  }  
}

也就是说,我想将任何Thing属性收集到名为的东西的子对象中。

另一个例子:

public class SomeThingElse : Thing
{
  public int Gamma {get; set; }

  public Thing Epsilon { get; set; }
}

...将序列化为

{
  alpha: "x",
  beta: "x",
  gamma: 42,

  things: {
    epsilon: {
      alpha: "y",
      beta: "y"
    }
  }
}

通过创建合同解析器,我可以轻松地剥离单个属性属性,并让非事物序列化。但我不知道如何在我剥离的属性中创建东西属性和东西:

public class MyContractResolver : CamelCasePropertyNamesContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var properties = base.CreateProperties(type, memberSerialization);

        // grab the properties that are NOT a Thing
        var toCreate = properties.Where(p => !typeof(Thing).IsAssignableFrom(p.PropertyType)).ToList();

        // grab the properties that ARE a Thing            
        var toGroup = properties.Where(p => typeof(Thing).IsAssignableFrom(p.PropertyType)).ToList();

        // create the new things property to stuff toGroup into
        var things = new JsonProperty
        {
            PropertyName = "things"
        };

        // THIS IS WHERE I'M STUCK...
        // TODO: somehow stuff toGroup into "things"

        // put the group back along with the non-thing properties
        toCreate.Add(things);

        // return the re-combined set of properties
        return toCreate;
    }            
}

我使用此解析器如下(此问题已简化):

static void Main(string[] args)
{
    var st = new SomeThing
    {
        Alpha = "x",
        Beta = "x",
        Delta = "x",
        ThisThing = new Thing() {Alpha = "y", Beta = "y"},
        ThatThing = new Thing() {Alpha = "z", Beta = "z"}
    };

    var settings = new JsonSerializerSettings
    {
        ContractResolver = new MyContractResolver(),
        Formatting = Formatting.Indented
    };

    var result = JsonConvert.SerializeObject(st, settings);

    Console.WriteLine(result);
}

哪个产生

{
  alpha: "x",
  beta: "x",
  delta: "x"
}

请注意,即使我已经创建并添加了一个名为“things”的JsonProperty,它也不会出现。我希望我只需要在合同解析器中填写TODO附近的空白。

或者我可能走错了方向。你能救我吗?

4 个答案:

答案 0 :(得分:2)

使用自定义IContractResolver结合自定义IValueProvider,可以执行您想要的操作。试试这个:

public class MyContractResolver : CamelCasePropertyNamesContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var properties = base.CreateProperties(type, memberSerialization);

        // if the type is a Thing and has child properties that are things...
        if (typeof(Thing).IsAssignableFrom(type) &&
            properties.Any(p => typeof(Thing).IsAssignableFrom(p.PropertyType)))
        {
            // grab only the properties that are NOT a Thing
            properties = properties
                .Where(p => !typeof(Thing).IsAssignableFrom(p.PropertyType))
                .ToList();

            // Create a virtual "things" property to group the remaining properties
            // into; associate the new property with a ValueProvider that will do
            // the actual grouping when the containing object is serialized
            properties.Add(new JsonProperty
            {
                DeclaringType = type,
                PropertyType = typeof(Dictionary<string, object>),
                PropertyName = "things",
                ValueProvider = new ThingValueProvider(),
                Readable = true,
                Writable = false
            });
        }

        return properties;
    }

    private class ThingValueProvider : IValueProvider
    {
        public object GetValue(object target)
        {
            // target should be a Thing; we want to get its Thing properties
            // and group them into a Dictionary.
            return target.GetType().GetProperties()
                         .Where(p => typeof(Thing).IsAssignableFrom(p.PropertyType))
                         .ToDictionary(p => p.Name, p => p.GetValue(target));
        }

        public void SetValue(object target, object value)
        {
            throw new NotImplementedException();
        }
    }
}

演示:

class Program
{
    static void Main(string[] args)
    {
        SomeThing st = new SomeThing
        {
            Alpha = "x.a",
            Beta = "x.b",
            ThisThing = new Thing { Alpha = "y.a", Beta = "y.b" },
            ThatThing = new SomeThingElse 
            { 
                Alpha = "z.a", 
                Beta = "z.b",
                Delta = 42,
                Epsilon = new Thing { Alpha = "e.a", Beta = "e.b" }
            }
        };

        JsonSerializerSettings settings = new JsonSerializerSettings();
        settings.ContractResolver = new MyContractResolver();
        settings.Formatting = Formatting.Indented;

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

        Console.WriteLine(json);
    }
}

public class Thing
{
    public string Alpha { get; set; }
    public string Beta { get; set; }
}

public class SomeThing : Thing
{
    public Thing ThisThing { get; set; }
    public Thing ThatThing { get; set; }
}

public class SomeThingElse : Thing
{
    public int Delta { get; set; }
    public Thing Epsilon { get; set; }
}

输出:

{
  "alpha": "x.a",
  "beta": "x.b",
  "things": {
    "thisThing": {
      "alpha": "y.a",
      "beta": "y.b"
    },
    "thatThing": {
      "delta": 42,
      "alpha": "z.a",
      "beta": "z.b",
      "things": {
        "epsilon": {
          "alpha": "e.a",
          "beta": "e.b"
        }
      }
    }
  }
}

答案 1 :(得分:1)

这是另一个使用反射来获取Thing的转换器:

public class MyConverter : JsonConverter
{
    public override bool CanRead { get { return false; } }

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

    public override void WriteJson(
        JsonWriter writer,
        object value,
        JsonSerializer serializer)
    {
        var someThing = (SomeThing)value;

        var things = typeof(SomeThing).GetProperties()
            .Where(pr => pr.PropertyType.IsAssignableFrom(typeof(Thing)))
            .ToDictionary (pr => pr.Name, pr => pr.GetValue(someThing));

        var nonThings = typeof(SomeThing).GetProperties()
            .Where(pr => !pr.PropertyType.IsAssignableFrom(typeof(Thing)));

        writer.WriteStartObject();
        writer.WritePropertyName("things");
        serializer.Serialize(writer, things);

        foreach (var nonThing in nonThings)
        {   
            writer.WritePropertyName(nonThing.Name);
            serializer.Serialize(writer, nonThing.GetValue(someThing));
        }

        writer.WriteEndObject();
    }

    public override bool CanConvert(Type type)
    {
        return type == typeof(SomeThing);
    }
}

我无法弄清楚如何尊重CamelCasePropertyNamesContractResolver 使用转换器。

答案 2 :(得分:0)

我认为你可能会走错方向。

如果包含Thing对象为零到多个,则可以定义属性

List<Thing> Things;

我相信在这一点上,json.Net会在没有合同解析器的情况下序列化您的需求。

答案 3 :(得分:-1)