Skip to content
On this page

粘土对象

关于粘土对象

粘土对象是一种可以模拟弱(动态)语言特性的对象,类似 JavaScript 一样操作对象。只需通过 Clay 类初始化即可。

使用场景

粘土对象常用于需要动态构建对象的地方,如 CMS 系统的 ViewModel,或者运行时创建一个新的对象,或者请求第三方 API 情况。

关于性能

粘土性能实际上并不高效,但是性能也并不低下,只不过略输于强类型调用。什么时候使用可以看以上的【使用场景】。

Clay 对象

Clay 对象是继承自 DynamicObject 的一个特殊对象,提供了像弱(动态)语言一样操作对象的方法及索引获取方式。

如何使用

创建一个对象

cs
// 创建一个空的粘土对象
dynamic clay = new Clay();

// 从现有的对象创建
dynamic clay2 = Clay.Object(new {});

// 从 json 字符串创建,可用于第三方 API 对接,非常有用
dynamic clay3 = Clay.Parse(@"{""foo"":""json"", ""bar"":100, ""nest"":{ ""foobar"":true } }");

读取/获取属性

cs
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 一样通过索引器获取

新增属性

cs
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 }; // 新增一个匿名类

更新属性值

cs
dynamic clay = Clay.Object(new
{
    Foo = "json",
    Bar = 100,
    Nest = new
    {
        Foobar = true
    }
});

// 更新
clay.Foo = "Penkar";
clay["Nest"].Foobar = false;
clay.Nest["Foobar"] = true;

删除属性

cs
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);    // 支持数组作为方法删除

判断键/索引是否存在

cs
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

遍历对象

cs
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 方式,Penkar 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();

转换成具体对象

cs
dynamic clay = new Clay();
clay.Arr = new string[] { "Penkar", "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; // 声明方式

固化粘土

固化粘土在很多时候和序列化很像,但是如果直接调用 Deserialize<object>Deserialize<dynamic> 无法返回实际类型,所以就有了固化类型的功能,如:

cs
// 返回 object
var obj = clay.Solidify();

// 返回 dynamic
var obj1 = clay.Solidify<dynamic>();

// 返回其他任意类型
var obj2 = clay.Solidify<City>();

输出 JSON

cs
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 服务,如:

cs
public IActionResult OutputClay()
{
    dynamic clay = Clay.Object(new
    {
       // ....
    });

    // 转换成 dictionary
    var dic = clay.ToDictionary();

    return new JsonResult(dic);
}

配置序列化 Dictionary 键命名策略支持:

cs
services.AddControllers()
        .AddJsonOptions(options =>
         {
            options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;    // 配置 Dictionary 类型序列化输出
         });

输出 XML 对象

cs
dynamic clay = Clay.Object(new
{
    Foo = "json",
    Bar = 100,
    Nest = new
    {
        Foobar = true
    },
    Arr = new string[] { "NOR", "XOR" }
});

// 输出 XElement
var xml = clay.XmlElement;

关键字处理

cs
dynamic clay = new Clay();
clay.@int = 1;
clay.@event = "事件";

转换成字典类型

cs
dynamic clay = Clay.Object(new { name = "张三" });
clay.name = "百小僧";
Dictionary<string, object> parms = clay.ToDictionary();

获取不存在 Key 处理

默认情况下,如果通过粘土对象获取不存在的值会抛出 Microsoft.CSharp.RuntimeBinder.RuntimeBinderException 异常,如:

cs
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。如:

cs
// 方式一,初始化指定 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

转换为特定集合

cs
dynamic clay = Clay.Parse(@"[]");
IEnumerable<SysMenu> = clay.ConvertTo<SysMenu>();

这种方式区别于 .Solidify<T>ConvertTo<T> 内部通过反射创建对象并自动适配属性类型。

序列化支持

默认情况下,Clay 为动态类型对象,不支持直接通过 System.Text.JsonNewtonsoft.Json 进行序列化和反序列化,这时只需添加以下配置即可。

  • System.Text.Json 方式
cs
services.AddControllersWithViews()
        .AddJsonOptions(options =>
        {
            options.JsonSerializerOptions.Converters.AddClayConverters();
        });
  • Newtonsoft.Json 方式
cs
.AddNewtonsoftJson(options =>
{
     options.SerializerSettings.Converters.AddClayConverters();
})