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; } } }