Git Product home page Git Product logo

expando's Introduction

Expando Class

Extensible dynamic types for .NET that support both static and dynamic properties

Expando is a .NET class that allows you to create extensible types that mix the functionality of static and dynamic types. You can create static types inherited from Expando that have all the features of the static type, but when cast to dynamic support extensibility via dynamic C# features that allow you to add properties and methods at runtime. You can also create mix-ins that combine the properties from two objects into a single object.

The library supports two-way serialization with JSON.NET and XmlSerializer which allows building extensible types that can persist and restore themselves. This is very useful for data models that can expose extensible custom properties that can be persisted as serialized strings for example.

This class provides functionality similar to the native ExpandoObject class, but with many more features for working with existing statically typed Types, the ability to serialize and to inherit from to extend existing types.

Features

Expando has the following features:

  • Allows strongly typed classes (must inherit from Expando)
  • Supports strongly typed Properties and Methods
  • Supports dynamically added Properties and Methods
  • Allows extension of strongly typed classes with dynamic features
  • Supports string based collection access to properties
    (both on static and dynamic properties)
  • Create Mix-ins of two types that combine properties
  • Supports two-way JSON.NET and XML serialization

You can find out more detail from this blog post: Creating a dynamic, extensible C# Expando Object

Installation

You can use this class with source code provided here or by using the Westwind.Utilities NuGet package.

pm> Install-Package Westwind.Utilities

Example Usage

This class essentially acts as a mix-in where you can create a strongly typed object and add dynamic properties to it.

To start create a class that inherits from the Expando class and simply create your class as usual by adding properties:

public class User : Westwind.Utilities.Dynamic.Expando
{
    public string Email { get; set; }
    public string Password { get; set; }
    public string Name { get; set; }
    public bool Active { get; set; }
    public DateTime? ExpiresOn { get; set; }

    public User() : base()
    { }

    // only required if you want to mix in seperate instance
    public User(object instance)
        : base(instance)
    {
    }
}

Then simply instantiate the class. If you reference the strongly typed class properties you get the strongly typed interface - ie. your declared properties:

var user = new User();
user.Email = "[email protected]"

If you cast the object to dynamic you can also attach and read any new properties.

dynamic duser = user;
duser.WhatsUp = "Hella"
duser["WhatTime"] = DateTime.Now;

string wu = duser.WhatsUp;
string wt = duser["WhatTime"];
wu = duser.["WhatsUp"]; // also works
wt = duser.WhatTime;  // also works

The following sequence demonstrates in more detail:

var user = new User();

// Set strongly typed properties
user.Email = "[email protected]";
user.Password = "nonya123";
user.Name = "Rickochet";
user.Active = true;

// Now add dynamic properties
dynamic duser = user;
duser.Entered = DateTime.Now;
duser.Accesses = 1;

// you can also add dynamic props via indexer 
user["NickName"] = "AntiSocialX";
duser["WebSite"] = "http://www.west-wind.com/weblog";
        
// Access strong type through dynamic ref
Assert.AreEqual(user.Name,duser.Name);

// Access strong type through indexer 
Assert.AreEqual(user.Password,user["Password"]);
        

// access dyanmically added value through indexer
Assert.AreEqual(duser.Entered,user["Entered"]);
        
// access index added value through dynamic
Assert.AreEqual(user["NickName"],duser.NickName);
        

// loop through all properties dynamic AND strong type properties (true)
foreach (var prop in user.GetProperties(true))
{ 
    object val = prop.Value;
    if (val == null)
        val = "null";

    Console.WriteLine(prop.Key + ": " + val.ToString());
}

Serialization

The Expando class supports JSON.NET and XmlSerializer two-way serialization. So you can create objects that contain combined static and dynamic properties and have both persist and restore to and from serialized content.

// Set standard properties
var ex = new User()
{
    Name = "Rick",
    Email = "[email protected]",
    Password = "Seekrit23",
    Active = true
};

// set dynamic properties
dynamic exd = ex;
exd.Entered = DateTime.Now;
exd.Company = "West Wind";
exd.Accesses = 10;

// set dynamic properties as dictionary
ex["Address"] = "32 Kaiea";
ex["Email"] = "[email protected]";
ex["TotalOrderAmounts"] = 51233.99M;

// *** Should serialize both static properties dynamic properties
var json = JsonConvert.SerializeObject(ex, Formatting.Indented);
Console.WriteLine("*** Serialized Native object:");
Console.WriteLine(json);

Assert.IsTrue(json.Contains("Name")); // static
Assert.IsTrue(json.Contains("Company")); // dynamic


// *** Now deserialize the JSON back into object to 
// *** check for two-way serialization
var user2 = JsonConvert.DeserializeObject<User>(json);
json = JsonConvert.SerializeObject(user2, Formatting.Indented);
Console.WriteLine("*** De-Serialized User object:");
Console.WriteLine(json);

