std::vector<T> to ValueTree and back!

I recently had a need to convert a std::vector<T> into a format that can be used in a ValueTree.
This is what I came up with:

template<typename T, size_t elementSize>
juce::String VectorToBase64String(const T& vec)
{
    juce::MemoryBlock mb(vec.data(), vec.size() * elementSize);
    return mb.toBase64Encoding();
}

template<typename T>
juce::String VectorToBase64String(const std::vector<T>& vec)
{
    return VectorToBase64String<std::vector<T>, sizeof(T)>(vec);
}

To go the other way, from string to vector:

template<typename T>
std::vector<T> Base64StringToVector(juce::String str)
{
    juce::MemoryBlock mb;
    if( mb.fromBase64Encoding(str) )
    {
        juce::Array<T> arr(static_cast<T*>(mb.getData()),
                           static_cast<int>(mb.getSize()) / sizeof(T));
        
        std::vector<T> vec;
        vec.resize(arr.size());
        
        for( size_t i = 0; i < vec.size(); ++i )
        {
            vec[i] = arr[static_cast<int>(i)];
        }
        
        return vec;
    }
    
    jassertfalse;
    return {};
}

It works great for what I need!
I hope someone else will find it useful.

4 Likes

I don’t quite get why you use the intermediate juce::Array here? You could copy the content into the std::vector right away using the iterator constructor (untested)

template<typename T>
std::vector<T> Base64StringToVector(juce::String str)
{
    juce::MemoryBlock mb;
    if( mb.fromBase64Encoding(str) )
    {
        const T* begin = mb.getData();
        const T* end   = begin + mb.getSize() / sizeof (T);

        return std::vector<T> (begin, end);
    }
    
    jassertfalse;
    return {};
}

Edit: Being so used to C++ 20 in the meantime I just realized that the iterator constructor is not available pre C++ 20. Anyway, this slightly more old school version using a std::memcpy should work with older C++ standards and still avoids an extra Array:

template<typename T>
std::vector<T> Base64StringToVector(juce::String str)
{
    juce::MemoryBlock mb;
    if( mb.fromBase64Encoding(str) )
    {        
        std::vector<T> vec (mb.getSize() / sizeof (T));

        std::memcpy (vec.data(), mb.getData(), mb.getSize());
        
        return vec;
    }
    
    jassertfalse;
    return {};
}
4 Likes

Thank you guys. I guess I will need this in the future.

Personally, I’ve have done with with a template overload of juce::VariantConverter which would allow you to use your type with things like juce::CachedValue…

Also, I don’t see the need to use a memoryblock and then convert to a string? juce::var has a constructor that takes a juce::MemoryBlock so you could just use that directly and let JUCE handle the string convertions.

3 Likes

Thanks @ImJimmi and @PluginPenguin
Those are 2 approaches that I never thought of using!
I have used VariantConverter in the past, but sometimes the fastest/quickest solution is to just write exactly what you need, and not implement a β€˜bridge class’ to conform with a framework’s intended usage.

I didn’t need to use CachedValue for this project, and I wanted to be able to inspect the Base64 string in the debugger. I can’t do that if I use the MemoryBlock -> var approach.

A lot of my code choices center around the ability to debug the code and inspect the variables. memcpy doesn’t let me do this during the copy stage.
but it’s good to know that you can use memcpy to write directly into std::vector.
the temporary juce::Array followed by the manual copy was my solution that gave me the ability to inspect what was being added to the std::vector, while it was being copied.

1 Like

I use this approach too – I’ve got templated helper structs for converting different container types to and from var, and then elsewhere in my codebase I’ve got helper functions for converting vars to/from ValueTrees.

The only downside I can see to this approach is that the initial conversion to var may result in the final ValueTree not looking quite as nice as if you’d built it β€œmanually” from the container, but this approach makes the C++ workflow much nicer…

Here’s the code:
Containers to/from vars

ValueTree to/from var

1 Like

Please for the sake of god, add a static_assert to check if Ts are trivially copyable before memcopying them. Will save you from headaches later on.

6 Likes

I just wanted to share a revision that works for C++17:

template<typename T>
juce::var VectorToJuceVar(const std::vector<T>& vec)
{
    juce::MemoryBlock mb(vec.data(), vec.size() * sizeof(T));
    return juce::var(mb);
}
template<typename T>
std::vector<T> JuceVarToVector(const juce::var& var)
{
    if( var.isBinaryData() )
    {
        auto mb = var.getBinaryData();
        juce::Array<T> arr(static_cast<T*>(mb->getData()),
                           static_cast<int>(mb->getSize()) / sizeof(T));
        
        std::vector<T> vec;
        vec.resize(arr.size());
        
        for( size_t i = 0; i < vec.size(); ++i )
        {
            vec[i] = arr[static_cast<int>(i)];
        }
        
        return vec;
    }
    
    jassertfalse;
    return {};
}