Search code examples
c#regexregex-replace

Regex pattern to replace any occurrence of {...} with actual values


I am trying to create a method that will allow me to replace any occurrence of {....} string with some values.

Let me give you an example:

public const string ProductsApi = "/api/companies/{companyId:int}/stores/{storeKey}/products";

This is a minimal API endpoint. In this particular endpoint we have 2 values to be replaced (this is optional, can be 1+ values to replace). So if companyId = 55 and storeKey = "se2", this will be the result:

public const string ProductPathForHttpClient = "/api/companies/55/stores/se2/products";

To minimize possibility for mistakes, I would like to have a string extension method, let's call it Interpolate(...), to do this:

var productsPathForHttpClient = roductsApi.Interpolate(55, "se2");

So, Interpolate would find 1st occurrence of {..}, and replace it with 1st value, then 2nd occurrence, and so on...

I can do this by searching for { (start) and } (end) and then replace this string with value, and then search for next occurrence, etc.

I am wondering if this is possible with regex? Basically I am trying to minimize mistakes to API calls, by defining the API, and then replace any {..} with actual values for HttpClient path.


Solution

  • Although Anton answer is correct, I will generalize it a little bit:

    public static class TemplateHelper
    {
        public static string Replace(string template, IDictionary<string, object> replacements, bool throwOnNotFound = false)
        {
            if (replacements == null)
                throw new ArgumentNullException(nameof(replacements));
            if (template == null)
                return null;
            return TemplatePattern.Replace(template, x => OnMatch(x, replacements, throwOnNotFound));
        }
    
        private static string OnMatch(Match match, IDictionary<string, object> replacements, bool throwOnNotFound)
        {
            var name = match.Groups["name"].Value;
            if (replacements.TryGetValue(name, out var value))
            {
                return value?.ToString() ?? string.Empty;
            }
            
            if (throwOnNotFound)
            {
                throw new KeyNotFoundException($"Variable name '{name}' not found in replacement list");
            }
    
            return match.Value;
        }
    
        private static readonly Regex TemplatePattern = new Regex(@"{(?<name>\w+)(?<type>:\w+)?}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
    }
    

    And some tests/examples of usage:

    [TestFixture]
    public class TemplateHelperTests
    {
        public static IEnumerable<TestCaseData> GetTestCases()
        {
            yield return T(
                "/api/companies/{companyId:int}/stores/{storeKey}/products",
                "/api/companies/55/stores/se2/products",
                new Dictionary<string, object>()
                {
                    { "companyId", 55 },
                    { "storeKey", "se2" }
                }).SetName("simple");
            yield return T(
                "/api/companies/{companyId:int}/stores/{storeKey}/products",
                "/api/companies/55/stores/{storeKey}/products",
                new Dictionary<string, object>()
                {
                    { "companyId", 55 }
                }).SetName("not_found");
            yield return T(
                "/api/companies/{companyId:int}/stores/{storeKey}/products",
                "/api/companies/55/stores/se2/products",
                new Dictionary<string, object>()
                {
                    { "companyId", 55 },
                    { "storeKey", "se2" },
                    { "foo", "bar" }
                }).SetName("too_much");
        }
    
        private static TestCaseData T(string template, string expected, Dictionary<string, object> replacements)
        {
            return new TestCaseData(template, expected, replacements);
        }
        [Test]
        [TestCaseSource(nameof(GetTestCases))]
        public void Check(string template, string expected, Dictionary<string, object> replacements)
        {
            var actual = TemplateHelper.Replace(template, replacements);
            actual.Should().Be(expected);
        }
    }