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
///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");
});
}
}