add data converter and data types for conversion

First PR for converters support.
It adds the abstract data_converter class
And adds new data value objects that can be passed between converters to easily transform one type of data to another

https://github.com/user-attachments/assets/e8a0fdee-8845-4d0d-a894-c948c2b0a209

Diffs=
4bf7c7545 add data converter and data types for conversion (#7734)

Co-authored-by: hernan <hernan@rive.app>
diff --git a/.rive_head b/.rive_head
index a891ec7..b080470 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-d9f5701ec10c0d0eecb0e3d2f5b0ee80499c746d
+4bf7c75453f0fc3fec1519a9a3dfe7a4ed63a054
diff --git a/dev/defs/data_bind/converters/data_converter.json b/dev/defs/data_bind/converters/data_converter.json
new file mode 100644
index 0000000..b5a0c49
--- /dev/null
+++ b/dev/defs/data_bind/converters/data_converter.json
@@ -0,0 +1,30 @@
+{
+  "name": "DataConverter",
+  "key": {
+    "int": 488,
+    "string": "dataconverter"
+  },
+  "abstract": true,
+  "properties": {
+    "order": {
+      "type": "FractionalIndex",
+      "initialValue": "FractionalIndex.invalid",
+      "initialValueRuntime": "0",
+      "key": {
+        "int": 661,
+        "string": "order"
+      },
+      "description": "Order value for sorting data converters",
+      "runtime": false
+    },
+    "name": {
+      "type": "String",
+      "initialValue": "''",
+      "key": {
+        "int": 662,
+        "string": "name"
+      },
+      "description": "Non-unique identifier, used to give friendly names to data converters."
+    }
+  }
+}
\ No newline at end of file
diff --git a/dev/defs/data_bind/data_bind.json b/dev/defs/data_bind/data_bind.json
index 3e3124d..3ec525b 100644
--- a/dev/defs/data_bind/data_bind.json
+++ b/dev/defs/data_bind/data_bind.json
@@ -31,6 +31,17 @@
         "int": 587,
         "string": "flags"
       }
+    },
+    "converterId": {
+      "type": "Id",
+      "typeRuntime": "uint",
+      "initialValue": "Core.missingId",
+      "initialValueRuntime": "-1",
+      "key": {
+        "int": 660,
+        "string": "converterid"
+      },
+      "description": "Identifier used to link to a data converter."
     }
   }
 }
\ No newline at end of file
diff --git a/include/rive/artboard.hpp b/include/rive/artboard.hpp
index 2e78c7e..e3cf802 100644
--- a/include/rive/artboard.hpp
+++ b/include/rive/artboard.hpp
@@ -295,6 +295,7 @@
                     {
                         auto dataBindClone = static_cast<DataBind*>(dataBind->clone());
                         dataBindClone->target(cloneObjects.back());
+                        dataBindClone->converter(dataBind->converter());
                         artboardClone->m_DataBinds.push_back(dataBindClone);
                     }
                 }
diff --git a/include/rive/data_bind/context/context_value.hpp b/include/rive/data_bind/context/context_value.hpp
index 370f46f..c26730b 100644
--- a/include/rive/data_bind/context/context_value.hpp
+++ b/include/rive/data_bind/context/context_value.hpp
@@ -1,21 +1,48 @@
 #ifndef _RIVE_DATA_BIND_CONTEXT_VALUE_HPP_
 #define _RIVE_DATA_BIND_CONTEXT_VALUE_HPP_
-#include "rive/refcnt.hpp"
 #include "rive/viewmodel/viewmodel_instance_value.hpp"
+#include "rive/data_bind/converters/data_converter.hpp"
 #include <stdio.h>
 namespace rive
 {
 class DataBindContextValue
 {
 protected:
-    ViewModelInstanceValue* m_Source;
+    ViewModelInstanceValue* m_source;
+    DataConverter* m_converter;
+    DataValue* m_dataValue;
 
 public:
-    DataBindContextValue(){};
+    DataBindContextValue(ViewModelInstanceValue* source, DataConverter* converter);
     virtual ~DataBindContextValue(){};
-    virtual void applyToSource(Core* component, uint32_t propertyKey){};
-    virtual void apply(Core* component, uint32_t propertyKey){};
+    virtual void applyToSource(Core* component, uint32_t propertyKey, bool isMainDirection);
+    virtual void apply(Core* component, uint32_t propertyKey, bool isMainDirection){};
     virtual void update(Core* component){};
+    virtual DataValue* getTargetValue(Core* target, uint32_t propertyKey) { return nullptr; };
+    void updateSourceValue();
+    template <typename T = DataValue, typename U> U getDataValue(DataValue* input)
+    {
+        auto dataValue = m_converter != nullptr ? m_converter->convert(input) : input;
+        if (dataValue->is<T>())
+        {
+            return dataValue->as<T>()->value();
+        }
+        return (new T())->value();
+    };
+    template <typename T = DataValue, typename U> U getReverseDataValue(DataValue* input)
+    {
+        auto dataValue = m_converter != nullptr ? m_converter->reverseConvert(input) : input;
+        if (dataValue->is<T>())
+        {
+            return dataValue->as<T>()->value();
+        }
+        return (new T())->value();
+    };
+    template <typename T = DataValue, typename U>
+    U calculateValue(DataValue* input, bool isMainDirection)
+    {
+        return isMainDirection ? getDataValue<T, U>(input) : getReverseDataValue<T, U>(input);
+    };
 };
 } // namespace rive
 
diff --git a/include/rive/data_bind/context/context_value_boolean.hpp b/include/rive/data_bind/context/context_value_boolean.hpp
index 82e9a7f..678e7fc 100644
--- a/include/rive/data_bind/context/context_value_boolean.hpp
+++ b/include/rive/data_bind/context/context_value_boolean.hpp
@@ -7,12 +7,9 @@
 {
 
 public:
-    DataBindContextValueBoolean(ViewModelInstanceValue* value);
-    void apply(Core* component, uint32_t propertyKey) override;
-    virtual void applyToSource(Core* component, uint32_t propertyKey) override;
-
-private:
-    bool m_Value;
+    DataBindContextValueBoolean(ViewModelInstanceValue* source, DataConverter* converter);
+    void apply(Core* component, uint32_t propertyKey, bool isMainDirection) override;
+    DataValue* getTargetValue(Core* target, uint32_t propertyKey) override;
 };
 } // namespace rive
 
diff --git a/include/rive/data_bind/context/context_value_color.hpp b/include/rive/data_bind/context/context_value_color.hpp
index 22fe8f8..207062a 100644
--- a/include/rive/data_bind/context/context_value_color.hpp
+++ b/include/rive/data_bind/context/context_value_color.hpp
@@ -7,12 +7,9 @@
 {
 
 public:
-    DataBindContextValueColor(ViewModelInstanceValue* value);
-    void apply(Core* component, uint32_t propertyKey) override;
-    virtual void applyToSource(Core* component, uint32_t propertyKey) override;
-
-private:
-    double m_Value;
+    DataBindContextValueColor(ViewModelInstanceValue* source, DataConverter* converter);
+    void apply(Core* component, uint32_t propertyKey, bool isMainDirection) override;
+    DataValue* getTargetValue(Core* target, uint32_t propertyKey) override;
 };
 } // namespace rive
 