Assert.IsTrue(json.Contains("Name")); // static
Assert.IsTrue(json.Contains("Company")); // dynamic

This produces the following JSON that mixes both the static and dynamic properties as a single JSON object literal:

{
  "Email": "[email protected]",
  "Password": "Seekrit23",
  "Name": "Rick",
  "Active": true,
  "ExpiresOn": null,
  "Entered": "2015-01-28T11:22:18.3548271-10:00",
  "Company": "West Wind",
  "Accesses": 10,
  "Address": "32 Kaiea",
  "Email": "[email protected]",
  "TotalOrderAmounts": 51233.99
}

The same code using XML Serialization:

// Set standard properties
var ex = new User();
ex.Name = "Rick";
ex.Active = true;


// set dynamic properties
dynamic exd = ex;
exd.Entered = DateTime.Now;
exd.Company = "West Wind";
exd.Accesses = 10;

// set dynamic properties as dictionary
ex["Address"] = "32 Kaiea";
ex["Email"] = "[email protected]";
ex["TotalOrderAmounts"] = 51233.99M;

// Serialize creates both static and dynamic properties
// dynamic properties are serialized as a 'collection'
string xml;
SerializationUtils.SerializeObject(exd, out xml);
Console.WriteLine("*** Serialized Dynamic object:");
Console.WriteLine(xml);

Assert.IsTrue(xml.Contains("Name")); // static
Assert.IsTrue(xml.Contains("Company")); // dynamic

// Serialize
var user2 = SerializationUtils.DeSerializeObject(xml,typeof(User));
SerializationUtils.SerializeObject(exd, out xml);
Console.WriteLine(xml);

Assert.IsTrue(xml.Contains("Rick")); // static
Assert.IsTrue(xml.Contains("West Wind")); // dynamic

Produces the following XML:

<?xml version="1.0" encoding="utf-8"?>
<User xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <Properties>
      <item>
         <key>Entered</key>
         <value type="datetime">2015-01-28T11:24:27.0119386-10:00</value>
      </item>
      <item>
         <key>Company</key>
         <value>West Wind</value>
      </item>
      <item>
         <key>Accesses</key>
         <value type="integer">10</value>
      </item>
      <item>
         <key>Address</key>
         <value>32 Kaiea</value>
      </item>
      <item>
         <key>Email</key>
         <value>[email protected]</value>
      </item>
      <item>
         <key>TotalOrderAmounts</key>
         <value type="decimal">51233.99</value>
      </item>
   </Properties>
   <Name>Rick</Name>
   <Active>true</Active>
   <ExpiresOn xsi:nil="true" />
</User>

Note that the XML serializes the dynaimc properties as a collection courtesy of the PropertyBag() custom XML serializer. Although this XML schema isn't as clean as the JSON, it does work with two-way serialization to properly deserialize the object.

Downsides

There are a few issues you should be aware of when you use this class. They're not show stoppers by any means, but keep the following in mind:

Must inherit

First, you have to inherit from Expando in order to use it to extend an existing type. So you'll always introduce an extra layer of inheritance. For many use cases this isn't a problem, but if you need to inherit multiple levels this can become a problem. A work around for this is to use composition by adding an Expando object property to an existing object to provide the dynamic extensibility and mix-in features to your type.

Noisy Class Interface

This class inherits from DynamicObject and implements the required methods on that class to provide the dynamic features. These methods end up on the class interface you inherit. Not clean, but luckily most base methods group together (TryXXXX methods).

Performance

If you stick purely to the static interface of the class performance should be excellent. But once you start accessing the dynamic properties you're into late runtime binding (or at least one-time late binding) and there will be some performance hit for using dynamic properties. However, dynamic can actually be very fast after the first access of each member. It's a tradeoff for the flexibility you get. Make sure you use a static reference for the static interface where possible and explicitly use the dynamic interface when accessing the dynamic properties.

License

This Expando library is an open source and licensed under MIT license, and there's no charge to use, integrate or modify the code for this project. You are free to use it in personal, commercial, government and any other type of application. Commercial licenses are also available.

All source code is copyright West Wind Technologies, regardless of changes made to them. Any source code modifications must leave the original copyright code headers intact.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

copyright, West Wind Technologies, 2012 - 2015

expando's People

Contributors

rickstrahl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

expando's Issues

Could you add only json version (Without Xml support)

It seems a part of code is used for XML serialization operations, but XML is obsolote for most of us.
If there is performance penalty because of XML feature, could you add only json version of this great work?
Thanks.

Unity support

I'm trying to use your project in Unity 3D (added dll to the Unity project). It works for the most part but the TwoWayJsonSerializeExpandoTyped test results in

