using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Collections; using System.Linq; using WireMock.Util; using WireMock.Validation; namespace WireMock.Matchers { /// /// JsonMatcher /// public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher { /// public object Value { get; } /// public string Name => "JsonMatcher"; /// public MatchBehaviour MatchBehaviour { get; } /// public bool IgnoreCase { get; } /// /// Initializes a new instance of the class. /// /// The string value to check for equality. /// Ignore the case from the PropertyName and PropertyValue (string only). public JsonMatcher([NotNull] string value, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase) { } /// /// Initializes a new instance of the class. /// /// The object value to check for equality. /// Ignore the case from the PropertyName and PropertyValue (string only). public JsonMatcher([NotNull] object value, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase) { } /// /// Initializes a new instance of the class. /// /// The match behaviour. /// The string value to check for equality. /// Ignore the case from the PropertyName and PropertyValue (string only). public JsonMatcher(MatchBehaviour matchBehaviour, [NotNull] string value, bool ignoreCase = false) { Check.NotNull(value, nameof(value)); MatchBehaviour = matchBehaviour; Value = value; IgnoreCase = ignoreCase; } /// /// Initializes a new instance of the class. /// /// The match behaviour. /// The object value to check for equality. /// Ignore the case from the PropertyName and PropertyValue (string only). public JsonMatcher(MatchBehaviour matchBehaviour, [NotNull] object value, bool ignoreCase = false) { Check.NotNull(value, nameof(value)); MatchBehaviour = matchBehaviour; Value = value; IgnoreCase = ignoreCase; } /// public double IsMatch(object input) { bool match = false; // When input is null or byte[], return Mismatch. if (input != null && !(input is byte[])) { try { // Check if JToken or object JToken jtokenInput = input is JToken tokenInput ? tokenInput : JObject.FromObject(input); // Check if JToken, string, IEnumerable or object JToken jtokenValue; switch (Value) { case JToken tokenValue: jtokenValue = tokenValue; break; case string stringValue: jtokenValue = JsonUtils.Parse(stringValue); break; case IEnumerable enumerableValue: jtokenValue = JArray.FromObject(enumerableValue); break; default: jtokenValue = JObject.FromObject(Value); break; } match = DeepEquals(jtokenValue, jtokenInput); } catch (JsonException) { // just ignore JsonException } } return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)); } 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(); } // 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; } } }