diff --git a/include/rive/data_bind/context/context_value_enum.hpp b/include/rive/data_bind/context/context_value_enum.hpp
index 98c21c4..dd097b2 100644
--- a/include/rive/data_bind/context/context_value_enum.hpp
+++ b/include/rive/data_bind/context/context_value_enum.hpp
@@ -7,12 +7,9 @@
 {
 
 public:
-    DataBindContextValueEnum(ViewModelInstanceValue* value);
-    void apply(Core* component, uint32_t propertyKey) override;
-    virtual void applyToSource(Core* component, uint32_t propertyKey) override;
-
-private:
-    int m_Value;
+    DataBindContextValueEnum(ViewModelInstanceValue* source, DataConverter* converter);
+    void apply(Core* component, uint32_t propertyKey, bool isMainDirection) override;
+    DataValue* getTargetValue(Core* target, uint32_t propertyKey) override;
 };
 } // namespace rive
 
diff --git a/include/rive/data_bind/context/context_value_list.hpp b/include/rive/data_bind/context/context_value_list.hpp
index 0c12f91..5dd9586 100644
--- a/include/rive/data_bind/context/context_value_list.hpp
+++ b/include/rive/data_bind/context/context_value_list.hpp
@@ -11,10 +11,12 @@
 {
 
 public:
-    DataBindContextValueList(ViewModelInstanceValue* value);
-    void apply(Core* component, uint32_t propertyKey) override;
+    DataBindContextValueList(ViewModelInstanceValue* source, DataConverter* converter);
+    void apply(Core* component, uint32_t propertyKey, bool isMainDirection) override;
     void update(Core* target) override;
-    virtual void applyToSource(Core* component, uint32_t propertyKey) override;
+    virtual void applyToSource(Core* component,
+                               uint32_t propertyKey,
+                               bool isMainDirection) override;
 
 private:
     std::vector<std::unique_ptr<DataBindContextValueListItem>> m_ListItemsCache;
diff --git a/include/rive/data_bind/context/context_value_number.hpp b/include/rive/data_bind/context/context_value_number.hpp
index b2d850a..f3a56fa 100644
--- a/include/rive/data_bind/context/context_value_number.hpp
+++ b/include/rive/data_bind/context/context_value_number.hpp
@@ -7,12 +7,9 @@
 {
 
 public:
-    DataBindContextValueNumber(ViewModelInstanceValue* value);
-    void apply(Core* component, uint32_t propertyKey) override;
-    virtual void applyToSource(Core* component, uint32_t propertyKey) override;
-
-private:
-    double m_Value;
+    DataBindContextValueNumber(ViewModelInstanceValue* source, DataConverter* converter);
+    void apply(Core* component, uint32_t propertyKey, bool isMainDirection) override;
+    DataValue* getTargetValue(Core* target, uint32_t propertyKey) override;
 };
 } // namespace rive
 
diff --git a/include/rive/data_bind/context/context_value_string.hpp b/include/rive/data_bind/context/context_value_string.hpp
index 0b32528..fb8f3cf 100644
--- a/include/rive/data_bind/context/context_value_string.hpp
+++ b/include/rive/data_bind/context/context_value_string.hpp
@@ -7,12 +7,9 @@
 {
 
 public:
-    DataBindContextValueString(ViewModelInstanceValue* value);
-    void apply(Core* component, uint32_t propertyKey) override;
-    virtual void applyToSource(Core* component, uint32_t propertyKey) override;
-
-private:
-    std::string m_Value;
+    DataBindContextValueString(ViewModelInstanceValue* source, DataConverter* converter);
+    void apply(Core* component, uint32_t propertyKey, bool isMainDirection) override;
+    DataValue* getTargetValue(Core* target, uint32_t propertyKey) override;
 };
 } // namespace rive
 
