5

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.

The Tahaan
  • 6,915
  • 4
  • 34
  • 54

4 Answers4

13

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.

creativecreatorormaybenot
  • 114,516
  • 58
  • 291
  • 402
3

Here is my solution:

extension StringFormating on String {
  String format(List<String> values) {
    int index = 0;
    return replaceAllMapped(new RegExp(r'{.*?}'), (_) {
      final value = values[index];
      index++;
      return value;
    });
  }

  String formatWithMap(Map<String, String> mappedValues) {
    return replaceAllMapped(new RegExp(r'{(.*?)}'), (match) {
      final mapped = mappedValues[match[1]];
      if (mapped == null)
        throw ArgumentError(
            '$mappedValues does not contain the key "${match[1]}"');
      return mapped;
    });
  }
}

This gives you a very similar functionality to what python offers:

"Test {} with {}!".format(["it", "foo"]);
"Test {a} with {b}!".formatWithMap({"a": "it", "b": "foo"})

both return "Test it with foo!"

felix-ht
  • 1,697
  • 15
  • 16
1

It's even more easy in Dart. Sample code below :

String host = "www.api.com"
String container = "books"
int resourceId = 10

String templateUrl = "$host/api/v3/$container/${resourceId.toString()}"

With the map, you can do as follows :

Map<String, String> params = {'host': 'www.api.com', 'container': 'books', 'resourceid': 10}  

String templateUrl = "${params['host']}/api/v3/${params['container']}/${params['resourceId']}"

Note : The above code defines Map as <String, String>. You might want <String, Dynamic> (and use .toString())

Sukhi
  • 13,261
  • 7
  • 36
  • 53
  • Please see the Edit I added for clarification. – The Tahaan Jul 27 '19 at 08:33
  • Edited the answer to demonstrate use of Map – Sukhi Jul 27 '19 at 08:50
  • Please see my further clarification - the step of setting the template in a variable and then later in a separate step filling it out cannot be optimised into a single step as you have done because then the template needs to be hard-coded everywhere it needs to be used. – The Tahaan Jul 27 '19 at 10:15
  • AFAIK, there’s no standard way of doing it in Dart. You will have to write you own code to achieve it. – Sukhi Jul 27 '19 at 12:16
0

Wouldn't it be simplest to just make it a function with named arguments? You could add some input validation if you wanted to.

String templateUrl({String host = "", String container = "", int resourceid = 0 }) {
    return "$host/api/v3/$container/$resourceId";
}

void main() {
    api.get(templateUrl(host:"www.api.com", container:"books", resourceid:10));    
}
jo-erlend
  • 113
  • 3