4

I would like to merge these two anonymous objects:

var man1 = new {
    name = new {
        first = "viet"
    },
    age = 20
};

var man2 = new {
    name = new {
        last = "vo"
    },
    address = "123 street"
};

Into a single one:

var man = new {
    name = new {
        first = "viet",
        last = "vo"
    },
    age = 20,
    address = "123 street"
};

I looked for a solution but found nothing clever.

Hoang Viet
  • 346
  • 1
  • 3
  • 10
  • 2
    imho that is a good use case of when interfaces (or using concrete classes) are necessary. You wanna get access to those properties, so you should make sure those instances garantee having the properties. That is what interfaces provide – Cleptus Jun 12 '21 at 08:26
  • What have you tried that is not "*correct*"? Have you take a look at C# 9 records? –  Jun 12 '21 at 08:34
  • 1
    I'm using VS2019 with C#8 @OlivierRogier. – Hoang Viet Jun 12 '21 at 08:38
  • 1
    You'd probably be best creating classes since both of those types are anonymous so you'd need a third type anyway. – ProgrammingLlama Jun 12 '21 at 08:43
  • What bout reflection, `Type.GetProperties`? Although this might not be the best idea, you can get the properties from both of the anonymous objects' types and compare em to see if both types have same named/typed properties and if so make a new object and copy data. – Space Jun 12 '21 at 08:43
  • 2
    I have edited your question –  Jun 12 '21 at 08:47
  • 4
    If you don't know the properties at compile time: you *could* JSON serialize and deserialize into dictionaries and merge the dictionaries easily; if you do know the types at compile time: simply create classes – Camilo Terevinto Jun 12 '21 at 08:50
  • 1
    You can't merge, as I know. You need to manually assign fields from others one by one. –  Jun 12 '21 at 08:50

2 Answers2

3

Convert the anonymous object to ExpandoObject which is essentially a dictionary of string key and object value:

var man1Expando = man1.ToDynamic();
var man2Expando = man2.ToDynamic();
public static ExpandoObject ToDynamic(this object obj)
{
    IDictionary<string, object> expando = new ExpandoObject();

    foreach (var propertyInfo in obj.GetType().GetProperties())
    {
        var currentValue = propertyInfo.GetValue(obj);
        if (propertyInfo.PropertyType.IsAnonymous())
        {
            expando.Add(propertyInfo.Name, currentValue.ToDynamic());
        }
        else
        {
            expando.Add(propertyInfo.Name, currentValue);
        }
    }

    return expando as ExpandoObject;
}

I'm using a helper extension to establish whether a type is an anonymous one:

public static bool IsAnonymous(this Type type)
{
    return type.DeclaringType is null
        && type.IsGenericType
        && type.IsSealed
        && type.IsClass
        && type.Name.Contains("Anonymous");
}

Then, merge two resulting expando objects into one, but recursively, checking for nested expando objects:

var result = MergeDictionaries(man1Expando, man2Expando, overwriteTarget: true);
public static IDictionary<string, object> MergeDictionaries(
    IDictionary<string, object> targetDictionary,
    IDictionary<string, object> sourceDictionary,
    bool overwriteTarget)
{
    foreach (var pair in sourceDictionary)
    {
        if (!targetDictionary.ContainsKey(pair.Key))
        {
            targetDictionary.Add(pair.Key, sourceDictionary[pair.Key]);
        }
        else
        {
            if (targetDictionary[pair.Key] is IDictionary<string, object> innerTargetDictionary)
            {
                if (pair.Value is IDictionary<string, object> innerSourceDictionary)
                {
                    targetDictionary[pair.Key] = MergeDictionaries(
                        innerTargetDictionary,
                        innerSourceDictionary,
                        overwriteTarget);
                }
                else
                {
                    // What to do when target propety is nested, but source is not?
                    // Who takes precedence? Target nested property or source value?
                    if (overwriteTarget)
                    {
                        // Replace target dictionary with source value.
                        targetDictionary[pair.Key] = pair.Value;
                    }
                }
            }
            else
            {
                if (pair.Value is IDictionary<string, object> innerSourceDictionary)
                {
                    // What to do when target propety is not nested, but source is?
                    // Who takes precedence? Target value or source nested value?
                    if (overwriteTarget)
                    {
                        // Replace target value with source dictionary.
                        targetDictionary[pair.Key] = innerSourceDictionary;
                    }
                }
                else
                {
                    // Both target and source are not nested.
                    // Who takes precedence? Target value or source value?
                    if (overwriteTarget)
                    {
                        // Replace target value with source value.
                        targetDictionary[pair.Key] = pair.Value;
                    }
                }
            }
        }
    }

    return targetDictionary;
}

The overwriteTarget parameter decides which object takes priority when merging.

Usage code:

var man1 = new
{
    name = new
    {
        first = "viet",
    },
    age = 20,
};

var man2 = new
{
    name = new
    {
        last = "vo",
    },
    address = "123 street",
};

var man1Expando = man1.ToDynamic();
var man2Expando = man2.ToDynamic();

dynamic result = MergeDictionaries(man1Expando, man2Expando, overwriteTarget: true);

Console.WriteLine(JsonConvert.SerializeObject(result, Formatting.Indented));

and the result:

{
  "name": {
    "first": "viet",
    "last": "vo"
  },
  "age": 20,
  "address": "123 street"
}

Notice how I assigned the result to dynamic. Leaving compiler assign the type will leave you with expando object presented as IDictionary<string, object>. With a dictionary representation, you cannot access properties in the same manner as if it was an anonymous object:

var result = MergeDictionaries(man1Expando, man2Expando, overwriteTarget: true);

result.name; // ERROR

That's why the dynamic. With dynamic you are losing compile time checking, but have two anonymous objects merged into one. You have to judge for yourself if it suits you.

Prolog
  • 2,698
  • 1
  • 18
  • 31
2

There's nothing built-in in the C# language to support your use case. Thus, the question in your title needs to be answered with "Sorry, there is no easy way".

I can offer the following alternatives:

  1. Do it manually:

    var man = new {
        name = new {
            first = man1.name.first,
            last = man2.name.first
        },
        age = man1.age,
        address = man2.address
    };
    
  2. Use a class instead of an anonymous type for the resulting type (let's call it CompleteMan). Then, you can

    • create a new instance var man = new CompleteMan(); ,
    • use reflection to collect the properties and values from your "partial men" (man1 and man2),
    • assign those values to the properties of your man.

    It's "easy" in the sense that the implementation will be fairly straight-forward, but it will still be a lot of code, and you need to take your nested types (name) into account.

    If you desperately want to avoid non-anonymous types, you could probably use an empty anonymous target object, but creating this object (var man = new { name = new { first = (string)null, last = (string)null, ...) is not really less work than creating a class in the first place.

  3. Use a dedicated dynamic data structure instead of anonymous C# classes:

Heinzi
  • 167,459
  • 57
  • 363
  • 519