c# - 序列化为 JSON 时如何将属性分组到子对象中

标签 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 属性收集到名为 things 的子对象中。

另一个例子:

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"
    }
  }
}

通过创建契约(Contract)解析器,我可以轻松地剥离单个事物属性,并让非事物自行序列化。但我不知道如何创建 things 属性并将其放回我剥离的属性中:

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,它也不会出现。我的希望是我只需要填写契约(Contract)解析器中 TODO 附近的空白。

或者也许我走错了方向。你能帮我吗?

最佳答案

可以将自定义 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"
        }
      }
    }
  }
}

关于c# - 序列化为 JSON 时如何将属性分组到子对象中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25795241/

相关文章:

c# - 初始化一个静态字段还是在静态属性中返回一个值?

java - RMI 方法中的参数

Django REST Framework - 对嵌套序列化程序的查询限制?

json - 在 .net 4.5 标准序列化程序中定义时间跨度格式

c# - 使用以复杂类型为键的对象引用和字典将复杂类型序列化/反序列化为 Json

c# - 反序列化记录 - 如何初始化丢失的属性

c# - 如何理解以下实现算法的 C# linq 代码以返回 n 中 k 元素的所有组合

c# - 尝试将本地数据库添加到 VS2015 中的 c# 项目时出错

c# - 无法在 C# 中反序列化 XML - InvalidOperationException

serialization - Json.net 似乎依赖 $type 作为第一个属性。有没有办法解除该限制或轻松重新排序我的 json?