mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-01-16 15:16:53 +01:00
Support for partial JSON matching (#539)
* support Json partial match with JsonPartialMatcher * fix erroneous filenames * add newline * newlines fix * add JsonPartialMatcher to mapper * curly braces for ifs * fix JToken type comparison * more test cases * rename AreEqual -> IsMatch + more test cases * separate tests for JPath matcher values Co-authored-by: Gleb Osokin <gleb.osokin@avira.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Collections;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
@@ -17,7 +18,7 @@ namespace WireMock.Matchers
|
||||
public object Value { get; }
|
||||
|
||||
/// <inheritdoc cref="IMatcher.Name"/>
|
||||
public string Name => "JsonMatcher";
|
||||
public virtual string Name => "JsonMatcher";
|
||||
|
||||
/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
@@ -29,6 +30,7 @@ namespace WireMock.Matchers
|
||||
public bool ThrowException { get; }
|
||||
|
||||
private readonly JToken _valueAsJToken;
|
||||
private readonly Func<JToken, JToken> _jTokenConverter;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonMatcher"/> class.
|
||||
@@ -67,6 +69,9 @@ namespace WireMock.Matchers
|
||||
|
||||
Value = value;
|
||||
_valueAsJToken = ConvertValueToJToken(value);
|
||||
_jTokenConverter = ignoreCase
|
||||
? (Func<JToken, JToken>)Rename
|
||||
: jToken => jToken;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IObjectMatcher.IsMatch"/>
|
||||
@@ -81,7 +86,9 @@ namespace WireMock.Matchers
|
||||
{
|
||||
var inputAsJToken = ConvertValueToJToken(input);
|
||||
|
||||
match = DeepEquals(_valueAsJToken, inputAsJToken);
|
||||
match = IsMatch(
|
||||
_jTokenConverter(_valueAsJToken),
|
||||
_jTokenConverter(inputAsJToken));
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
@@ -95,6 +102,17 @@ namespace WireMock.Matchers
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the input against the matcher value
|
||||
/// </summary>
|
||||
/// <param name="value">Matcher value</param>
|
||||
/// <param name="input">Input value</param>
|
||||
/// <returns></returns>
|
||||
protected virtual bool IsMatch(JToken value, JToken input)
|
||||
{
|
||||
return JToken.DeepEquals(value, input);
|
||||
}
|
||||
|
||||
private static JToken ConvertValueToJToken(object value)
|
||||
{
|
||||
// Check if JToken, string, IEnumerable or object
|
||||
@@ -114,19 +132,6 @@ namespace WireMock.Matchers
|
||||
}
|
||||
}
|
||||
|
||||
private bool DeepEquals(JToken value, JToken input)
|
||||
{
|
||||
if (!IgnoreCase)
|
||||
{
|
||||
return JToken.DeepEquals(value, input);
|
||||
}
|
||||
|
||||
JToken renamedValue = Rename(value);
|
||||
JToken renamedInput = Rename(input);
|
||||
|
||||
return JToken.DeepEquals(renamedValue, renamedInput);
|
||||
}
|
||||
|
||||
private static string ToUpper(string input)
|
||||
{
|
||||
return input?.ToUpperInvariant();
|
||||
|
||||
87
src/WireMock.Net/Matchers/JsonPartialMatcher.cs
Normal file
87
src/WireMock.Net/Matchers/JsonPartialMatcher.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace WireMock.Matchers
|
||||
{
|
||||
/// <summary>
|
||||
/// JsonPartialMatcher
|
||||
/// </summary>
|
||||
public class JsonPartialMatcher : JsonMatcher
|
||||
{
|
||||
/// <inheritdoc cref="IMatcher.Name"/>
|
||||
public override string Name => "JsonPartialMatcher";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonPartialMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="value">The string value to check for equality.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
|
||||
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
|
||||
public JsonPartialMatcher([NotNull] string value, bool ignoreCase = false, bool throwException = false)
|
||||
: base(value, ignoreCase, throwException)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonPartialMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="value">The object value to check for equality.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
|
||||
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
|
||||
public JsonPartialMatcher([NotNull] object value, bool ignoreCase = false, bool throwException = false)
|
||||
: base(value, ignoreCase, throwException)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonPartialMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="value">The value to check for equality.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
|
||||
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
|
||||
public JsonPartialMatcher(MatchBehaviour matchBehaviour, [NotNull] object value, bool ignoreCase = false, bool throwException = false)
|
||||
: base(matchBehaviour, value, ignoreCase, throwException)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool IsMatch(JToken value, JToken input)
|
||||
{
|
||||
if (value == null || value == input)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (input == null || value.Type != input.Type)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (value.Type)
|
||||
{
|
||||
case JTokenType.Object:
|
||||
var nestedValues = value.ToObject<Dictionary<string, JToken>>();
|
||||
return nestedValues?.Any() != true ||
|
||||
nestedValues.All(pair => IsMatch(pair.Value, input.SelectToken(pair.Key)));
|
||||
|
||||
case JTokenType.Array:
|
||||
var valuesArray = value.ToObject<JToken[]>();
|
||||
var tokenArray = input.ToObject<JToken[]>();
|
||||
|
||||
if (valuesArray?.Any() != true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return tokenArray?.Any() == true &&
|
||||
valuesArray.All(subFilter => tokenArray.Any(subToken => IsMatch(subFilter, subToken)));
|
||||
|
||||
default:
|
||||
return value.ToString() == input.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,10 @@ namespace WireMock.Serialization
|
||||
object value = matcher.Pattern ?? matcher.Patterns;
|
||||
return new JsonMatcher(matchBehaviour, value, ignoreCase, throwExceptionWhenMatcherFails);
|
||||
|
||||
case "JsonPartialMatcher":
|
||||
object matcherValue = matcher.Pattern ?? matcher.Patterns;
|
||||
return new JsonPartialMatcher(matchBehaviour, matcherValue, ignoreCase, throwExceptionWhenMatcherFails);
|
||||
|
||||
case "JsonPathMatcher":
|
||||
return new JsonPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user