using System; using System.Collections; using System.Linq; using Newtonsoft.Json.Linq; using Stef.Validation; using WireMock.Util; namespace WireMock.Matchers; /// /// JsonMatcher /// public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher { /// public virtual string Name => "JsonMatcher"; /// public object Value { get; } /// public MatchBehaviour MatchBehaviour { get; } /// public bool IgnoreCase { 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). public JsonMatcher(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(object value, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase) { } /// /// 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). public JsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false) { Guard.NotNull(value); MatchBehaviour = matchBehaviour; IgnoreCase = ignoreCase; Value = value; _valueAsJToken = ConvertValueToJToken(value); _jTokenConverter = ignoreCase ? Rename : jToken => jToken; } /// public MatchResult IsMatch(object? input) { var score = MatchScores.Mismatch; Exception? error = null; // When input is null or byte[], return Mismatch. if (input != null && input is not byte[]) { try { var inputAsJToken = ConvertValueToJToken(input); var match = IsMatch(_jTokenConverter(_valueAsJToken), _jTokenConverter(inputAsJToken)); score = MatchScores.ToScore(match); } catch (Exception ex) { error = ex; } } return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), error); } /// /// 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; } }