{
  "Properties": {
    "Entered": "2019-02-28T14:38:38.3122796-06:00",
    "Company": "West Wind",
    "Accesses": 10,
    "Address": "32 Kaiea",
    "TotalOrderAmounts": 51233.99
  },
  "email": "[email protected]",
  "password": "Seekrit23",
  "name": "Rick",
  "active": true
}

I tried the same test directly from Expando and get the expected result

{
  "Email": "[email protected]",
  "Password": "Seekrit23",
  "Name": "Rick",
  "Active": true,
  "ExpiresOn": null,
  "Entered": "2019-02-28T14:53:19.3184712-06:00",
  "Company": "West Wind",
  "Accesses": 10,
  "Address": "32 Kaiea",
  "TotalOrderAmounts": 51233.99
}

Any ideas why they would have different results?

Serialization of DateTimeOffset

My apologies if this isn't the correct thread, but it would appear that the Serialization/Deserialization of DateTimeOffset in an Expando object is not handled.

Thank you for such a comprehensive utilities library. I'm glad I've found it.

remove property from Westwind.Utilities.Dynamic.Expando

how to remove property from Westwind.Utilities.Dynamic.Expando

my tries but app crash
any way to remove property from object

public class  modelStatic : Westwind.Utilities.Dynamic.Expando
{
public list<string >attachments{get;set;}
....
.. // so other properties 

}
  dynamic dynamicModel = new ExpandoObject();
                dynamicModel = modelStatic;
                ((IDictionary<String, Object>)dynamicModel)?.Remove("attachments");

I want to add properties of base class to serialization, but I'm getting weird error "Parameter count mismatch."

public class A : Expando
{
   public string Prop1 { get; set; }
}

public class B : A 
{
   public string Prop2 { get; set; }
}

I want to serialize B, but only own properties are serialized, because of this line :

_InstancePropertyInfo = Instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
so I replaced it with this:

_InstancePropertyInfo = Instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public) ;
Now, I'm getting weird error during serialization:

"Parameter count mismatch."

Callstack says:

at System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, Object[] index)
   at Expando.<GetProperties>d__19.MoveNext() in ......
   at Expando.<GetDynamicMemberNames>d__9.MoveNext() in .....
   at JsonSerializerInternalWriter.SerializeDynamic(JsonWriter writer, IDynamicMetaObjectProvider value, JsonDynamicContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)

Indexer and property getters different values for strongly typed class after cast from dynamic

Inheriting from Expando is great - but if the inheriting class implements strongly typed properties, setting a property dynamically causes some problems. If the types don't agree, casting the dynamic instance back to its static type will give different values for the property depending on whether you use the indexer or property to access it. For example

class Person : Expando
{
    public string Name {get; set;}
}

void Main()
{
    dynamic ex = new Person();
    ex.Name = 100;
    var person = ex as Person;
    Console.WriteLine(person.Name);
    //>> null
    Console.WriteLine(person ["Name"]);
    //>> 100
}

NuGet Package

Is this available as a NuGet package? Westwind.Utilities doesn't seem to ship with this project.

JSON.NET [JsonRequired] is broken

As demonstrated by the test case below, the behaviour of [JsonRequired] from Json.NET is broken.

    [TestFixture]
    public class JsonRequiredTests
    {
        [Test]
        public void TestJsonRequired()
        {
            // Passes
            Assert.That(() => JsonConvert.DeserializeObject<MyTestCase>("{}"), Throws.InstanceOf<JsonSerializationException>());

            // Fails
            Assert.That(() => JsonConvert.DeserializeObject<MyTestCase2>("{}"), Throws.InstanceOf<JsonSerializationException>());
        }

        public class MyTestCase
        {
            [JsonRequired]
            public string SomeProperty { get; set; }
        }

        public class MyTestCase2 : Westwind.Utilities.Expando
        {
            [JsonRequired]
            public string SomeProperty { get; set; }
        }
    }

Concrete property and indexer return not same value

In the case of existing property - access through property or through index should return the same value, but actually an indexer is stored in Properties and not in Object

   public class ObjWithProp : Expando
{
    public string SomeProp { get; set; }
}

[TestFixture]
[Ignore]
public class ExistingProps
{

    [Test]
    public void Given_Porp_When_SetWithIndex_Then_PropsValue()
    {
        //arrange
        ObjWithProp obj = new ObjWithProp();
        //act
        obj.SomeProp = "value1";
        obj["SomeProp"] = "value2";
        //assert
        Assert.AreEqual("value2",obj.SomeProp);

    }
}

performance

I have used the library extensively on a large project where - node'js would be a good fit. I had porformance issues that I suspect trace to it - I would like to improve that - suggestions ?

I mean around 17K rows can take around couple of hours to process

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.