【问题标题】:How to transform an object during conversion to json?如何在转换为 json 期间转换对象?
【发布时间】:2021-04-13 11:36:46
【问题描述】:

我有一个平面 DTO 对象,其中包含通过 API 调用返回的约 500 个字段。 .NET 机器将其转换为 JSON,一切都很好。它看起来像这样:

{
  "id": 1,
  "desc": "foo",
  "system": "axmls",
  "bedrooms": 5,
  "flooring": "tile",
  "roof": "tesla",
  ...
  ...
}

现在我被要求将这大约 500 个字段分类,使其看起来类似于:

{
  "main": {
    "id": 1,
    "desc": "foo",
    "system": "axmls",
    ...
  }
  "interior": {
    "bedrooms": 5,
    "flooring": "tile",
    ...
  }
  "exterior": {
    "roof": "tesla",
    ...
  }
}

我已经想到了几种方法来做到这一点(都不太吸引人)

  1. 创建一个新对象以匹配此结构并将数据复制到其中。不太好。
  2. 手动构建 JSON。没有。
  3. 用 Category 属性标记原始对象上的每个字段,然后在 FilterAttribute 中以某种方式对其进行转换。这个听起来最有希望,虽然我不知道如何实现它。

有没有办法以理智的方式创建这样的 JSON 结构?

【问题讨论】:

  • 考虑JSONata
  • 构建一个元数据字典(字符串、字符串),将每个项目(卧室、地板、屋顶)映射到类别(主要、内部、外部)。将您拥有的数据(大的初始列表)读入Dictionary<string, string>。然后走那个字典,在元数据中查找类别。最后用结果填充 Dictionary<string, Dictionary<string, string>> 并将其序列化回 JSON
  • 创建一个新对象来匹配这个结构并将数据复制到它。不太好。 - 是最简单的方法。可维护,可读,因为读者可以看到我们暴露了不同的数据结构。可维护,因为您只有一种方法可以将原始数据结构转换为我们发送的数据结构。
  • 如果您想要一个非常简单的实现 @Fabio 的建议,请在下面查看我的回答。简单、可维护、自我记录

标签: c# asp.net .net asp.net-web-api


【解决方案1】:

这是我在 cmets 中描述的内容。首先,您需要一些元数据。我在这里使用了一个枚举(因为它减少了拼写错误的可能性——尽管它确实使代码稍微复杂了一点。

更新:我发现了一个错误,我将所有内容都视为字符串。现在我将树的叶节点视为object

首先是类别类型:

private enum CategoryName
{
    Main,
    Interior,
    Exterior,
}

以及类别元数据:

private static readonly Dictionary<string, CategoryName> _categoryMetadata =
    new Dictionary<string, CategoryName>
    {
        {"id", CategoryName.Main},
        {"desc", CategoryName.Main},
        {"system", CategoryName.Main},
        {"bedrooms", CategoryName.Interior},
        {"flooring", CategoryName.Interior},
        {"roof", CategoryName.Exterior}
    };

现在,您的输入 JSON。请注意,我对字符串进行了单引号,这使得在 C# 中使用起来更容易,并且不会改变 JSON 的性质。我还拿出了你所有的... 条目:

 private const string InputJson = @"
 {
     'id': 1,
     'desc': 'foo',
     'system': 'axmls',
     'bedrooms': 5,
     'flooring': 'tile',
     'roof': 'tesla',
 }";

最后是代码。正如我在 cmets 中描述的那样,“构建一个元数据字典(字符串,字符串),将每个项目(卧室、地板、屋顶)映射到类别(主要、内部、外部)。读取您拥有的数据(大的初始列表) 到 Dictionary。然后遍历该 Dictionary,在元数据中查找类别。最后用结果填充 Dictionary> 并将其序列化回 JSON” em>:

var output = new Dictionary<string, Dictionary<string, object>>();
var input = JsonConvert.DeserializeObject<Dictionary<string, object>>(InputJson);
foreach (var item in input)
{
    if (_categoryMetadata.TryGetValue(item.Key, out var category))
    {
        var categoryString = category.ToString().ToLower();
        if (!output.ContainsKey(categoryString))
        {
            output[categoryString] = new Dictionary<string, object>();
        }

        output[categoryString].Add(item.Key, item.Value);
    }
}

var result = JsonConvert.SerializeObject(output, Formatting.Indented);

完成后(以及在我的更新之后),JSON 看起来像:

{
  "main": {
    "id": 1,
    "desc": "foo",
    "system": "axmls"
  },
  "interior": {
    "bedrooms": 5,
    "flooring": "tile"
  },
  "exterior": {
    "roof": "tesla"
  }
}

在现实生活中,您会在 JsonConvert.Serialize 调用中删除 Formatting.Indented 参数。

【讨论】:

  • @AngryHacker:您可能希望在if (_categoryMetadata.TryGetValue(item.Key, out var category)) 之后出现else 块,以表明您缺少类别。我一般不会在我的示例代码中进行错误检查
【解决方案2】:

只是为了形象化它看起来有多简单:)

public class House
{
    public int Id { get; set; }
    public string Description { get; set; }
    public string System { get; set; }
    public int Bedrooms { get; set; }
    public string Flooring { get; set; }
    public string Roof { get; set; }
}

不复制值的公开结构

public class ExposedHouse
{        
    public Main Main { get; }
    public Interior Interior { get; }
    public Exterior Exterior { get; }

    public ExposedHouse(House house)
    {
        Main = new Main(house);
        Interior = new Interior(house);
        Exterior = new Exterior (house);
    }

    public class Main
    {
        private readonly House _house;

        public int Id => _house.Id;
        public string Description => _house.Description;
        public string System => _house.System;
   
        public Main(House house) { _house = house; }
    }

    public class Interior
    {
        private readonly House _house;

        public int Bedrooms => _house.Bedrooms;
        public string Flooring => _house.Flooring;

        public Interior(House house) { _house = house; }
    }

    public class Exterior
    {
        private readonly House _house;

        public string Roof => _house.Flooring;

        public Exterior(House house) { _house = house; }
    }
}

不复制值的转换

var exposed = new ExposedHouse(house);
return Ok(exposed);

如果将来需求发生变化并且您需要以不同的方式格式化某些值,使用这种方法任何人都可以做到:)

【讨论】:

  • 我试图避免复制对象。请记住,它有 500 多个属性。
  • 我的意思是将 500 个项目复制并粘贴到正确的插槽中。我只是在相关属性上放置了一个 Category 属性,稍后通过 Reflection 读取它们。
  • @AngryHacker,当前的方法不会复制值,而是用不同的结构“表示”它们。这种方法不涉及反射“昂贵”,并为其他更改提供了良好的基础。例如,如果新的需求需要格式化一些特定的值——对于反射方法,你需要构建/查找/使用另一种通用方法,但是使用这个——只需跳入代码并更改它;)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-29
  • 2017-06-16
  • 1970-01-01
  • 2023-03-17
相关资源
最近更新 更多