I have the following code in my Program.cs:
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("clientsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"clientsettings.{host.GetSetting("environment")}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
I want to convert the result of building my configuration to JObject\Json for sending to the client. How can I do it?
and I don't want to create my custom class for my settings.
My answer: merge
public static JObject GetSettingsObject(string environmentName)
{
object[] fileNames = { "settings.json", $"settings.{environmentName}.json" };
var jObjects = new List<object>();
foreach (var fileName in fileNames)
{
var fPath = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + fileName;
if (!File.Exists(fPath))
continue;
using (var file = new StreamReader(fPath, Encoding.UTF8))
jObjects.Add(JsonConvert.DeserializeObject(file.ReadToEnd()));
}
if (jObjects.Count == 0)
throw new InvalidOperationException();
var result = (JObject)jObjects[0];
for (var i = 1; i < jObjects.Count; i++)
result.Merge(jObjects[i], new JsonMergeSettings
{
MergeArrayHandling = MergeArrayHandling.Merge
});
return result;
}
Since configuration is actually just a key value store where the keys have a certain format to represent a path, serializing it back into a JSON is not that simple.
What you could do is recursively traverse through the configuration children and write its values to a JObject. This would look like this:
public JToken Serialize(IConfiguration config)
{
JObject obj = new JObject();
foreach (var child in config.GetChildren())
{
obj.Add(child.Key, Serialize(child));
}
if (!obj.HasValues && config is IConfigurationSection section)
return new JValue(section.Value);
return obj;
}
Note that this is extremely limited in how the output looks. For example, numbers or booleans, which are valid types in JSON, will be represented as strings. And since arrays are represented through numerical key paths (e.g. key:0 and key:1), you will get property names that are strings of indexes.
Let’s take for example the following JSON:
{
"foo": "bar",
"bar": {
"a": "string",
"b": 123,
"c": true
},
"baz": [
{ "x": 1, "y": 2 },
{ "x": 3, "y": 4 }
]
}
This will be represented in configuration through the following key paths:
"foo" -> "bar"
"bar:a" -> "string"
"bar:b" -> "123"
"bar:c" -> "true"
"baz:0:x" -> "1"
"baz:0:y" -> "2"
"baz:1:x" -> "3"
"baz:1:y" -> "4"
As such, the resulting JSON for the above Serialize method would look like this:
{
"foo": "bar",
"bar": {
"a": "string",
"b": "123",
"c": "true"
},
"baz": {
"0": { "x": "1", "y": "2" },
"1": { "x": "3", "y": "4" }
}
}
So this will not allow you to get back the original representation. That being said, when reading the resulting JSON again with Microsoft.Extensions.Configuration.Json, then it will result in the same configuration object. So you can use this to store the configuration as JSON.
If you want anything prettier than that, you will have to add logic to detect array and non-string types, since both of these are not concepts of the configuration framework.
I want to merge appsettings.json and appsettings.{host.GetSetting("environment")}.json to one object [and send that to the client]
Keep in mind that environment-specific configuration files often contain secrets that shouldn’t leave the machine. This is also especially true for environment variables. If you want to transmit the configuration values, then make sure not to include the environment variables when building the configuration.
The configuration data is represented by a flattened collection of KeyValuePair<string, string>. You could create a dictionary from it and serialize that to JSON. However, that will probably not give you the desired result:
Configuration.AsEnumerable().ToDictionary(k => k.Key, v => v.Value);
Also, please take in mind that this configuration object will contain environment variables, you definitely don't want to send these to the client.
A better option might be to first bind the configuration to your POCO's and serialize those to JSON:
var appConfig = new AppConfig();
Configuration.Bind(appConfig);
var json = JsonConvert.SerializeObject(appConfig);
public class AppConfig
{
// Your settings here
public string Foo { get; set; }
public int Bar { get; set; }
}
The resultant IConfiguration object from the Build() method will encompass all of your configuration sources, and will merge based on the priority order defined by the order in which you added your config sources.
In your case this would be:
clientsettings.json
clientsettings.env.json
Environment Variables
You wont need to worry about merging sources manually or loading the files, as its already done for you.
To improve on poke's answer, I came up with this:
private JToken Serialize(IConfiguration config)
{
JObject obj = new JObject();
foreach (var child in config.GetChildren())
{
if (child.Path.EndsWith(":0"))
{
var arr = new JArray();
foreach (var arrayChild in config.GetChildren())
{
arr.Add(Serialize(arrayChild));
}
return arr;
}
else
{
obj.Add(child.Key, Serialize(child));
}
}
if (!obj.HasValues && config is IConfigurationSection section)
{
if (bool.TryParse(section.Value, out bool boolean))
{
return new JValue(boolean);
}
else if (decimal.TryParse(section.Value, out decimal real))
{
return new JValue(real);
}
else if (long.TryParse(section.Value, out int integer))
{
return new JValue(integer);
}
return new JValue(section.Value);
}
return obj;
}
The code above accounts for data types such as boolean, long & decimal.
long & decimal are the largest data types available for integers so will encompass any smaller values like short or float.
The code will also construct your arrays properly, so you end up with a like for like representation of all of your config in one json file.
Here is Tom's solution converted to use System.Text.Json.
static internal JsonNode? Serialize(IConfiguration config)
{
JsonObject obj = new();
foreach (var child in config.GetChildren())
{
if (child.Path.EndsWith(":0"))
{
var arr = new JsonArray();
foreach (var arrayChild in config.GetChildren())
{
arr.Add(Serialize(arrayChild));
}
return arr;
}
else
{
obj.Add(child.Key, Serialize(child));
}
}
if (obj.Count() == 0 && config is IConfigurationSection section)
{
if (bool.TryParse(section.Value, out bool boolean))
{
return JsonValue.Create(boolean);
}
else if (decimal.TryParse(section.Value, out decimal real))
{
return JsonValue.Create(real);
}
else if (long.TryParse(section.Value, out long integer))
{
return JsonValue.Create(integer);
}
return JsonValue.Create(section.Value);
}
return obj;
}
// Use like this...
var json = Serialize(Config);
File.WriteAllText("out.json",
json.ToJsonString(new JsonSerializerOptions() { WriteIndented = true}));
Do you really want to sent to client all your environment variables (.AddEnvironmentVariables()), connections string and all other stuff in appsettings??? I recommend you do not do this.
Instead, make one class (say ClientConfigOptions), configure it binding using services.Configure<ClientConfigOptions>(configuration.GetSection("clientConfig")) and send it to client.
With this approach, you may also tune your ClientConfigOptions with Actions, copy some values from different appsetting paths, etc.
Related
Suppose, I have some complex JSON:
{
"path1": {
"path1Inner1": {
"id": "id1"
},
"path1Inner2": {
"id": "id2"
}
},
"path2": {
"path2Inner1": {
"id": "id3"
},
"path2Inner2": {
"id": "id4",
"key": "key4"
}
}
}
And there is also some whitelist of json path expressions, for example:
$.path1.path1Inner1
$.path2.path2Inner2.key
I want to leave in the JSON tree only nodes and properties that match the "whitelist", so, the result would be:
{
"path1": {
"path1Inner1": {
"id": "id1"
}
},
"path2": {
"path2Inner2": {
"key": "key4"
}
}
}
I.e. this is not just a selection by JSON path (which is a trivial task) but the nodes and properties have to keep the initial place in the source JSON tree.
First of all, I have many thanks for this and this answers. They became the starting point of my analysis of the problem.
Those answers present two different approaches in achieving the goal of "whitelisting" by paths. The first one rebuilds the whitelist paths structure from scratch (i.e. starting from the empty object creates the needed routes). The implementation parses the string paths and tries to rebuild the tree based on the parsed path. This approach needs very handy work of considering all possible types of paths and therefore might be error-prone. You can find some of the mistakes I have found in my comment to the answer.
The second approach is based on the json.net object tree API (Parent, Ancestors, Descendants, etc. etc.). The algorithm traverses the tree and removes paths that are not "whitelisted". I find that approach much easier and much less error-prone as well as supporting the wide range of cases "in one go".
The algorithm I have implemented is in many points similar to the second answer but, I think, is much easier in implementation and understanding. Also, I don't think it is worse in its performance.
public static class JsonExtensions
{
public static TJToken RemoveAllExcept<TJToken>(this TJToken token, IEnumerable<string> paths) where TJToken : JContainer
{
HashSet<JToken> nodesToRemove = new(ReferenceEqualityComparer.Instance);
HashSet<JToken> nodesToKeep = new(ReferenceEqualityComparer.Instance);
foreach (var whitelistedToken in paths.SelectMany(token.SelectTokens))
TraverseTokenPath(whitelistedToken, nodesToRemove, nodesToKeep);
//In that case neither path from paths has returned any token
if (nodesToKeep.Count == 0)
{
token.RemoveAll();
return token;
}
nodesToRemove.ExceptWith(nodesToKeep);
foreach (var notWhitelistedNode in nodesToRemove)
notWhitelistedNode.Remove();
return token;
}
private static void TraverseTokenPath(JToken value, ISet<JToken> nodesToRemove, ISet<JToken> nodesToKeep)
{
JToken? immediateValue = value;
do
{
nodesToKeep.Add(immediateValue);
if (immediateValue.Parent is JObject or JArray)
{
foreach (var child in immediateValue.Parent.Children())
if (!ReferenceEqualityComparer.Instance.Equals(child, value))
nodesToRemove.Add(child);
}
immediateValue = immediateValue.Parent;
} while (immediateValue != null);
}
}
To compare the JToken instances it's necessary to use reference equality comparer since some of JToken types use "by value" comparison like JValue does. Otherwise, you could get buggy behaviour in some cases.
For example, having source JSON
{
"path2":{
"path2Inner2":[
"id",
"id"
]
}
}
and a path $..path2Inner2[0] you will get the result JSON
{
"path2":{
"path2Inner2":[
"id",
"id"
]
}
}
instead of
{
"path2":{
"path2Inner2":[
"id"
]
}
}
As far as .net 5.0 is concerned the standard ReferenceEqualityComparer can be used. If you use an earlier version of .net you might need to implement it.
Let's suppose that you have a valid json inside a sample.json file:
{
"path1": {
"path1Inner1": {
"id": "id1"
},
"path1Inner2": {
"id": "id2"
}
},
"path2": {
"path2Inner1": {
"id": "id3"
},
"path2Inner2": {
"id": "id4",
"key": "key4"
}
}
}
Then you can achieve the desired output with the following program:
static void Main()
{
var whitelist = new[] { "$.path1.path1Inner1", "$.path2.path2Inner2.key" };
var rawJson = File.ReadAllText("sample.json");
var semiParsed = JObject.Parse(rawJson);
var root = new JObject();
foreach (var path in whitelist)
{
var value = semiParsed.SelectToken(path);
if (value == null) continue; //no node exists under the path
var toplevelNode = CreateNode(path, value);
root.Merge(toplevelNode);
}
Console.WriteLine(root);
}
We read the json file and semi parse it to a JObject
We define a root where will merge the processing results
We iterate through the whitelisted json paths to process them
We retrieve the actual value of the node (specified by the path) via the SelectToken call
If the path is pointing to a non-existing node then SelectToken returns null
Then we create a new JObject which contains the full hierarchy and the retrieved value
Finally we merge that object to the root
Now let's see the two helper methods
static JObject CreateNode(string path, JToken value)
{
var entryLevels = path.Split('.').Skip(1).Reverse().ToArray();
return CreateHierarchy(new Queue<string>(entryLevels), value);
}
We split the path by dots and remove the first element ($)
We reverse the order to be able to put it into a Queue
We want to build up the hierarchy from inside out
Finally we call a recursive function with the queue and the retrieved value
static JObject CreateHierarchy(Queue<string> pathLevels, JToken currentNode)
{
if (pathLevels.Count == 0) return currentNode as JObject;
var newNode = new JObject(new JProperty(pathLevels.Dequeue(), currentNode));
return CreateHierarchy(pathLevels, newNode);
}
We first define the exit condition to make sure that we will not create an infinite recursion
We create a new JObject where we specify the name and value
The output of the program will be the following:
{
"path1": {
"path1Inner1": {
"id": "id1"
}
},
"path2": {
"path2Inner2": {
"key": "key4"
}
}
}
I'm running an ASP.net MVC4 application with Knockout.
I have a generic script that posts my Knockout Forms.
I need to optimize the data sent to the server, because when i post my Knockout ViewModel, SelectList with all items are posted too!
Example Server ViewModel :
Public Class FooViewModel
Public Property Bar As String
Public Property Products As List(Of SelectListItem)
End Class
The JS code to convert my Knockout ViewModel to JSON
var data = ko.toJSON(viewModel);
data variable contains all products items and that's not very optimized.
I found this code (which work) :
viewModel.toJSON = function () {
var copy = ko.toJS(this);
// remove any unneeded properties
delete copy.Products;
return copy;
}
But I need a generic solution ... ! And here I don't see how i can make it generic ...
A quick and dirty solution would be to add a suffix on every array properties like "_NoPost" and then loop and delete every property that has this suffix, but it smells ... bad :/
Any thoughts ?
The one option is to separate your form data from your lookup data like the following. This will allow you to get hold of only your form data when you need to post it to the server.
Public Class FormViewModel
Public Property Bar As String
End Class
Public Class FooViewModel
Public Property FormData As FormViewModel
Public Property Products As List(Of SelectListItem)
End Class
Which will allow you to
var data = ko.toJSON(viewModel);
$post(url, data.FormData, function(d){...});
In your HTML you will also have to include the FormData as part of the variable i.e.
<input data-bind="value: FormData.Bar">
EDIT
Based on your feedback you can use the following function to construct a "clean" object for you. The idea is to pass in the original JSON object as well as a mapping object which will indicate which of the properties should be excluded/left behind:
function MapJson(obj, map) {
if (obj == undefined)
return;
map = map || {};
var ret = {};
for (var prop in obj) {
if (map[prop] != undefined && map[prop] == false)
continue;
if (typeof obj[prop] !== "object")
ret[prop] = obj[prop];
else {
if (map.constructor == Array) {
ret[prop] = MapJson(obj[prop], map[0]);
}
else
ret[prop] = MapJson(obj[prop], map[prop]);
}
}
return ret;
}
You can then use it like this - by setting the property's value to false it will be excluded from the data. The sample shows how to block both an array within a child object as well as an array within an array:
var obj = {
Name: "John Doe",
Vehicle: {
Details: {
Make: "Mazda",
Model: 2010
},
Registration: "ABC123",
ServiceDates: ["01 Jan", "23 Feb", "13 March"]
},
WeekDays: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
Children: [{ Name: "Mary", Age: 4, Hobbies: ["Soccer", "Chess", "Swim"] }, { Name: "Jane", Age: 2, Hobbies: ["Tennis", "Movies", "Reading"] }]
};
var map = {
Vehicle: {
ServiceDates: false
},
Children: [{
Hobbies: false,
}]
};
MapJson(obj, map);
Hope it helps.
EDIT 2
Herewith a working sample based on the data you posted in your comment.
var vm = {
"Type":"PropertyTax",
"Label":"d",
"StartDate":"2015-01-01T00:00:00",
"EndDate":"2015-12-31T00:00:00",
"Value":0,
"RegularizationMonth":0,
"TotalConsumption":null,
"UnitPrice":null,
"Active":true,"Products":[{"Selected":false,"Text":"XXX 39","Value":"28"},{"Selected":false,"Text":"ZZZ","Value":"38"}],"ChargeProducts":[{"ProductID":"28","Products":[{"Selected":false,"Text":"XXX 39","Value":"28"},{"Selected":false,"Text":"XXX 41","Value":"8"}]}],
"map":{"Products":false,"ChargeProducts":[{"Products":false}]}
};
var result = MapJson(vm, vm.map);
console.log("Result: ", result);
If you use KO.Mapping, you can choose certain pieces to ignore.
var mapping = { 'ignore': ["SomeFieldProperty"] };
ko.mapping.fromJS(data, mapping, viewModel);
This section in the documentation lists all the ways you can manipulate the bindings coming and going, for ko.mapping:
http://knockoutjs.com/documentation/plugins-mapping.html
Scroll down to the bottom for the bits on ignore as well as topics covering how you can manage bindings and debinding.
I have the following:
request url: 'endpoint/1,2,3?q=foo'
action to which the request is bound:
public object Bar([ModelBinder] List< T > ids, [FromUri] string q)
I want to map the "1,2,3" fragment to the "ids" parameter, so I created a ModelBinderProvider according to this link, which should call the proper model binder.
public class MyModelBinderProvider: ModelBinderProvider
{
public override IModelBinder GetBinder(HttpConfiguration configuration, Type modelType)
{
IModelBinder modelBinder = null;
if (modelType.IsGenericType && (modelType.GetGenericTypeDefinition() == typeof(List<>)))
{
modelBinder = new ListModelBinder();
}
return modelBinder;
}
}
I registered the provider in Global.asax like this:
GlobalConfiguration.Configuration.Services.Insert(typeof(ModelBinderProvider), 0, new MyModelBinderProvider());
The reason: I created this provider because I want, no matter what T is ('1,2,3' or 'one,two,three'), the bind to work.
The problem:
Let' say T is 'int'; everytime a request is sent, the 'modelType' paramater is always 'int' and not what I expect - 'List< int >', so the request is not properly handled.
The weird thing: Doing something like this works but T is specialized and therefor not what I want:
var simpleProvider = new SimpleModelBinderProvider(typeof(List<int>), new ListModelBinder());
GlobalConfiguration.Configuration.Services.Insert(typeof(ModelBinderProvider), 0, simpleProvider);
I cannot see what I'm doing wrong, why is the 'modelType' parameter not the expected value?
It is a very old question but I had an similar issue here with a legacy code.
Commas are reserved and it should be avoided although they work in some cases but if you really want to use them...
I think that is more a route issue than a model binder once the "1,2,3" is path part of the url. Assuming this I wrote a small RouteHandler that does the trick (please forgive the very simple "word to integer" translator).
The CsvRouteHandler gets the id array from URL and put it on RouteData as an array of integers. If the original array has words such as one, two or three it translates each value to int.
MvcRouteHandler
protected override IHttpHandler GetHttpHandler(System.Web.Routing.RequestContext requestContext)
{
var idArrayParameter = requestContext.RouteData.Values["idArray"] != null ? requestContext.RouteData.Values["idArray"].ToString() : null;
if (string.IsNullOrEmpty(idArrayParameter))
{
return base.GetHttpHandler(requestContext);
}
requestContext.RouteData.Values.Remove("idArray"); // remove the old array from routedata
// Note: it is horrible and bugged but and you probably have your own translation method :)
string[] idArray = idArrayParameter.Split(',');
int[] ids = new int[idArray.Length];
for(int i = 0; i < idArray.Length; i++)
{
if (!int.TryParse(idArray[i], out ids[i]))
{
switch (idArray[i])
{
case "one":
ids[i] = 1;
break;
case "two":
ids[i] = 2;
break;
case "three":
ids[i] = 3;
break;
}
}
}
requestContext.RouteData.Values.Add("Id", ids);
return base.GetHttpHandler(requestContext);
}
}
Route configuration:
routes.Add(
name: "Id Array Route",
item: new Route(
url: "endpoint/{idArray}",
defaults: new RouteValueDictionary(new { controller = "Test", action = "Index" }),
routeHandler: new CsvRouteHandler())
);
I have a json as below :
"[{"a":"b","c":"d"},{"a":"e","c":"f"},{"a":"g","c":"h"}]"
now I want to deserilize this into a list of objects of anonymous type "foo"
var foo=new { a=string.empty , c=string.empty };
the code is :
ServiceStackJsonSerializer Jserializer = new ServiceStackJsonSerializer();
dynamic foos = Jserializer.Deserialize<List<foo.GetType()>>(jsonString);
but not working .
update :
replacing ServiceStack with JavascriptSerializer and passing dictionary[] solved the problem without need to anonymous Type
JavaScriptSerializer jSerializer = new JavaScriptSerializer();
var Foos = jSerializer.Deserialize<Dictionary<string, object>[]>(jsonString);
I don't know what the Jserializer class is, but I do know of the JavaScriptSerializer class. Unfortunately, it doesn't support deserialization into anonymous types. You'll have to create a concrete type like this:
class Foo
{
public string a { get; set; }
public string c { get; set; }
}
Using the following code worked for me:
const string json =
#"[{""a"":""b"",""c"":""d""},{""a"":""e"",""c"":""f""},{""a"":""g"",""c"":""h""}]";
var foos = new JavaScriptSerializer().Deserialize<Foo[]>(json);
the variable foos will contain an array of Foo instances.
There are multiple ways you can dynamically parse JSON with ServiceStack's JsonSerializer e.g:
var json = "[{\"a\":\"b\",\"c\":\"d\"},{\"a\":\"e\",\"c\":\"f\"},{\"a\":\"g\",\"c\":\"h\"}]";
var dictionary = json.FromJson<List<Dictionary<string, string>>>();
".NET Collections:".Print();
dictionary.PrintDump();
List<JsonObject> map = JsonArrayObjects.Parse(json);
"Dynamically with JsonObject:".Print();
map.PrintDump();
Which uses ServiceStack's T.Dump() extension method to print out:
.NET Collections:
[
{
a: b,
c: d
},
{
a: e,
c: f
},
{
a: g,
c: h
}
]
Dynamically with JsonObject:
[
{
a: b,
c: d
},
{
a: e,
c: f
},
{
a: g,
c: h
}
]
For what you are trying to do it sounds like json.net would be a better fit. See this question
Deserialize json object into dynamic object using Json.net
The below example can deserialize JSON to a list of anonymous objects using NewtonSoft.Json's DeserializeAnonymousType method.
var json = System.IO.File.ReadAllText(#"C:\TestJSONFiles\yourJSONFile.json");
var fooDefinition = new { a = "", b = 0 }; // type with fields of string, int
var fooListDefinition = Enumerable.Range(0, 0).Select(e => fooDefinition).ToList();
var foos = JsonConvert.DeserializeAnonymousType(json, fooListDefinition);
I'm trying to verify a method call using Moq, but I can't quite get the syntax right. Currently, I've got this as my verify:
repository.Verify(x => x.ExecuteNonQuery("fav_AddFavorites", new
{
fid = 123,
inputStr = "000456"
}), Times.Once());
The code compiles, but the test fails with the error:
Expected invocation on the mock once, but was 0 times:
x => x.ExecuteNonQuery("fav_AddFavorites", new <>f__AnonymousType0<Int32, String>(123, "000456"))
No setups configured.
Performed invocations:
IRepository.ExecuteNonQuery("fav_AddFavorites", { fid = 123, inputStr = 000456 })
How can I verify the method call and match the method parameters for an anonymous type?
UPDATE
To answer the questions:
I am trying to verify both that the method was called and that the parameters are correct.
The signature of the method I'm trying to verify is:
int ExecuteNonQuery(string query, object param = null);
The setup code is simply:
repository = new Mock<IRepository>();
UPDATE 2
It looks like this is a problem with Moq and how it handles anonymous types in .Net. The code posted by Paul Matovich runs fine, however, once the code and the test are in different assemblies the test fails.
This Passes
public class Class1
{
private Class2 _Class2;
public Class1(Class2 class2)
{
_Class2 = class2;
}
public void DoSomething(string s)
{
_Class2.ExecuteNonQuery(s, new { fid = 123, inputStr = "000456" });
}
}
public class Class2
{
public virtual void ExecuteNonQuery(string s, object o)
{
}
}
/// <summary>
///A test for ExecuteNonQuery
///</summary>
[TestMethod()]
public void ExecuteNonQueryTest()
{
string testString = "Hello";
var Class2Stub = new Mock<Class2>();
Class1 target = new Class1(Class2Stub.Object);
target.DoSomething(testString);
Class2Stub.Verify(x => x.ExecuteNonQuery(testString, It.Is<object>(o => o.Equals(new { fid = 123, inputStr = "000456" }))), Times.Once());
}
##Update##
That is strange, it doesn't work in different assemblies. Someone can give us the long definition about why the object.equals from different assemblies behaves differently, but for different assemblies, this will work, any variance in the object values will return a different hash code.
Class2Stub.Verify(x => x.ExecuteNonQuery(testString, It.Is<object>(o => o.GetHashCode() == (new { fid = 123, inputStr = "000456" }).GetHashCode())), Times.Once());
One option is to "verify" it in a Callback. Obviously this needs to be done at Setup time, e.g.:
aMock.Setup(x => x.Method(It.IsAny<object>())).Callback<object>(
(p1) =>
{
dynamic o = p1;
Assert.That(o.Name, Is.EqualTo("Bilbo"));
});
None of the answers are great when your test assembly is different than the system under test's assembly (really common). Here's my solution that uses JSON serialization and then strings comparison.
Test Helper Function:
using Newtonsoft.Json;
public static class VerifyHelper
{
public static bool AreEqualObjects(object expected, object actual)
{
var expectedJson = JsonConvert.SerializeObject(expected);
var actualJson = JsonConvert.SerializeObject(actual);
return expectedJson == actualJson;
}
}
Example System Under Test:
public void DoWork(string input)
{
var obj = new { Prop1 = input };
dependency.SomeDependencyFunction(obj);
}
Example Unit Test:
var expectedObject = new { Prop1 = "foo" };
sut.DoWork("foo");
dependency.Verify(x => x.SomeDependencyFunction(It.Is<object>(y => VerifyHelper.AreEqualObjects(expectedObject, y))), Times.Once());
This solution is really simple, and I think makes the unit test easier to understand as opposed to the other answers in this thread. However, because it using simple string comparison, the test's anonymous object has to be set up exactly the same as the system under the test's anonymous object. Ergo, let's say you only cared to verify the value of a single property, but your system under test sets additional properties on the anonymous object, your unit test will need to set all those other properties (and in the same exact order) for the helper function to return true.
I created a reusable method based on Pauls answer:
object ItIsAnonymousObject(object value)
{
return It.Is<object>(o => o.GetHashCode() == value.GetHashCode());
}
...
dependency.Verify(
x => x.SomeDependencyFunction(ItIsAnonymousObject(new { Prop1 = "foo" })),
Times.Once());
Also, this can be used for property name case-insensitive comparison:
protected object ItIsAnonymousObject(object value)
{
var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
return It.Is<object>(o => JsonSerializer.Serialize(o, options) == JsonSerializer.Serialize(value, options));
}