stringdartstringtemplate

Generic Template String like in Python in Dart


In python, I often use strings as templates, e.g.

templateUrl = '{host}/api/v3/{container}/{resourceid}'  
params = {'host': 'www.api.com', 'container': 'books', 'resourceid': 10}  
api.get(templateUrl.format(**params))  

This allows for easy base class setup and the like. How can I do the same in dart?

I'm assuming I will need to create a utility function to parse the template and substitute manually but really hoping there is something ready to use.

Perhaps a TemplateString class with a format method that takes a Map of name/value pairs to substitute into the string.

Note: the objective is to have a generic "format" or "interpolation" function that doesn't need to know in advance what tags or names will exist in the template.

Further clarification: the templates themselves are not resolved when they are set up. Specifically, the template is defined in one place in the code and then used in many other places.


Solution

  • Dart does not have a generic template string functionality that would allow you to insert values into your template at runtime.
    Dart only allows you to interpolate strings with variables using the $ syntax in strings, e.g. var string = '$domain/api/v3/${actions.get}'. You would need to have all the variables defined in your code beforehand.

    However, you can easily create your own implementation.

    Implementation

    You pretty much explained how to do it in your question yourself: you pass a map and use it to have generic access to the parameters using the [] operator.

    To convert the template string into something that is easy to access, I would simply create another List containing fixed components, like /api/v3/ and another Map that holds generic components with their name and their position in the template string.

    class TemplateString {
      final List<String> fixedComponents;
      final Map<int, String> genericComponents;
    
      int totalComponents;
    
      TemplateString(String template)
          : fixedComponents = <String>[],
            genericComponents = <int, String>{},
            totalComponents = 0 {
        final List<String> components = template.split('{');
    
        for (String component in components) {
          if (component == '') continue; // If the template starts with "{", skip the first element.
    
          final split = component.split('}');
    
          if (split.length != 1) {
            // The condition allows for template strings without parameters.
            genericComponents[totalComponents] = split.first;
            totalComponents++;
          }
    
          if (split.last != '') {
            fixedComponents.add(split.last);
            totalComponents++;
          }
        }
      }
    
      String format(Map<String, dynamic> params) {
        String result = '';
    
        int fixedComponent = 0;
        for (int i = 0; i < totalComponents; i++) {
          if (genericComponents.containsKey(i)) {
            result += '${params[genericComponents[i]]}';
            continue;
          }
          result += fixedComponents[fixedComponent++];
        }
    
        return result;
      }
    }
    

    Here would be an example usage, I hope that the result is what you expected:

    main() {
      final templateUrl = TemplateString('{host}/api/v3/{container}/{resourceid}');
      final params = <String, dynamic>{'host': 'www.api.com', 'container': 'books', 'resourceid': 10};
    
      print(templateUrl.format(params)); // www.api.com/api/v3/books/10
    }
    

    Here it is as a Gist.