Flutter- Convert complex objects (Nested Maps, Lists) to a url query string

convert maps inside maps, list inside maps, maps inside list to a single query string for a get request

One basic necessity of a mobile app is to consume data from an api. In flutter, the most popular libraries for making api request are http and dio. The http library is simple but the challenge comes when making a GET request and there is the need to pass complex params with the api call. By the end of this tutorial, we will create a simple utility class to simplify our get requests and parameter management. You can skip the tutorial and copy the class code at the end or download the class file on github

Sample of a flutter get request using the http library

// problematic sample
final params = {
  'firstName': 'Jodn',
  'lastName': 'Smith',
  'age':'45'
};
final uri = Uri.http('www.api.mysite.com', '/users', params);
final headers = {HttpHeaders.contentTypeHeader: 'application/json'};
final response = await http.get(uri, headers: headers);

problems with this sample.

  • property values must be string
  • dealing with complex or nested maps or list properties is complicated to construct.

After this tutorials we should be able to send a complex param as the one below

//completed example 
final params = {
  'name': 'John',
  'columns': ['firstName', 'lastName'],
  'ageRange': {
    'from': 12,
    'to': 60,
  },
  'someInnerArray': [1,2,3,5]
};
final Uri uri = SimplifiedUri.uri('http://api.mysite.com/users', params);
final headers = {HttpHeaders.contentTypeHeader: 'application/json'};
final response = await http.get(uri, headers: headers);

output

http://api.mysite.com/users?name=John&columns%5B%5D=firstName&columns%5B%5D=lastName&ageRange%5Bfrom%5D=12&ageRange%5Bto%5D=60&someInnerArray%5B%5D=1&someInnerArray%5B%5D=2&someInnerArray%5B%5D=3&someInnerArray%5B%5D=5
  • we will now be able to pass complex param properties
  • supports nested params and list

Lets begin

method 1

simpleFlutterUri(String url, dynamic param)

this method will accept the url and the complex parameter as arguments and return a single uri.

 /// converts a dart object to a query string
  Uri simpleFlutterUri(String url, dynamic param){
    // combine the url with the generated query string
    final urlString = "$url?${objectToQueryString(param)}";
    //create a Uri object from the urlString and return
    return Uri.parse(urlString);
  }

method 2

objectToQueryString(dynamic json)

this method converts map,list or array with nested objects to a query string. it calls two other methods that transforms maps properties and list properties nested in the param. these methods adds each transformed property to a list variable 'List queries = [];' which we will later join to form the query string.

 ///transforms inner map properties and adds them to the query list
String objectToQueryString(dynamic json){
    //if no param we return an empty string
    if(json==null)
      return "";
    //keep the list of parameters to transform
    List<String> queries = [];
    //lets handle map params
    if(json is Map){
      //we loop through all the map properties by their keys
      json.keys.forEach((key) {
        //if property value is a nested map we call our method that handles nested maps
        if(json[key] is Map){
          generateInnerMap(queries, "$key", json[key]);
        }
        //if value is an array we call our method that handles inner list
        else if(json[key] is List){
          generateInnerList(queries, "$key", json[key]);
        }
        //else we add the string conversion for the property. eg. name=joshua
        else
          queries.add("$key=${json[key]}");
      });
    }
    //lets handle list param by calling our method that handles inner list
    else if(json is List){
      generateInnerList(queries, "", json);
    }
   //lets join all transformed properties as a single string and urlEncode the string
    return  Uri.encodeFull(queries.join("&"));
  }

method 3

generateInnerMap(List queryList, String parentKey,Map innerJson)

this method transforms inner map properties and adds them to the query list

  ///transforms inner map properties and adds them to the query list
  generateInnerMap(List<String> queryList, String parentKey,Map<String, dynamic> innerJson){
    //loop through each map key
    innerJson.keys.forEach((key) {
      // the property is another nested map we call recursive
      if(innerJson[key] is Map){
        generateInnerMap(queryList, "$parentKey[$key]", innerJson[key]);
      }
      // if the property is a list then we call the list transformer method
      else if(innerJson[key] is List){
        generateInnerList(queryList, "$parentKey[$key]", innerJson[key]);
      }
      // else we transform the primitive property and to the query list. eg. ageRange[from]=3
      else
        queryList.add("$parentKey[$key]=${innerJson[key] }");
    });
  }

method 4 (last)

generateInnerList(List queryList, String parentKey,List innerList)

this method just transforms an array property to a query string param

///transforms array properties and adds them to the query list
  generateInnerList(List<String> queryList, String parentKey,List<dynamic> innerList){
    //loop through list items
    innerList.forEach((item) {
      //if property is a map object we call our map object transformer
      if(item is Map){
        generateInnerMap(queryList, "$parentKey[]", item as Map<String, dynamic>);
      }
      // else we transform the primitive property and add to the query list. eg. columns[]=firstName
      else
        queryList.add("$parentKey[]=$item");
    });
  }

We can combine all the methods into a utility class and reuse for any of our flutter projects

class file is also on github

///A simple Flutter / Dart Utility class for converting complex objects to uri and query strings
///by Joshua Opata. opatajoshua@gmail.com
class SimplifiedUri{
  static Uri uri(String url, dynamic param){
    // combine the url with the generated query string
    final urlString = "$url?${objectToQueryString(param)}";
    print('urlString: $urlString');
    //create a Uri object from the urlString and return
    return Uri.parse(urlString);
  }

  /// converts a dart object to a query string
  static String objectToQueryString(dynamic json){
    //if no param we return an empty string
    if(json==null)
      return "";
    //keep the list of parameters to transform
    List<String> queries = [];
    //lets handle map params
    if(json is Map){
      //we loop through all the map properties by their keys
      json.keys.forEach((key) {
        //if property value is a nested map we call our method that handles nested maps
        if(json[key] is Map){
          generateInnerMap(queries, "$key", json[key]);
        }
        //if value is an array we call our method that handles inner list
        else if(json[key] is List){
          generateInnerList(queries, "$key", json[key]);
        }
        //else we add the string conversion for the property. eg. name=joshua
        else
          queries.add("$key=${json[key]}");
      });
    }
    //lets handle list param by calling our method that handles inner list
    else if(json is List){
      generateInnerList(queries, "", json);
    }
    //lets join all transformed properties as a single string and urlEncode the string
    return  Uri.encodeFull(queries.join("&"));
  }

  ///transforms inner map properties and adds them to the query list
  static generateInnerMap(List<String> queryList, String parentKey,Map<String, dynamic> innerJson){
    //loop through each map key
    innerJson.keys.forEach((key) {
      // the property is another nested map we call recursive
      if(innerJson[key] is Map){
        generateInnerMap(queryList, "$parentKey[$key]", innerJson[key]);
      }
      // if the property is a list then we call the list transformer method
      else if(innerJson[key] is List){
        generateInnerList(queryList, "$parentKey[$key]", innerJson[key]);
      }
      // else we transform the primitive property and add to the query list. eg. ageRange[from]=3
      else
        queryList.add("$parentKey[$key]=${innerJson[key] }");
    });
  }

  ///transforms array properties and adds them to the query list
  static generateInnerList(List<String> queryList, String parentKey,List<dynamic> innerList){
    //loop through list items
    innerList.forEach((item) {
      //if property is a map object we call our map object transformer
      if(item is Map){
        generateInnerMap(queryList, "$parentKey[]", item as Map<String, dynamic>);
      }
      // else we transform the primitive property and add to the query list. eg. columns[]=firstName
      else
        queryList.add("$parentKey[]=$item");
    });
  }
}