@@ -7,7 +7,7 @@ namespace FluentAssertions.Json
77{
88 internal static class JTokenDifferentiator
99 {
10- public static Difference FindFirstDifference ( JToken actual , JToken expected )
10+ public static Difference FindFirstDifference ( JToken actual , JToken expected , bool ignoreExtraProperties )
1111 {
1212 var path = new JPath ( ) ;
1313
@@ -26,50 +26,100 @@ public static Difference FindFirstDifference(JToken actual, JToken expected)
2626 return new Difference ( DifferenceKind . ExpectedIsNull , path ) ;
2727 }
2828
29- return FindFirstDifference ( actual , expected , path ) ;
29+ return FindFirstDifference ( actual , expected , path , ignoreExtraProperties ) ;
3030 }
3131
32- private static Difference FindFirstDifference ( JToken actual , JToken expected , JPath path )
32+ private static Difference FindFirstDifference ( JToken actual , JToken expected , JPath path , bool ignoreExtraProperties )
3333 {
3434 switch ( actual )
3535 {
3636 case JArray actualArray :
37- return FindJArrayDifference ( actualArray , expected , path ) ;
38- case JObject actualObbject :
39- return FindJObjectDifference ( actualObbject , expected , path ) ;
37+ return FindJArrayDifference ( actualArray , expected , path , ignoreExtraProperties ) ;
38+ case JObject actualObject :
39+ return FindJObjectDifference ( actualObject , expected , path , ignoreExtraProperties ) ;
4040 case JProperty actualProperty :
41- return FindJPropertyDifference ( actualProperty , expected , path ) ;
41+ return FindJPropertyDifference ( actualProperty , expected , path , ignoreExtraProperties ) ;
4242 case JValue actualValue :
4343 return FindValueDifference ( actualValue , expected , path ) ;
4444 default :
4545 throw new NotSupportedException ( ) ;
4646 }
4747 }
4848
49- private static Difference FindJArrayDifference ( JArray actualArray , JToken expected , JPath path )
49+ private static Difference FindJArrayDifference ( JArray actualArray , JToken expected , JPath path ,
50+ bool ignoreExtraProperties )
5051 {
5152 if ( ! ( expected is JArray expectedArray ) )
5253 {
5354 return new Difference ( DifferenceKind . OtherType , path , Describe ( actualArray . Type ) , Describe ( expected . Type ) ) ;
5455 }
55-
56- return CompareItems ( actualArray , expectedArray , path ) ;
56+
57+ if ( ignoreExtraProperties )
58+ {
59+ return CompareExpectedItems ( actualArray , expectedArray , path ) ;
60+ }
61+ else
62+ {
63+ return CompareItems ( actualArray , expectedArray , path ) ;
64+ }
65+ }
66+
67+ private static Difference CompareExpectedItems ( JArray actual , JArray expected , JPath path )
68+ {
69+ JToken [ ] actualChildren = actual . Children ( ) . ToArray ( ) ;
70+ JToken [ ] expectedChildren = expected . Children ( ) . ToArray ( ) ;
71+
72+ int matchingIndex = 0 ;
73+ for ( int expectedIndex = 0 ; expectedIndex < expectedChildren . Length ; expectedIndex ++ )
74+ {
75+ var expectedChild = expectedChildren [ expectedIndex ] ;
76+ bool match = false ;
77+ for ( int actualIndex = matchingIndex ; actualIndex < actualChildren . Length ; actualIndex ++ )
78+ {
79+ var difference = FindFirstDifference ( actualChildren [ actualIndex ] , expectedChild , true ) ;
80+
81+ if ( difference == null )
82+ {
83+ match = true ;
84+ matchingIndex = actualIndex + 1 ;
85+ break ;
86+ }
87+ }
88+
89+ if ( ! match )
90+ {
91+ if ( matchingIndex >= actualChildren . Length )
92+ {
93+ if ( actualChildren . Any ( actualChild => FindFirstDifference ( actualChild , expectedChild , true ) == null ) )
94+ {
95+ return new Difference ( DifferenceKind . WrongOrder , path . AddIndex ( expectedIndex ) ) ;
96+ }
97+
98+ return new Difference ( DifferenceKind . ActualMissesElement , path . AddIndex ( expectedIndex ) ) ;
99+ }
100+
101+ return FindFirstDifference ( actualChildren [ matchingIndex ] , expectedChild ,
102+ path . AddIndex ( expectedIndex ) , true ) ;
103+ }
104+ }
105+
106+ return null ;
57107 }
58108
59109 private static Difference CompareItems ( JArray actual , JArray expected , JPath path )
60110 {
61- JEnumerable < JToken > actualChildren = actual . Children ( ) ;
62- JEnumerable < JToken > expectedChildren = expected . Children ( ) ;
111+ JToken [ ] actualChildren = actual . Children ( ) . ToArray ( ) ;
112+ JToken [ ] expectedChildren = expected . Children ( ) . ToArray ( ) ;
63113
64- if ( actualChildren . Count ( ) != expectedChildren . Count ( ) )
114+ if ( actualChildren . Length != expectedChildren . Length )
65115 {
66- return new Difference ( DifferenceKind . DifferentLength , path , actualChildren . Count ( ) , expectedChildren . Count ( ) ) ;
116+ return new Difference ( DifferenceKind . DifferentLength , path , actualChildren . Length , expectedChildren . Length ) ;
67117 }
68118
69- for ( int i = 0 ; i < actualChildren . Count ( ) ; i ++ )
119+ for ( int i = 0 ; i < actualChildren . Length ; i ++ )
70120 {
71- Difference firstDifference = FindFirstDifference ( actualChildren . ElementAt ( i ) , expectedChildren . ElementAt ( i ) ,
72- path . AddIndex ( i ) ) ;
121+ Difference firstDifference = FindFirstDifference ( actualChildren [ i ] , expectedChildren [ i ] ,
122+ path . AddIndex ( i ) , false ) ;
73123
74124 if ( firstDifference != null )
75125 {
@@ -80,17 +130,18 @@ private static Difference CompareItems(JArray actual, JArray expected, JPath pat
80130 return null ;
81131 }
82132
83- private static Difference FindJObjectDifference ( JObject actual , JToken expected , JPath path )
133+ private static Difference FindJObjectDifference ( JObject actual , JToken expected , JPath path , bool ignoreExtraProperties )
84134 {
85135 if ( ! ( expected is JObject expectedObject ) )
86136 {
87137 return new Difference ( DifferenceKind . OtherType , path , Describe ( actual . Type ) , Describe ( expected . Type ) ) ;
88138 }
89139
90- return CompareProperties ( actual ? . Properties ( ) , expectedObject . Properties ( ) , path ) ;
140+ return CompareProperties ( actual ? . Properties ( ) , expectedObject . Properties ( ) , path , ignoreExtraProperties ) ;
91141 }
92142
93- private static Difference CompareProperties ( IEnumerable < JProperty > actual , IEnumerable < JProperty > expected , JPath path )
143+ private static Difference CompareProperties ( IEnumerable < JProperty > actual , IEnumerable < JProperty > expected , JPath path ,
144+ bool ignoreExtraProperties )
94145 {
95146 var actualDictionary = actual ? . ToDictionary ( p => p . Name , p => p . Value ) ?? new Dictionary < string , JToken > ( ) ;
96147 var expectedDictionary = expected ? . ToDictionary ( p => p . Name , p => p . Value ) ?? new Dictionary < string , JToken > ( ) ;
@@ -105,7 +156,7 @@ private static Difference CompareProperties(IEnumerable<JProperty> actual, IEnum
105156
106157 foreach ( KeyValuePair < string , JToken > actualPair in actualDictionary )
107158 {
108- if ( ! expectedDictionary . ContainsKey ( actualPair . Key ) )
159+ if ( ! ignoreExtraProperties && ! expectedDictionary . ContainsKey ( actualPair . Key ) )
109160 {
110161 return new Difference ( DifferenceKind . ExpectedMissesProperty , path . AddProperty ( actualPair . Key ) ) ;
111162 }
@@ -116,7 +167,7 @@ private static Difference CompareProperties(IEnumerable<JProperty> actual, IEnum
116167 JToken actualValue = actualDictionary [ expectedPair . Key ] ;
117168
118169 Difference firstDifference = FindFirstDifference ( actualValue , expectedPair . Value ,
119- path . AddProperty ( expectedPair . Key ) ) ;
170+ path . AddProperty ( expectedPair . Key ) , ignoreExtraProperties ) ;
120171
121172 if ( firstDifference != null )
122173 {
@@ -127,7 +178,8 @@ private static Difference CompareProperties(IEnumerable<JProperty> actual, IEnum
127178 return null ;
128179 }
129180
130- private static Difference FindJPropertyDifference ( JProperty actualProperty , JToken expected , JPath path )
181+ private static Difference FindJPropertyDifference ( JProperty actualProperty , JToken expected , JPath path ,
182+ bool ignoreExtraProperties )
131183 {
132184 if ( ! ( expected is JProperty expectedProperty ) )
133185 {
@@ -139,7 +191,7 @@ private static Difference FindJPropertyDifference(JProperty actualProperty, JTok
139191 return new Difference ( DifferenceKind . OtherName , path ) ;
140192 }
141193
142- return FindFirstDifference ( actualProperty . Value , expectedProperty . Value , path ) ;
194+ return FindFirstDifference ( actualProperty . Value , expectedProperty . Value , path , ignoreExtraProperties ) ;
143195 }
144196
145197 private static Difference FindValueDifference ( JValue actualValue , JToken expected , JPath path )
@@ -252,6 +304,10 @@ public override string ToString()
252304 return $ "misses property { Path } ";
253305 case DifferenceKind . ExpectedMissesProperty :
254306 return $ "has extra property { Path } ";
307+ case DifferenceKind . ActualMissesElement :
308+ return $ "misses expected element { Path } ";
309+ case DifferenceKind . WrongOrder :
310+ return $ "has expected element { Path } in the wrong order";
255311 default :
256312 throw new ArgumentOutOfRangeException ( ) ;
257313 }
@@ -298,6 +354,8 @@ internal enum DifferenceKind
298354 OtherValue ,
299355 DifferentLength ,
300356 ActualMissesProperty ,
301- ExpectedMissesProperty
357+ ExpectedMissesProperty ,
358+ ActualMissesElement ,
359+ WrongOrder
302360 }
303361}
0 commit comments