diff --git a/include/rive/data_bind/converters/data_converter.hpp b/include/rive/data_bind/converters/data_converter.hpp
new file mode 100644
index 0000000..bf6deb6
--- /dev/null
+++ b/include/rive/data_bind/converters/data_converter.hpp
@@ -0,0 +1,18 @@
+#ifndef _RIVE_DATA_CONVERTER_HPP_
+#define _RIVE_DATA_CONVERTER_HPP_
+#include "rive/generated/data_bind/converters/data_converter_base.hpp"
+#include "rive/data_bind/data_values/data_value.hpp"
+#include <stdio.h>
+namespace rive
+{
+class DataConverter : public DataConverterBase
+{
+public:
+    virtual DataValue* convert(DataValue* value) { return value; };
+    virtual DataValue* reverseConvert(DataValue* value) { return value; };
+    virtual DataType outputType() { return DataType::none; };
+    StatusCode import(ImportStack& importStack) override;
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/data_bind/data_bind.hpp b/include/rive/data_bind/data_bind.hpp
index 0ccccb3..a62cfd6 100644
--- a/include/rive/data_bind/data_bind.hpp
+++ b/include/rive/data_bind/data_bind.hpp
@@ -5,6 +5,8 @@
 #include "rive/viewmodel/viewmodel_instance_value.hpp"
 #include "rive/data_bind/context/context_value.hpp"
 #include "rive/data_bind/data_context.hpp"
+#include "rive/data_bind/converters/data_converter.hpp"
+#include "rive/data_bind/data_values/data_type.hpp"
 #include <stdio.h>
 namespace rive
 {
@@ -21,12 +23,16 @@
     ComponentDirt dirt() { return m_Dirt; };
     void dirt(ComponentDirt value) { m_Dirt = value; };
     bool addDirt(ComponentDirt value, bool recurse);
+    DataConverter* converter() const { return m_dataConverter; };
+    void converter(DataConverter* value) { m_dataConverter = value; };
 
 protected:
     ComponentDirt m_Dirt = ComponentDirt::Filthy;
     Core* m_target;
     ViewModelInstanceValue* m_Source;
     std::unique_ptr<DataBindContextValue> m_ContextValue;
+    DataConverter* m_dataConverter;
+    DataType outputType();
 };
 } // namespace rive
 
diff --git a/include/rive/data_bind/data_values/data_type.hpp b/include/rive/data_bind/data_values/data_type.hpp
new file mode 100644
index 0000000..c1740b5
--- /dev/null
+++ b/include/rive/data_bind/data_values/data_type.hpp
@@ -0,0 +1,30 @@
+#ifndef _RIVE_DATA_TYPE_HPP_
+#define _RIVE_DATA_TYPE_HPP_
+namespace rive
+{
+/// Data types used for converters.
+enum class DataType : unsigned int
+{
+    /// None.
+    none = 0,
+
+    /// String.
+    string = 1,
+
+    /// Number.
+    number = 2,
+
+    /// Bool.
+    boolean = 3,
+
+    /// Color.
+    color = 4,
+
+    /// List.
+    list = 5,
+
+    /// Enum.
+    enumType = 6
+};
+} // namespace rive
+#endif
\ No newline at end of file
diff --git a/include/rive/data_bind/data_values/data_value.hpp b/include/rive/data_bind/data_values/data_value.hpp
new file mode 100644
index 0000000..e412183
--- /dev/null
+++ b/include/rive/data_bind/data_values/data_value.hpp
@@ -0,0 +1,21 @@
+#ifndef _RIVE_DATA_VALUE_HPP_
+#define _RIVE_DATA_VALUE_HPP_
+#include "rive/data_bind/data_values/data_type.hpp"
+
+#include <stdio.h>
+namespace rive
+{
+class DataValue
+{
+public:
+    virtual bool isTypeOf(DataType dataType) const { return false; }
+    template <typename T> inline bool is() const { return isTypeOf(T::typeKey); }
+    template <typename T> inline T* as()
+    {
+        assert(is<T>());
+        return static_cast<T*>(this);
+    }
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/data_bind/data_values/data_value_boolean.hpp b/include/rive/data_bind/data_values/data_value_boolean.hpp
new file mode 100644
index 0000000..4fd8575
--- /dev/null
+++ b/include/rive/data_bind/data_values/data_value_boolean.hpp
@@ -0,0 +1,23 @@
+#ifndef _RIVE_DATA_VALUE_BOOLEAN_HPP_
+#define _RIVE_DATA_VALUE_BOOLEAN_HPP_
+#include "rive/data_bind/data_values/data_value.hpp"
+
+#include <stdio.h>
+namespace rive
+{
+class DataValueBoolean : public DataValue
+{
+private:
+    bool m_value = false;
+
+public:
+    DataValueBoolean(bool value) : m_value(value){};
+    DataValueBoolean(){};
+    static const DataType typeKey = DataType::boolean;
+    bool isTypeOf(DataType typeKey) const override { return typeKey == DataType::boolean; }
+    bool value() { return m_value; };
+    void value(bool value) { m_value = value; };
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/data_bind/data_values/data_value_color.hpp b/include/rive/data_bind/data_values/data_value_color.hpp
new file mode 100644
index 0000000..fdc88b5
--- /dev/null
+++ b/include/rive/data_bind/data_values/data_value_color.hpp
@@ -0,0 +1,23 @@
+#ifndef _RIVE_DATA_VALUE_COLOR_HPP_
+#define _RIVE_DATA_VALUE_COLOR_HPP_
+#include "rive/data_bind/data_values/data_value.hpp"
+
+#include <stdio.h>
+namespace rive
+{
+class DataValueColor : public DataValue
+{
+private:
+    int m_value = false;
+
+public:
+    DataValueColor(int value) : m_value(value){};
+    DataValueColor(){};
+    static const DataType typeKey = DataType::color;
+    bool isTypeOf(DataType typeKey) const override { return typeKey == DataType::color; }
+    int value() { return m_value; };
+    void value(int value) { m_value = value; };
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/data_bind/data_values/data_value_enum.hpp b/include/rive/data_bind/data_values/data_value_enum.hpp
new file mode 100644
index 0000000..9c2dbe5
--- /dev/null
+++ b/include/rive/data_bind/data_values/data_value_enum.hpp
@@ -0,0 +1,26 @@
+#ifndef _RIVE_DATA_VALUE_ENUM_HPP_
+#define _RIVE_DATA_VALUE_ENUM_HPP_
+#include "rive/data_bind/data_values/data_value.hpp"
+#include "rive/viewmodel/data_enum.hpp"
+
+#include <iostream>
+namespace rive
+{
+class DataValueEnum : public DataValue
+{
+private:
+    uint32_t m_value = 0;
+    DataEnum* m_dataEnum;
+
+public:
+    DataValueEnum(uint32_t value, DataEnum* dataEnum) : m_value(value), m_dataEnum(dataEnum){};
+    DataValueEnum(){};
+    static const DataType typeKey = DataType::enumType;
+    bool isTypeOf(DataType typeKey) const override { return typeKey == DataType::enumType; };
+    uint32_t value() { return m_value; };
+    void value(uint32_t value) { m_value = value; };
+    DataEnum* dataEnum() { return m_dataEnum; };
+    void dataEnum(DataEnum* value) { m_dataEnum = value; };
+};
+} // namespace rive
+#endif
\ No newline at end of file
diff --git a/include/rive/data_bind/data_values/data_value_number.hpp b/include/rive/data_bind/data_values/data_value_number.hpp
new file mode 100644
index 0000000..90a3b5d
--- /dev/null
+++ b/include/rive/data_bind/data_values/data_value_number.hpp
@@ -0,0 +1,23 @@
+#ifndef _RIVE_DATA_VALUE_NUMBER_HPP_
+#define _RIVE_DATA_VALUE_NUMBER_HPP_
+#include "rive/data_bind/data_values/data_value.hpp"
+
+#include <stdio.h>
+namespace rive
+{
+class DataValueNumber : public DataValue
+{
+private:
+    float m_value = 0;
+
+public:
+    DataValueNumber(float value) : m_value(value){};
+    DataValueNumber(){};
+    static const DataType typeKey = DataType::number;
+    bool isTypeOf(DataType typeKey) const override { return typeKey == DataType::number; }
+    float value() { return m_value; };
+    void value(float value) { m_value = value; };
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/data_bind/data_values/data_value_string.hpp b/include/rive/data_bind/data_values/data_value_string.hpp
new file mode 100644
index 0000000..7bf2676
--- /dev/null
+++ b/include/rive/data_bind/data_values/data_value_string.hpp
@@ -0,0 +1,22 @@
+#ifndef _RIVE_DATA_VALUE_STRING_HPP_
+#define _RIVE_DATA_VALUE_STRING_HPP_
+#include "rive/data_bind/data_values/data_value.hpp"
+
+#include <iostream>
+namespace rive
+{
+class DataValueString : public DataValue
+{
+private:
+    std::string m_value = "";
+
+public:
+    DataValueString(std::string value) : m_value(value){};
+    DataValueString(){};
+    static const DataType typeKey = DataType::string;
+    bool isTypeOf(DataType typeKey) const override { return typeKey == DataType::string; };
+    std::string value() { return m_value; };
+    void value(std::string value) { m_value = value; };
+};
+} // namespace rive
+#endif
\ No newline at end of file
diff --git a/include/rive/generated/core_registry.hpp b/include/rive/generated/core_registry.hpp
index 592e41b..d8e9eb9 100644
--- a/include/rive/generated/core_registry.hpp
+++ b/include/rive/generated/core_registry.hpp
@@ -118,6 +118,7 @@
 #include "rive/data_bind/bindable_property_enum.hpp"
 #include "rive/data_bind/bindable_property_number.hpp"
 #include "rive/data_bind/bindable_property_string.hpp"
+#include "rive/data_bind/converters/data_converter.hpp"
 #include "rive/data_bind/data_bind.hpp"
 #include "rive/data_bind/data_bind_context.hpp"
 #include "rive/draw_rules.hpp"
@@ -994,6 +995,9 @@
             case DataBindBase::flagsPropertyKey:
                 object->as<DataBindBase>()->flags(value);
                 break;
+            case DataBindBase::converterIdPropertyKey:
+                object->as<DataBindBase>()->converterId(value);
+                break;
             case BindablePropertyEnumBase::propertyValuePropertyKey:
                 object->as<BindablePropertyEnumBase>()->propertyValue(value);
                 break;
@@ -1132,6 +1136,9 @@
             case OpenUrlEventBase::urlPropertyKey:
                 object->as<OpenUrlEventBase>()->url(value);
                 break;
+            case DataConverterBase::namePropertyKey:
+                object->as<DataConverterBase>()->name(value);
+                break;
             case BindablePropertyStringBase::propertyValuePropertyKey:
                 object->as<BindablePropertyStringBase>()->propertyValue(value);
                 break;
@@ -1999,6 +2006,8 @@
                 return object->as<DataBindBase>()->propertyKey();
             case DataBindBase::flagsPropertyKey:
                 return object->as<DataBindBase>()->flags();
+            case DataBindBase::converterIdPropertyKey:
+                return object->as<DataBindBase>()->converterId();
             case BindablePropertyEnumBase::propertyValuePropertyKey:
                 return object->as<BindablePropertyEnumBase>()->propertyValue();
             case NestedArtboardLeafBase::fitPropertyKey:
@@ -2097,6 +2106,8 @@
                 return object->as<TransitionValueStringComparatorBase>()->value();
             case OpenUrlEventBase::urlPropertyKey:
                 return object->as<OpenUrlEventBase>()->url();
+            case DataConverterBase::namePropertyKey:
+                return object->as<DataConverterBase>()->name();
             case BindablePropertyStringBase::propertyValuePropertyKey:
                 return object->as<BindablePropertyStringBase>()->propertyValue();
             case TextValueRunBase::textPropertyKey:
@@ -2617,6 +2628,7 @@
             case OpenUrlEventBase::targetValuePropertyKey:
             case DataBindBase::propertyKeyPropertyKey:
             case DataBindBase::flagsPropertyKey:
+            case DataBindBase::converterIdPropertyKey:
             case BindablePropertyEnumBase::propertyValuePropertyKey:
             case NestedArtboardLeafBase::fitPropertyKey:
             case WeightBase::valuesPropertyKey:
@@ -2661,6 +2673,7 @@
             case KeyFrameStringBase::valuePropertyKey:
             case TransitionValueStringComparatorBase::valuePropertyKey:
             case OpenUrlEventBase::urlPropertyKey:
+            case DataConverterBase::namePropertyKey:
             case BindablePropertyStringBase::propertyValuePropertyKey:
             case TextValueRunBase::textPropertyKey:
             case CustomPropertyStringBase::propertyValuePropertyKey:
@@ -3180,6 +3193,8 @@
                 return object->is<DataBindBase>();
             case DataBindBase::flagsPropertyKey:
                 return object->is<DataBindBase>();
+            case DataBindBase::converterIdPropertyKey:
+                return object->is<DataBindBase>();
             case BindablePropertyEnumBase::propertyValuePropertyKey:
                 return object->is<BindablePropertyEnumBase>();
             case NestedArtboardLeafBase::fitPropertyKey:
@@ -3264,6 +3279,8 @@
                 return object->is<TransitionValueStringComparatorBase>();
             case OpenUrlEventBase::urlPropertyKey:
                 return object->is<OpenUrlEventBase>();
+            case DataConverterBase::namePropertyKey:
+                return object->is<DataConverterBase>();
             case BindablePropertyStringBase::propertyValuePropertyKey:
                 return object->is<BindablePropertyStringBase>();
             case TextValueRunBase::textPropertyKey:
diff --git a/include/rive/generated/data_bind/converters/data_converter_base.hpp b/include/rive/generated/data_bind/converters/data_converter_base.hpp
new file mode 100644
index 0000000..737b567
--- /dev/null
+++ b/include/rive/generated/data_bind/converters/data_converter_base.hpp
@@ -0,0 +1,66 @@
+#ifndef _RIVE_DATA_CONVERTER_BASE_HPP_
+#define _RIVE_DATA_CONVERTER_BASE_HPP_
+#include <string>
+#include "rive/core.hpp"
+#include "rive/core/field_types/core_string_type.hpp"
+namespace rive
+{
+class DataConverterBase : public Core
+{
+protected:
+    typedef Core Super;
+
+public:
+    static const uint16_t typeKey = 488;
+
+    /// Helper to quickly determine if a core object extends another without RTTI
+    /// at runtime.
+    bool isTypeOf(uint16_t typeKey) const override
+    {
+        switch (typeKey)
+        {
+            case DataConverterBase::typeKey:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    uint16_t coreType() const override { return typeKey; }
+
+    static const uint16_t namePropertyKey = 662;
+
+private:
+    std::string m_Name = "";
+
+public:
+    inline const std::string& name() const { return m_Name; }
+    void name(std::string value)
+    {
+        if (m_Name == value)
+        {
+            return;
+        }
+        m_Name = value;
+        nameChanged();
+    }
+
+    void copy(const DataConverterBase& object) { m_Name = object.m_Name; }
+
+    bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
+    {
+        switch (propertyKey)
+        {
+            case namePropertyKey:
+                m_Name = CoreStringType::deserialize(reader);
+                return true;
+        }
+        return false;
+    }
+
+protected:
+    virtual void nameChanged() {}
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/generated/data_bind/data_bind_base.hpp b/include/rive/generated/data_bind/data_bind_base.hpp
index b900663..77db021 100644
--- a/include/rive/generated/data_bind/data_bind_base.hpp
+++ b/include/rive/generated/data_bind/data_bind_base.hpp
@@ -29,10 +29,12 @@
 
     static const uint16_t propertyKeyPropertyKey = 586;
     static const uint16_t flagsPropertyKey = 587;
+    static const uint16_t converterIdPropertyKey = 660;
 
 private:
     uint32_t m_PropertyKey = Core::invalidPropertyKey;
     uint32_t m_Flags = 0;
+    uint32_t m_ConverterId = -1;
 
 public:
     inline uint32_t propertyKey() const { return m_PropertyKey; }
@@ -57,11 +59,23 @@
         flagsChanged();
     }
 
+    inline uint32_t converterId() const { return m_ConverterId; }
+    void converterId(uint32_t value)
+    {
+        if (m_ConverterId == value)
+        {
+            return;
+        }
+        m_ConverterId = value;
+        converterIdChanged();
+    }
+
     Core* clone() const override;
     void copy(const DataBindBase& object)
     {
         m_PropertyKey = object.m_PropertyKey;
         m_Flags = object.m_Flags;
+        m_ConverterId = object.m_ConverterId;
     }
 
     bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
@@ -74,6 +88,9 @@
             case flagsPropertyKey:
                 m_Flags = CoreUintType::deserialize(reader);
                 return true;
+            case converterIdPropertyKey:
+                m_ConverterId = CoreUintType::deserialize(reader);
+                return true;
         }
         return false;
     }
@@ -81,6 +98,7 @@
 protected:
     virtual void propertyKeyChanged() {}
     virtual void flagsChanged() {}
+    virtual void converterIdChanged() {}
 };
 } // namespace rive
 
diff --git a/include/rive/importers/backboard_importer.hpp b/include/rive/importers/backboard_importer.hpp
index f237931..c92454f 100644
--- a/include/rive/importers/backboard_importer.hpp
+++ b/include/rive/importers/backboard_importer.hpp
@@ -12,6 +12,8 @@
 class Backboard;
 class FileAsset;
 class FileAssetReferencer;
+class DataConverter;
+class DataBind;
 class BackboardImporter : public ImportStackObject
 {
 private:
@@ -20,6 +22,8 @@
     std::vector<NestedArtboard*> m_NestedArtboards;
     std::vector<FileAsset*> m_FileAssets;
     std::vector<FileAssetReferencer*> m_FileAssetReferencers;
+    std::vector<DataConverter*> m_DataConverters;
+    std::vector<DataBind*> m_DataConverterReferencers;
     int m_NextArtboardId;
 
 public:
@@ -29,6 +33,8 @@
     void addNestedArtboard(NestedArtboard* artboard);
     void addFileAsset(FileAsset* asset);
     void addFileAssetReferencer(FileAssetReferencer* referencer);
+    void addDataConverterReferencer(DataBind* referencer);
+    void addDataConverter(DataConverter* converter);
 
     StatusCode resolve() override;
     const Backboard* backboard() const { return m_Backboard; }
diff --git a/src/data_bind/context/context_value.cpp b/src/data_bind/context/context_value.cpp
new file mode 100644
index 0000000..5a12954
--- /dev/null
+++ b/src/data_bind/context/context_value.cpp
@@ -0,0 +1,122 @@
+#include "rive/data_bind/context/context_value.hpp"
+#include "rive/data_bind/context/context_value_color.hpp"
+#include "rive/data_bind/data_values/data_type.hpp"
+#include "rive/data_bind/data_values/data_value.hpp"
+#include "rive/data_bind/data_values/data_value_number.hpp"
+#include "rive/data_bind/data_values/data_value_string.hpp"
+#include "rive/data_bind/data_values/data_value_enum.hpp"
+#include "rive/data_bind/data_values/data_value_color.hpp"
+#include "rive/data_bind/data_values/data_value_boolean.hpp"
+#include "rive/generated/core_registry.hpp"
+
+using namespace rive;
+
+DataBindContextValue::DataBindContextValue(ViewModelInstanceValue* source,
+                                           DataConverter* converter) :
+    m_source(source), m_converter(converter)
+{
+    if (m_source != nullptr)
+    {
+        switch (m_source->coreType())
+        {
+            case ViewModelInstanceNumberBase::typeKey:
+                m_dataValue =
+                    new DataValueNumber(m_source->as<ViewModelInstanceNumber>()->propertyValue());
+                break;
+            case ViewModelInstanceStringBase::typeKey:
+                m_dataValue =
+                    new DataValueString(m_source->as<ViewModelInstanceString>()->propertyValue());
+                break;
+            case ViewModelInstanceColorBase::typeKey:
+                m_dataValue =
+                    new DataValueColor(m_source->as<ViewModelInstanceColor>()->propertyValue());
+                break;
+            case ViewModelInstanceBooleanBase::typeKey:
+                m_dataValue =
+                    new DataValueBoolean(m_source->as<ViewModelInstanceBoolean>()->propertyValue());
+                break;
+            case ViewModelInstanceEnumBase::typeKey:
+            {
+                auto viewmodelInstanceEnum = m_source->as<ViewModelInstanceEnum>();
+                auto viewModelPropertyEnum =
+                    viewmodelInstanceEnum->viewModelProperty()->as<ViewModelPropertyEnum>();
+                m_dataValue = new DataValueEnum(viewmodelInstanceEnum->propertyValue(),
+                                                viewModelPropertyEnum->dataEnum());
+            }
+            break;
+            default:
+                m_dataValue = new DataValue();
+        }
+    }
+}
+
+void DataBindContextValue::updateSourceValue()
+{
+    if (m_source != nullptr)
+    {
+        switch (m_source->coreType())
+        {
+            case ViewModelInstanceNumberBase::typeKey:
+                m_dataValue->as<DataValueNumber>()->value(
+                    m_source->as<ViewModelInstanceNumber>()->propertyValue());
+                break;
+            case ViewModelInstanceStringBase::typeKey:
+                m_dataValue->as<DataValueString>()->value(
+                    m_source->as<ViewModelInstanceString>()->propertyValue());
+                break;
+            case ViewModelInstanceColorBase::typeKey:
+                m_dataValue->as<DataValueColor>()->value(
+                    m_source->as<ViewModelInstanceColor>()->propertyValue());
+                break;
+            case ViewModelInstanceBooleanBase::typeKey:
+                m_dataValue->as<DataValueBoolean>()->value(
+                    m_source->as<ViewModelInstanceBoolean>()->propertyValue());
+                break;
+            case ViewModelInstanceEnumBase::typeKey:
+                m_dataValue->as<DataValueEnum>()->value(
+                    m_source->as<ViewModelInstanceEnum>()->propertyValue());
+                break;
+        }
+    }
+}
+
+void DataBindContextValue::applyToSource(Core* component,
+                                         uint32_t propertyKey,
+                                         bool isMainDirection)
+{
+    auto targetValue = getTargetValue(component, propertyKey);
+    switch (m_source->coreType())
+    {
+        case ViewModelInstanceNumberBase::typeKey:
+        {
+
+            auto value = calculateValue<DataValueNumber, float>(targetValue, isMainDirection);
+            m_source->as<ViewModelInstanceNumber>()->propertyValue(value);
+        }
+        break;
+        case ViewModelInstanceStringBase::typeKey:
+        {
+            auto value = calculateValue<DataValueString, std::string>(targetValue, isMainDirection);
+            m_source->as<ViewModelInstanceString>()->propertyValue(value);
+        }
+        break;
+        case ViewModelInstanceColorBase::typeKey:
+        {
+            auto value = calculateValue<DataValueColor, int>(targetValue, isMainDirection);
+            m_source->as<ViewModelInstanceColor>()->propertyValue(value);
+        }
+        break;
+        case ViewModelInstanceBooleanBase::typeKey:
+        {
+            auto value = calculateValue<DataValueBoolean, bool>(targetValue, isMainDirection);
+            m_source->as<ViewModelInstanceBoolean>()->propertyValue(value);
+        }
+        break;
+        case ViewModelInstanceEnumBase::typeKey:
+        {
+            auto value = calculateValue<DataValueEnum, uint32_t>(targetValue, isMainDirection);
+            m_source->as<ViewModelInstanceEnum>()->propertyValue(value);
+        }
+        break;
+    }
+}
\ No newline at end of file
diff --git a/src/data_bind/context/context_value_boolean.cpp b/src/data_bind/context/context_value_boolean.cpp
index b7b708b..b08b8f5 100644
--- a/src/data_bind/context/context_value_boolean.cpp
+++ b/src/data_bind/context/context_value_boolean.cpp
@@ -1,27 +1,23 @@
 #include "rive/data_bind/context/context_value_boolean.hpp"
+#include "rive/data_bind/data_values/data_value_boolean.hpp"
 #include "rive/generated/core_registry.hpp"
 
 using namespace rive;
 
-DataBindContextValueBoolean::DataBindContextValueBoolean(ViewModelInstanceValue* value)
+DataBindContextValueBoolean::DataBindContextValueBoolean(ViewModelInstanceValue* source,
+                                                         DataConverter* converter) :
+    DataBindContextValue(source, converter)
+{}
+
+void DataBindContextValueBoolean::apply(Core* target, uint32_t propertyKey, bool isMainDirection)
 {
-    m_Source = value;
-    m_Value = m_Source->as<ViewModelInstanceBoolean>()->propertyValue();
+    updateSourceValue();
+    auto value = calculateValue<DataValueBoolean, bool>(m_dataValue, isMainDirection);
+    CoreRegistry::setBool(target, propertyKey, value);
 }
 
-void DataBindContextValueBoolean::apply(Core* target, uint32_t propertyKey)
-{
-    CoreRegistry::setBool(target,
-                          propertyKey,
-                          m_Source->as<ViewModelInstanceBoolean>()->propertyValue());
-}
-
-void DataBindContextValueBoolean::applyToSource(Core* target, uint32_t propertyKey)
+DataValue* DataBindContextValueBoolean::getTargetValue(Core* target, uint32_t propertyKey)
 {
     auto value = CoreRegistry::getBool(target, propertyKey);
-    if (m_Value != value)
-    {
-        m_Value = value;
-        m_Source->as<ViewModelInstanceBoolean>()->propertyValue(value);
-    }
+    return new DataValueBoolean(value);
 }
\ No newline at end of file
diff --git a/src/data_bind/context/context_value_color.cpp b/src/data_bind/context/context_value_color.cpp
index 44a1857..c66171d 100644
--- a/src/data_bind/context/context_value_color.cpp
+++ b/src/data_bind/context/context_value_color.cpp
@@ -1,27 +1,23 @@
 #include "rive/data_bind/context/context_value_color.hpp"
+#include "rive/data_bind/data_values/data_value_color.hpp"
 #include "rive/generated/core_registry.hpp"
 
 using namespace rive;
 
-DataBindContextValueColor::DataBindContextValueColor(ViewModelInstanceValue* value)
+DataBindContextValueColor::DataBindContextValueColor(ViewModelInstanceValue* source,
+                                                     DataConverter* converter) :
+    DataBindContextValue(source, converter)
+{}
+
+void DataBindContextValueColor::apply(Core* target, uint32_t propertyKey, bool isMainDirection)
 {
-    m_Source = value;
-    m_Value = m_Source->as<ViewModelInstanceColor>()->propertyValue();
+    updateSourceValue();
+    auto value = calculateValue<DataValueColor, int>(m_dataValue, isMainDirection);
+    CoreRegistry::setColor(target, propertyKey, value);
 }
 
-void DataBindContextValueColor::apply(Core* target, uint32_t propertyKey)
-{
-    CoreRegistry::setColor(target,
-                           propertyKey,
-                           m_Source->as<ViewModelInstanceColor>()->propertyValue());
-}
-
-void DataBindContextValueColor::applyToSource(Core* target, uint32_t propertyKey)
+DataValue* DataBindContextValueColor::getTargetValue(Core* target, uint32_t propertyKey)
 {
     auto value = CoreRegistry::getColor(target, propertyKey);
-    if (m_Value != value)
-    {
-        m_Value = value;
-        m_Source->as<ViewModelInstanceColor>()->propertyValue(value);
-    }
+    return new DataValueColor(value);
 }
\ No newline at end of file
diff --git a/src/data_bind/context/context_value_enum.cpp b/src/data_bind/context/context_value_enum.cpp
index a0064bc..dac5920 100644
--- a/src/data_bind/context/context_value_enum.cpp
+++ b/src/data_bind/context/context_value_enum.cpp
@@ -1,36 +1,27 @@
 #include "rive/data_bind/context/context_value_enum.hpp"
+#include "rive/data_bind/data_values/data_value_enum.hpp"
 #include "rive/generated/core_registry.hpp"
 
 using namespace rive;
 
-DataBindContextValueEnum::DataBindContextValueEnum(ViewModelInstanceValue* value)
+DataBindContextValueEnum::DataBindContextValueEnum(ViewModelInstanceValue* source,
+                                                   DataConverter* converter) :
+    DataBindContextValue(source, converter)
+{}
+
+void DataBindContextValueEnum::apply(Core* target, uint32_t propertyKey, bool isMainDirection)
 {
-    m_Source = value;
-    m_Value = m_Source->as<ViewModelInstanceEnum>()->propertyValue();
+
+    updateSourceValue();
+    auto value = calculateValue<DataValueEnum, uint32_t>(m_dataValue, isMainDirection);
+    CoreRegistry::setUint(target, propertyKey, value);
 }
 
-void DataBindContextValueEnum::apply(Core* target, uint32_t propertyKey)
+DataValue* DataBindContextValueEnum::getTargetValue(Core* target, uint32_t propertyKey)
 {
-    auto enumSource = m_Source->as<ViewModelInstanceEnum>();
-    auto enumProperty = enumSource->viewModelProperty()->as<ViewModelPropertyEnum>();
-    auto enumValue = enumProperty->value(m_Source->as<ViewModelInstanceEnum>()->propertyValue());
-    // TODO: @hernan decide which one makes more sense. Probably setUint, but setString was used to
-    // update text input with enum values.
-    CoreRegistry::setString(target, propertyKey, enumValue);
-    CoreRegistry::setUint(target,
-                          propertyKey,
-                          m_Source->as<ViewModelInstanceEnum>()->propertyValue());
-}
-
-void DataBindContextValueEnum::applyToSource(Core* target, uint32_t propertyKey)
-{
-    auto value = CoreRegistry::getString(target, propertyKey);
-    auto enumSource = m_Source->as<ViewModelInstanceEnum>();
-    auto enumProperty = enumSource->viewModelProperty()->as<ViewModelPropertyEnum>();
-    auto valueIndex = enumProperty->valueIndex(value);
-    if (valueIndex != -1 && m_Value != valueIndex)
-    {
-        m_Value = valueIndex;
-        m_Source->as<ViewModelInstanceEnum>()->value(static_cast<uint32_t>(valueIndex));
-    }
+    auto value = CoreRegistry::getUint(target, propertyKey);
+    auto viewmodelInstanceEnum = m_source->as<ViewModelInstanceEnum>();
+    auto viewModelPropertyEnum =
+        viewmodelInstanceEnum->viewModelProperty()->as<ViewModelPropertyEnum>();
+    return new DataValueEnum(value, viewModelPropertyEnum->dataEnum());
 }
\ No newline at end of file
diff --git a/src/data_bind/context/context_value_list.cpp b/src/data_bind/context/context_value_list.cpp
index f0aa427..92fc2fd 100644
--- a/src/data_bind/context/context_value_list.cpp
+++ b/src/data_bind/context/context_value_list.cpp
@@ -5,10 +5,10 @@
 
 using namespace rive;
 
-DataBindContextValueList::DataBindContextValueList(ViewModelInstanceValue* value)
-{
-    m_Source = value;
-}
+DataBindContextValueList::DataBindContextValueList(ViewModelInstanceValue* source,
+                                                   DataConverter* converter) :
+    DataBindContextValue(source, converter)
+{}
 
 std::unique_ptr<ArtboardInstance> DataBindContextValueList::createArtboard(
     Component* target,
@@ -72,7 +72,7 @@
 {
     if (target != nullptr)
     {
-        auto sourceList = m_Source->as<ViewModelInstanceList>();
+        auto sourceList = m_source->as<ViewModelInstanceList>();
         auto listItems = sourceList->listItems();
 
         int listIndex = 0;
@@ -125,9 +125,11 @@
     }
 }
 
-void DataBindContextValueList::apply(Core* target, uint32_t propertyKey) {}
+void DataBindContextValueList::apply(Core* target, uint32_t propertyKey, bool isMainDirection) {}
 
-void DataBindContextValueList::applyToSource(Core* target, uint32_t propertyKey)
+void DataBindContextValueList::applyToSource(Core* target,
+                                             uint32_t propertyKey,
+                                             bool isMainDirection)
 {
     // TODO: @hernan does applyToSource make sense? Should we block it somehow?
 }
\ No newline at end of file
diff --git a/src/data_bind/context/context_value_number.cpp b/src/data_bind/context/context_value_number.cpp
index 63fabf8..4f40997 100644
--- a/src/data_bind/context/context_value_number.cpp
+++ b/src/data_bind/context/context_value_number.cpp
@@ -1,27 +1,23 @@
 #include "rive/data_bind/context/context_value_number.hpp"
+#include "rive/data_bind/data_values/data_value_number.hpp"
 #include "rive/generated/core_registry.hpp"
 
 using namespace rive;
 
-DataBindContextValueNumber::DataBindContextValueNumber(ViewModelInstanceValue* value)
+DataBindContextValueNumber::DataBindContextValueNumber(ViewModelInstanceValue* source,
+                                                       DataConverter* converter) :
+    DataBindContextValue(source, converter)
+{}
+
+void DataBindContextValueNumber::apply(Core* target, uint32_t propertyKey, bool isMainDirection)
 {
-    m_Source = value;
-    m_Value = m_Source->as<ViewModelInstanceNumber>()->propertyValue();
+    updateSourceValue();
+    auto value = calculateValue<DataValueNumber, float>(m_dataValue, isMainDirection);
+    CoreRegistry::setDouble(target, propertyKey, value);
 }
 
-void DataBindContextValueNumber::apply(Core* target, uint32_t propertyKey)
-{
-    CoreRegistry::setDouble(target,
-                            propertyKey,
-                            m_Source->as<ViewModelInstanceNumber>()->propertyValue());
-}
-
-void DataBindContextValueNumber::applyToSource(Core* target, uint32_t propertyKey)
+DataValue* DataBindContextValueNumber::getTargetValue(Core* target, uint32_t propertyKey)
 {
     auto value = CoreRegistry::getDouble(target, propertyKey);
-    if (m_Value != value)
-    {
-        m_Value = value;
-        m_Source->as<ViewModelInstanceNumber>()->propertyValue(value);
-    }
+    return new DataValueNumber(value);
 }
\ No newline at end of file
diff --git a/src/data_bind/context/context_value_string.cpp b/src/data_bind/context/context_value_string.cpp
index 9cf19f7..2c73dc8 100644
--- a/src/data_bind/context/context_value_string.cpp
+++ b/src/data_bind/context/context_value_string.cpp
@@ -1,27 +1,23 @@
 #include "rive/data_bind/context/context_value_string.hpp"
+#include "rive/data_bind/data_values/data_value_string.hpp"
 #include "rive/generated/core_registry.hpp"
 
 using namespace rive;
 
-DataBindContextValueString::DataBindContextValueString(ViewModelInstanceValue* value)
+DataBindContextValueString::DataBindContextValueString(ViewModelInstanceValue* source,
+                                                       DataConverter* converter) :
+    DataBindContextValue(source, converter)
+{}
+
+void DataBindContextValueString::apply(Core* target, uint32_t propertyKey, bool isMainDirection)
 {
-    m_Source = value;
-    m_Value = m_Source->as<ViewModelInstanceString>()->propertyValue();
+    updateSourceValue();
+    auto value = calculateValue<DataValueString, std::string>(m_dataValue, isMainDirection);
+    CoreRegistry::setString(target, propertyKey, value);
 }
 
-void DataBindContextValueString::apply(Core* target, uint32_t propertyKey)
-{
-    CoreRegistry::setString(target,
-                            propertyKey,
-                            m_Source->as<ViewModelInstanceString>()->propertyValue());
-}
-
-void DataBindContextValueString::applyToSource(Core* target, uint32_t propertyKey)
+DataValue* DataBindContextValueString::getTargetValue(Core* target, uint32_t propertyKey)
 {
     auto value = CoreRegistry::getString(target, propertyKey);
-    if (m_Value != value)
-    {
-        m_Value = value;
-        m_Source->as<ViewModelInstanceString>()->propertyValue(value);
-    }
+    return new DataValueString(value);
 }
\ No newline at end of file
diff --git a/src/data_bind/converters/data_converter.cpp b/src/data_bind/converters/data_converter.cpp
new file mode 100644
index 0000000..d9645b9
--- /dev/null
+++ b/src/data_bind/converters/data_converter.cpp
@@ -0,0 +1,18 @@
+#include "rive/data_bind/converters/data_converter.hpp"
+#include "rive/importers/import_stack.hpp"
+#include "rive/importers/backboard_importer.hpp"
+#include "rive/backboard.hpp"
+
+using namespace rive;
+
+StatusCode DataConverter::import(ImportStack& importStack)
+{
+    auto backboardImporter = importStack.latest<BackboardImporter>(Backboard::typeKey);
+    if (backboardImporter == nullptr)
+    {
+        return StatusCode::MissingObject;
+    }
+    backboardImporter->addDataConverter(this);
+
+    return Super::import(importStack);
+}
\ No newline at end of file
diff --git a/src/data_bind/data_bind.cpp b/src/data_bind/data_bind.cpp
index 9877f5b..e4c52c9 100644
--- a/src/data_bind/data_bind.cpp
+++ b/src/data_bind/data_bind.cpp
@@ -14,10 +14,12 @@
 #include "rive/data_bind/context/context_value_enum.hpp"
 #include "rive/data_bind/context/context_value_list.hpp"
 #include "rive/data_bind/context/context_value_color.hpp"
+#include "rive/data_bind/data_values/data_type.hpp"
 #include "rive/animation/transition_viewmodel_condition.hpp"
 #include "rive/animation/state_machine.hpp"
 #include "rive/importers/artboard_importer.hpp"
 #include "rive/importers/state_machine_importer.hpp"
+#include "rive/importers/backboard_importer.hpp"
 
 using namespace rive;
 
@@ -34,6 +36,13 @@
 
 StatusCode DataBind::import(ImportStack& importStack)
 {
+
+    auto backboardImporter = importStack.latest<BackboardImporter>(Backboard::typeKey);
+    if (backboardImporter == nullptr)
+    {
+        return StatusCode::MissingObject;
+    }
+    backboardImporter->addDataConverterReferencer(this);
     if (target())
     {
         switch (target()->coreType())
@@ -66,31 +75,61 @@
             }
         }
     }
+
     return Super::import(importStack);
 }
 
+DataType DataBind::outputType()
+{
+    if (converter())
+    {
+        return converter()->outputType();
+    }
+    switch (m_Source->coreType())
+    {
+        case ViewModelInstanceNumberBase::typeKey:
+            return DataType::number;
+        case ViewModelInstanceStringBase::typeKey:
+            return DataType::string;
+        case ViewModelInstanceEnumBase::typeKey:
+            return DataType::enumType;
+        case ViewModelInstanceColorBase::typeKey:
+            return DataType::color;
+        case ViewModelInstanceBooleanBase::typeKey:
+            return DataType::boolean;
+        case ViewModelInstanceListBase::typeKey:
+            return DataType::list;
+    }
+    return DataType::none;
+}
+
 void DataBind::bind()
 {
-    switch (m_Source->coreType())
+    switch (outputType())
     {
-        case ViewModelInstanceNumberBase::typeKey:
-            m_ContextValue = rivestd::make_unique<DataBindContextValueNumber>(m_Source);
+        case DataType::number:
+            m_ContextValue =
+                rivestd::make_unique<DataBindContextValueNumber>(m_Source, converter());
             break;
-        case ViewModelInstanceStringBase::typeKey:
-            m_ContextValue = rivestd::make_unique<DataBindContextValueString>(m_Source);
+        case DataType::string:
+            m_ContextValue =
+                rivestd::make_unique<DataBindContextValueString>(m_Source, converter());
             break;
-        case ViewModelInstanceEnumBase::typeKey:
-            m_ContextValue = rivestd::make_unique<DataBindContextValueEnum>(m_Source);
+        case DataType::boolean:
+            m_ContextValue =
+                rivestd::make_unique<DataBindContextValueBoolean>(m_Source, converter());
             break;
-        case ViewModelInstanceListBase::typeKey:
-            m_ContextValue = rivestd::make_unique<DataBindContextValueList>(m_Source);
+        case DataType::color:
+            m_ContextValue = rivestd::make_unique<DataBindContextValueColor>(m_Source, converter());
+            break;
+        case DataType::enumType:
+            m_ContextValue = rivestd::make_unique<DataBindContextValueEnum>(m_Source, converter());
+            break;
+        case DataType::list:
+            m_ContextValue = rivestd::make_unique<DataBindContextValueList>(m_Source, converter());
             m_ContextValue->update(m_target);
             break;
-        case ViewModelInstanceColorBase::typeKey:
-            m_ContextValue = rivestd::make_unique<DataBindContextValueColor>(m_Source);
-            break;
-        case ViewModelInstanceBooleanBase::typeKey:
-            m_ContextValue = rivestd::make_unique<DataBindContextValueBoolean>(m_Source);
+        default:
             break;
     }
 }
@@ -114,7 +153,10 @@
             if (((flagsValue & DataBindFlags::Direction) == DataBindFlags::ToTarget) ||
                 ((flagsValue & DataBindFlags::TwoWay) == DataBindFlags::TwoWay))
             {
-                m_ContextValue->apply(m_target, propertyKey());
+                m_ContextValue->apply(m_target,
+                                      propertyKey(),
+                                      (flagsValue & DataBindFlags::Direction) ==
+                                          DataBindFlags::ToTarget);
             }
         }
     }
@@ -128,7 +170,10 @@
     {
         if (m_ContextValue != nullptr)
         {
-            m_ContextValue->applyToSource(m_target, propertyKey());
+            m_ContextValue->applyToSource(m_target,
+                                          propertyKey(),
+                                          (flagsValue & DataBindFlags::Direction) ==
+                                              DataBindFlags::ToSource);
         }
     }
 }
diff --git a/src/importers/backboard_importer.cpp b/src/importers/backboard_importer.cpp
index 286f133..845c536 100644
--- a/src/importers/backboard_importer.cpp
+++ b/src/importers/backboard_importer.cpp
@@ -7,6 +7,8 @@
 #include "rive/assets/file_asset.hpp"
 #include "rive/viewmodel/viewmodel.hpp"
 #include "rive/viewmodel/viewmodel_instance.hpp"
+#include "rive/data_bind/converters/data_converter.hpp"
+#include "rive/data_bind/data_bind.hpp"
 #include <unordered_set>
 
 using namespace rive;
@@ -85,6 +87,25 @@
         auto asset = m_FileAssets[index];
         referencer->setAsset(asset);
     }
+    for (auto referencer : m_DataConverterReferencers)
+    {
+        auto index = (size_t)referencer->converterId();
+        if (index >= m_DataConverters.size() || index < 0)
+        {
+            continue;
+        }
+        referencer->converter(m_DataConverters[index]);
+    }
 
     return StatusCode::Ok;
+}
+
+void BackboardImporter::addDataConverter(DataConverter* dataConverter)
+{
+    m_DataConverters.push_back(dataConverter);
+}
+
+void BackboardImporter::addDataConverterReferencer(DataBind* dataBind)
+{
+    m_DataConverterReferencers.push_back(dataBind);
 }
\ No newline at end of file