【问题标题】:Reflection GetProperties on different custom classes and collections不同自定义类和集合上的反射 GetProperties
【发布时间】:2017-05-12 12:47:05
【问题描述】:

在 Web API 中,我创建了一个自定义 MediaTypeFormatter 来创建具有特定模式的 JSON 输出,这使得标准序列化不适合。并不是说上面的上下文可能是相关的,但我需要使用我指定的模式将对象和 IEnumerable 转换为 JSON。

在伪代码中:

If object is a collection
  foreach item in collection
     Write the name/type of the item (will be same for all)
         foreach property of item
              Write the property name, type, and value
Else
  foreach property of object 
     Write the property name, type, and value

我最感兴趣的部分是通过反射获取类属性名称/值。

例如,这是从控制器发送的:

return new MyPerson { .FirstName = "Bob", .DateOfBirth = Convert.ToDateTime("1979-03-01") }

...将输出为(粗略的示例,因为 JSON 很容易更改以制作必要的架构):

{ "MyPerson" : {"FirstName": {"value": "Bob", "Type": "string"}}, "DateOfBirth": {"value": "1979-03-01", "Type": "date"}}

同样,一个集合将被迭代以产生类似的输出:

return new IEnumerable<Foo>() {
    new Foo() { Param1 = "aaa", Param2 = "bbb" },
    new Foo() { Param1 = "ccc", Param2 = "ddd" }
}

...生产

{ "FooCollection": [ 
    { "Foo" : {"Param1": {"value": "aaa", "Type": "string"}}, {"Param2": {"value": "bbb", "Type": "string"}} },
    { "Foo" : {"Param1": {"value": "ccc", "Type": "string"}}, {"Param2": {"value": "ddd", "Type": "string"}} }
]}

我尝试消化许多与此挑战相关的其他示例 (1,2),但我正在努力适应它们。这是我设法得到的:

private void WriteStream(Type type, object value, Stream stream, HttpContentHeaders contentHeaders)
{
    using (StringWriter _stringWriter = new StringWriter()) {
        if (!(value is ICollection))
        {
            foreach (PropertyInfo p in value.GetProperties())
            {
                _stringWriter.Write(GetPropertyData(p));
            }
        }
        else
        {
            foreach (object o in value)
            {
                foreach (PropertyInfo p in o.GetProperties())
                {
                    _stringWriter.Write(GetPropertyData(p));
                }
            }
        }
        // output omitted for brevity...
    }
}

public function GetPropertyData(PropertyInfo p) {
    return string.Format("{name: '{0}', type: '{1}', value: '{2}'},", 
        p.Name, 
        p.PropertyType.ToString(), 
        p.GetValue(p).ToString())
}

【问题讨论】:

  • "写属性名、类型和值" - 我在你的 Json 示例中看不到类型。另外,如果属性不是基本类型(int、float、string 等)怎么办?
  • 抱歉,RTFM 过载让我头晕目眩。修改后的问题。所有属性类型都是基本的(字符串、int、double 或 bool),尽管我很想知道如果它们不是基本类型该怎么办(尽管可能是另一个问题)
  • 实用程序可用于从 C# 类或 JSON 生成 JSON 模式。我觉得你在这里有点想重新发明轮子。
  • 你能举一个这样的例子吗?我已经使用 NewtonSoft et al 进行一般序列化,但在这种情况下,模式必须是明确的,并且不是标准的。 JSON 生成不是问题,而是如何使用反射检查对象。

标签: c# .net reflection asp.net-apicontroller


【解决方案1】:

我相信您以错误的方式解决问题。与其通过创建自定义 MediaTypeFormatter 来重新发明轮子,不如为您的对象使用正确的模型,然后让序列化程序完成其余的工作。

一个例子是为你的目的使用扩展方法:

public static class JsonExtensions
{
    public static object CreateModels<T>(this IEnumerable<T> models, string modelName = null)
    {
        modelName = modelName ?? typeof(T).Name+"Collection";

        return new Dictionary<string, object>()
        {
            { modelName, models.Select(m => CreateModel(m)) }
        };
    }

    public static IDictionary<string, object> CreateModel<T>(this T model, string modelName = null)
    {
        modelName = modelName ?? typeof(T).Name;

        return new Dictionary<string, object>()
        {
            { modelName, GetProperties(model) }
        };
    }

    private static IDictionary<string, object> GetProperties<T>(T obj)
    {
        var props = typeof(T).GetProperties();
        return props.ToDictionary(p => p.Name, p => (object)new { type = p.PropertyType.ToString(), value = p.GetValue(obj, null).ToString() });
    }
}

假设您在项目中使用 Json.NET,这就是它们的使用方式:

JsonConvert.SerializeObject(new MyPerson { FirstName = "Bob", DateOfBirth = Convert.ToDateTime("1979-03-01") }.CreateModel());

输出(漂亮打印):

{
  "MyPerson": {
    "FirstName": {
      "type": "System.String",
      "value": "Bob"
    },
    "DateOfBirth": {
      "type": "System.DateTime",
      "value": "3\/1\/1979 12:00:00 AM"
    }
  }
}

同时:

JsonConvert.SerializeObject(new List<Foo>() {
    new Foo() { Param1 = "aaa", Param2 = "bbb" },
    new Foo() { Param1 = "ccc", Param2 = "ddd" }
}.CreateModels());

输出:

{
  "FooCollection": [
    {
      "Foo": {
        "Param1": {
          "type": "System.String",
          "value": "aaa"
        },
        "Param2": {
          "type": "System.String",
          "value": "bbb"
        }
      }
    },
    {
      "Foo": {
        "Param1": {
          "type": "System.String",
          "value": "ccc"
        },
        "Param2": {
          "type": "System.String",
          "value": "ddd"
        }
      }
    }
  ]
}

.NET Fiddle 演示 HERE

注意

我在您的示例中看到您使用 string 而不是 System.String 作为属性的类型名称。使用别名类型名称代替 CLR 真实名称并不容易,但如果确实有必要,您可以查看 this answer 以获取获取它的方法。

【讨论】:

  • 这很有趣。更大的问题是我需要支持标准格式的 JSON,再加上 Google DataTable 格式(不是严格的 JSON)[developers.google.com/chart/interactive/docs/…,而不想单独修改每个控制器以检查所需的输出(昨天问到还没有输入 @987654324 @)...如果不使用格式化程序,我看不到如何做到这一点。
  • Google 自己的描述是,“与 JSON 非常相似,但并不完全相同。”,所以我不知道您的解决方案是否可行?
  • Web API 已经使用 Json.NET 来序列化对象,因此您应该创建一个操作过滤器来拦截返回的对象并在需要时更改它们。你可以从这个questionthis one开始。
  • 使用这种方法,我假设同一个控制器 URL 可以返回以不同方式表达的相同数据?如果是这样,请问调用者是如何控制的?
  • 这取决于你,你可以访问HttpActionExecutedContext中的整个HTTP请求。
猜你喜欢
  • 1970-01-01
  • 2017-02-21
  • 2023-03-19
  • 1970-01-01
  • 1970-01-01
  • 2011-12-06
  • 1970-01-01
  • 1970-01-01
  • 2021-01-20
相关资源
最近更新 更多