using System;
using System.Collections;
using System.Linq;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WireMock.Util;
using Stef.Validation;
namespace WireMock.Matchers
{
///
/// JsonMatcher
///
public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher
{
///
public object Value { get; }
///
public virtual string Name => "JsonMatcher";
///
public MatchBehaviour MatchBehaviour { get; }
///
public bool IgnoreCase { get; }
///
public bool ThrowException { get; }
private readonly JToken _valueAsJToken;
private readonly Func _jTokenConverter;
///
/// Initializes a new instance of the class.
///
/// The string value to check for equality.
/// Ignore the case from the PropertyName and PropertyValue (string only).
/// Throw an exception when the internal matching fails because of invalid input.
public JsonMatcher([NotNull] string value, bool ignoreCase = false, bool throwException = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, throwException)
{
}
///
/// Initializes a new instance of the class.
///
/// The object value to check for equality.
/// Ignore the case from the PropertyName and PropertyValue (string only).
/// Throw an exception when the internal matching fails because of invalid input.
public JsonMatcher([NotNull] object value, bool ignoreCase = false, bool throwException = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, throwException)
{
}
///
/// Initializes a new instance of the class.
///
/// The match behaviour.
/// The value to check for equality.
/// Ignore the case from the PropertyName and PropertyValue (string only).
/// Throw an exception when the internal matching fails because of invalid input.
public JsonMatcher(MatchBehaviour matchBehaviour, [NotNull] object value, bool ignoreCase = false, bool throwException = false)
{
Guard.NotNull(value, nameof(value));
MatchBehaviour = matchBehaviour;
IgnoreCase = ignoreCase;
ThrowException = throwException;
Value = value;
_valueAsJToken = ConvertValueToJToken(value);
_jTokenConverter = ignoreCase
? (Func)Rename
: jToken => jToken;
}
///
public double IsMatch(object input)
{
bool match = false;
// When input is null or byte[], return Mismatch.
if (input != null && !(input is byte[]))
{
try
{
var inputAsJToken = ConvertValueToJToken(input);
match = IsMatch(
_jTokenConverter(_valueAsJToken),
_jTokenConverter(inputAsJToken));
}
catch (JsonException)
{
if (ThrowException)
{
throw;
}
}
}
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match));
}
///
/// Compares the input against the matcher value
///
/// Matcher value
/// Input value
///
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
switch (value)
{
case JToken tokenValue:
return tokenValue;
case string stringValue:
return JsonUtils.Parse(stringValue);
case IEnumerable enumerableValue:
return JArray.FromObject(enumerableValue);
default:
return JObject.FromObject(value);
}
}
private static string ToUpper(string input)
{
return input?.ToUpperInvariant();
}
// https://stackoverflow.com/questions/11679804/json-net-rename-properties
private static JToken Rename(JToken json)
{
if (json is JProperty property)
{
JToken propertyValue = property.Value;
if (propertyValue.Type == JTokenType.String)
{
string stringValue = propertyValue.Value();
propertyValue = ToUpper(stringValue);
}
return new JProperty(ToUpper(property.Name), Rename(propertyValue));
}
if (json is JArray array)
{
var renamedValues = array.Select(Rename);
return new JArray(renamedValues);
}
if (json is JObject obj)
{
var renamedProperties = obj.Properties().Select(Rename);
return new JObject(renamedProperties);
}
return json;
}
}
}