29. 粘土对象
📝 模块更新日志
-
新特性
- 粘土对象
.ConvertTo
支持自定义值提供器 4.8.8.40 ⏱️2023.08.03 70d5888 - 粘土对象支持结构
struct
对象类型 4.8.8.7 ⏱️2023.04.30 a0fa3aa - 粘土对象可反射转换成特定
IEnumerable<T>
类型:clay.ConverTo<T>()
4.8.8 ⏱️2023.04.13 5d54a65 - 粘土对象可配置访问不存在
Key
时是抛异常还是返回null
4.8.7.40 ⏱️2023.04.10 e994d53 - 粘土对象可转换成
IEnumerable<T>
对象并实现Lambda/Linq
操作 4.8.7.19 ⏱️2023.03.22 2b14ed9
- 粘土对象
查看变化
dynamic clay = Clay.Parse("{\"Foo\":\"json\",\"Bar\":100,\"Nest\":{\"Foobar\":true},\"Arr\":[\"NOR\",\"XOR\"]}");
// 将 clay.Arr 转换成 IEnumerable<dynamic>
IEnumerable<dynamic> query = clay.Arr.AsEnumerator<dynamic>();
// 实现 Lambda/Linq 操作
var result = query.Where(u => u.StartsWith("N"))
.Select(u => new
{
Name = u
})
.ToList();
-
- 粘土对象支持任何字符作为
JSON/XML
键 4.8.6.9 ⏱️2023.02.19 f99aee8 #note_16329657
- 粘土对象支持任何字符作为
-
问题修复
- 粘土对象不支持枚举类型问题 4.8.8.41 ⏱️2023.08.25 #I7VDDL
-
ExpandoObject.ToDictionary()
转换异常 4.8.8.25 ⏱️2023.06.14 #I7BY0P - 粘土对象转换为
Dictionary<string, object>
类型异常 4.8.7.41 ⏱️2023.04.11 f96baeb - 粘土对象不支持运行时动态设置携带特殊字符的
Key
键 4.8.7.39 ⏱️2023.04.10 6572515 - 粘土对象遍历对象键值对因
4.8.7.19
版本更新导致异常 4.8.7.25 ⏱️2023.03.28 #I6R4ZU - 粘土对象不支持
数字
作为JSON/XML
键问题 4.8.6.9 ⏱️2023.02.19 #note_16329657 - 粘土对象不支持
中文
作为JSON/XML
键问题 4.8.6.6 ⏱️2023.02.18 4961e01
-
其他更改
- 粘土对象
number
类型处理,若含.
转double
类型,否则转long
类型 4.8.7.24 ⏱️2023.03.28 e82e883
- 粘土对象
以下内容仅限 Furion 2.1.12 +
版本使用。
29.1 关于粘土对象
粘土对象是 Furion
框架推出的一种动态类型,可以模拟弱(动态)语言操作特性,使 C# 对象实现类似 JavaScript
一样操作对象。只需通过 Clay
类初始化即可。
为什么起名为 “粘土” 呢?因为这个对象可以自由的添加属性,移除属性,又可以固化成任何对象,具有可拓展、可塑造的特点。
29.1.1 使用场景
粘土对象常用于需要动态构建对象的地方,如 CMS
系统 的 ViewModel
,或者运行时创建一个新的对象,或者请求第三方 API
情况。
29.1.2 关于性能
粘土性能实际上并不高效,但是性能也并不低下,只不过略输于强类型调用。什么时候使用可以看以上的【使用场景】。
29.2 Clay
对象
Clay
对象是继承自 DynamicObject
的一个特殊对象,提供了像弱(动态)语言一样操作对象的方法及索引获取方式。
29.3 如何使用
29.3.1 创建一个对象
// 创建一个空的粘土对象
dynamic clay = new Clay();
// 从现有的对象创建
dynamic clay2 = Clay.Object(new {});
// 从 json 字符串创建,可用于第三方 API 对接,非常有用
dynamic clay3 = Clay.Parse(@"{""foo"":""json"", ""bar"":100, ""nest"":{ ""foobar"":true } }");
29.3.2 读取/获取属性
dynamic clay = Clay.Object(new
{
Foo = "json",
Bar = 100,
Nest = new
{
Foobar = true
}
});
var r1 = clay.Foo; // "json" - string类型
var r2 = clay.Bar; // 100 - double类型
var r3 = clay.Nest.Foobar; // true - bool类型
var r4 = clay["Nest"]["Foobar"]; // 还可以和 JavaScript 一样通过索引器获取
29.3.3 新增属性
dynamic clay = Clay.Object(new
{
Foo = "json",
Bar = 100,
Nest = new
{
Foobar = true
}
});
// 新增
clay.Arr = new string[] { "NOR", "XOR" }; // 添加一个数组
clay.Obj1 = new City { }; // 新增一个实例对象
clay.Obj2 = new { Foo = "abc", Bar = 100 }; // 新增一个匿名类
29.3.4 更新属性值
dynamic clay = Clay.Object(new
{
Foo = "json",
Bar = 100,
Nest = new
{
Foobar = true
}
});
// 更新
clay.Foo = "Furion";
clay["Nest"].Foobar = false;
clay.Nest["Foobar"] = true;
29.3.5 删除属性
dynamic clay = Clay.Object(new
{
Foo = "json",
Bar = 100,
Nest = new
{
Foobar = true
},
Arr = new string[] { "NOR", "XOR" }
});
// 删除操作
clay.Delete("Foo"); // 通过 Delete 方法删除
clay.Arr.Delete(0); // 支持数组 Delete 索引删除
clay("Bar"); // 支持直接通过对象作为方法删除
clay.Arr(1); // 支持数组作为方法删除
29.3.6 判断键/索引是否存在
dynamic clay = Clay.Object(new
{
Foo = "json",
Bar = 100,
Nest = new
{
Foobar = true
},
Arr = new string[] { "NOR", "XOR" }
});
// 判断属性是否存在
var a = clay.IsDefined("Foo"); // true
var b = clay.IsDefined("Foooo"); // false
var c = clay.Foo(); // true
var d = clay.Foooo(); // false;
var e = clay.Arr.IsDefined(0); // true
var f = clay.Arr.IsDefined(3); // false
29.3.7 遍历对象
dynamic clay = Clay.Object(new
{
Foo = "json",
Bar = 100,
Nest = new
{
Foobar = true
},
Arr = new string[] { "NOR", "XOR" }
});
// 遍历数组
foreach (string item in clay.Arr)
{
Console.WriteLine(item); // NOR, XOR
}
// 遍历整个对象属性及值,类似 JavaScript 的 for (var p in obj)
foreach (KeyValuePair<string, dynamic> item in clay)
{
Console.WriteLine(item.Key + ":" + item.Value); // Foo:json, Bar: 100, Nest: { "Foobar":true}, Arr:["NOR","XOR"]
}
// 数组/集合 可使用 Lambda 方式,Furion 4.8.7.19+ 支持
IEnumerable<dynamic> query = clay.Arr.AsEnumerator<dynamic>(); // 通过 .AsEnumerator<T>() 转换成 IEnumerable<T> 类型
var result = query.Where(u => u.StartsWith("N"))
.Select(u => new
{
Name = u
})
.ToList();
// 也可以通过原生方法转换成 IEnumerable 对象
IEnumerable<dynamic> query = ((System.Collections.IEnumerable)clay.Arr).Cast<dynamic>(); // 其中 Cast<T> 的 T 可以时任意类型,比如 Cast<string>();
// 获取对象所有键或数组所有索引
IEnumerable<string> keys = clay.GetDynamicMemberNames();
29.3.8 转换成具体对象
dynamic clay = new Clay();
clay.Arr = new string[] { "Furion", "Fur" };
// 数组转换示例
var a1 = clay.Arr.Deserialize<string[]>(); // 通过 Deserialize 方法
var a2 = (string[])clay.Arr; // 强制转换
string[] a3 = clay.Arr; // 声明方式
// 对象转换示例
clay.City = new City { Id = 1, Name = "中山市" };
var c1 = clay.City.Deserialize<City>(); // 通过 Deserialize 方法
var c2 = (City)clay.City; // 强制转换
City c3 = clay.City; // 声明方式
29.3.9 固化粘土
固化粘土在很多时候和序列化很像,但是如果直接调用 Deserialize<object>
或 Deserialize<dynamic>
无法返回实际类型,所以就有了固化类型的功能,如:
// 返回 object
var obj = clay.Solidify();
// 返回 dynamic
var obj1 = clay.Solidify<dynamic>();
// 返回其他任意类型
var obj2 = clay.Solidify<City>();
29.3.10 输出 JSON
dynamic clay = Clay.Object(new
{
Foo = "json",
Bar = 100,
Nest = new
{
Foobar = true
},
Arr = new string[] { "NOR", "XOR" }
});
// 输出 JSON
var json = clay.ToString(); // "{\"Foo\":\"json\",\"Bar\":100,\"Nest\":{\"Foobar\":true},\"Arr\":[\"NOR\",\"XOR\"]}"
Clay
序列化成 JSON
键大小写控制默认情况下,Clay
输出成 JSON
后将保持原样输出,如果需要实现键命名控制,则需要先转换成 Dictionary
然后再配置 AddJsonOptions
服务,如:
public IActionResult OutputClay()
{
dynamic clay = Clay.Object(new
{
// ....
});
// 转换成 dictionary
var dic = clay.ToDictionary();
return new JsonResult(dic);
}
配置序列化 Dictionary
键命名策略支持:
services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; // 配置 Dictionary 类型序列化输出
});
29.3.11 输出 XML
对象
dynamic clay = Clay.Object(new
{
Foo = "json",
Bar = 100,
Nest = new
{
Foobar = true
},
Arr = new string[] { "NOR", "XOR" }
});
// 输出 XElement
var xml = clay.XmlElement;
29.3.12 关键字处理
dynamic clay = new Clay();
clay.@int = 1;
clay.@event = "事件";
29.3.13 转换成字典类型
dynamic clay = Clay.Object(new { name = "张三" });
clay.name = "百小僧";
Dictionary<string, object> parms = clay.ToDictionary();
29.3.14 获取不存在 Key
处理
以下内容仅限 Furion 4.8.7.40 +
版本使用。
默认情况下,如果通过粘土对象获取不存在的值会抛出 Microsoft.CSharp.RuntimeBinder.RuntimeBinderException
异常,如:
dynamic clay = Clay.Object(new
{
Foo = "json",
Bar = 100,
Nest = new
{
Foobar = true
}
});
// 获取不存在的值
var undefined = clay["undefined"]; // 抛出 Microsoft.CSharp.RuntimeBinder.RuntimeBinderException 异常
这时我们可以配置粘土对象的 ThrowOnUndefined
属性行为,配置 true
时获取不存在的 Key
抛异常,false
表示返回 null
。如:
// 方式一,初始化指定 throwOnUndefined: false,所有构造函数和静态初始化方法都有该参数
dynamic clay = Clay.Object(new
{
Foo = "json",
Bar = 100,
Nest = new
{
Foobar = true
}
}, throwOnUndefined: false);
var undefined = clay.Undefined; // => null
// 方式二,操作时指定,设置 ThrowOnUndefined 属性
dynamic clay = Clay.Object(new
{
Foo = "json",
Bar = 100,
Nest = new
{
Foobar = true
}
});
var undefined = clay.Undefined; // 抛出 Microsoft.CSharp.RuntimeBinder.RuntimeBinderExceptionnull
clay.ThrowOnUndefined = false;
var undefined = clay.Undefined; // => null
// 方式三,只配置局部对象
dynamic clay = Clay.Object(new
{
Foo = "json",
Bar = 100,
Nest = new
{
Foobar = true
}
});
var undefined = clay.Undefined; // 抛出 Microsoft.CSharp.RuntimeBinder.RuntimeBinderException
var bar = clay.Nest.Bar; // 抛出 Microsoft.CSharp.RuntimeBinder.RuntimeBinderException
// 设置 Nest 节点不抛异常
clay.Nest.ThrowOnUndefined = false;
var undefined = clay.Undefined; // 抛出 Microsoft.CSharp.RuntimeBinder.RuntimeBinderException
var bar = clay.Nest.Bar; // => null
29.3.15 转换为特定集合
以下内容仅限 Furion 4.8.8 +
版本使用。
dynamic clay = Clay.Parse(@"[]");
IEnumerable<SysMenu> data = clay.ConvertTo<SysMenu>();
这种方式区别于 .Solidify<T>
,ConvertTo<T>
内部通过反射创建对象并自动适配属性类型。
另外在 Furion 4.8.8.40+
版本提供了 ConvertTo
自定义值参数,可以针对特定属性进行额外处理,如:
Func<PropertyInfo, object, object> valueProvider = (property, oldValue) =>
{
// 例如针对属性名为 Device 的进行额外 处理
if (property.Name == "Device")
{
return JsonSerializer.Deserialize<Device>(oldValue.ToString());
}
return oldValue;
};
IEnumerable<SysMenu> data = clay.ConvertTo<SysMenu>(valueProvider);
29.4 序列化支持
默认情况下,Clay
为动态类型对象,不支持直接通过 System.Text.Json
和 Newtonsoft.Json
进行序列化和反序列化,这时只需添加以下配置即可。
System.Text.Json
方式
services.AddControllersWithViews()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.AddClayConverters();
});
Newtonsoft.Json
方式
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.Converters.AddClayConverters();
})
29.5 反馈与建议
给 Furion 提 Issue。