It is doable but mostly pointless as you would typically know what kind of data you have to process.
I managed to come up with something like this. It will work for every type that is iterable with for-each loop or a tuple, or has overloaded operator<<. You can do it without C++20 features but it will be a total SFINAE mess.
#include <iostream>
#include <string>
#include <type_traits>
#include <map>
#include <list>
#include <vector>
#include <tuple>
#include <sstream>
using namespace std;
template <typename T>
concept InterableRange = requires (T a) {
    std::begin(a);
    std::end(a);
};
template <typename T>
concept TupleLikeType = requires (T a) {
    std::tuple_size<T>();
};
template<TupleLikeType T>
string stringify(const T& c);
template<class T>
string stringify(const T& c);
template<InterableRange T>
string stringify(const T& c) {
    string str = "[ ";
    auto size = std::size(c);
    std::size_t i = 0;
    for (const auto& elem : c) {
        str += stringify(elem);
        if(i++ < size - 1)
            str += ", ";
    }
    str += " ]";
    return str;
}
template<TupleLikeType T>
string stringify(const T& c) {
    string str = "[ ";
    auto size = std::tuple_size<T>();
    std::size_t i = 0;
    std::stringstream input;   
    auto insert = [&input, size, &i](const auto& data) {
        input << stringify(data);
        if(i++ < size - 1)
        {
            input.put(',');
            input.put(' ');
        }
    };
    std::apply([&insert](const auto&... args){
        (insert(args), ...);
    }, c);
    str += input.str();
    str += " ]";
    return str;
}
template<class T>
string stringify(const T& c) {
    std::stringstream input;   
    input << c;
    return input.str();
}
int main() {
    map<string,vector<list<int>>> m {
        { "1", {{1,2}, {3, 4}}},
        { "2", {{10,20}, {30, 40}}}
    };
    cout << stringify(m);
}
It will print
[ [ [ 1 ], [ [ 1, 2 ], [ 3, 4 ] ] ], [ [ 2 ], [ [ 10, 20 ], [ 30, 40 ] ] ] ]