Looks like there is a difference in behaviour depending on whether you are using an Undo Manager with your Value Tree or not:
juce::ValueTree chestnut{ "Node", {{"Doubleprop", 0.0}} };
jassert(chestnut.getProperty("Doubleprop").isDouble());
chestnut.setProperty("Doubleprop", "String", nullptr);
jassert(chestnut.getProperty("Doubleprop").isString()); // Passes
juce::UndoManager um{};
um.beginNewTransaction();
juce::ValueTree linden{ "Node", {{"Doubleprop", 0.0}} };
jassert(linden.getProperty("Doubleprop").isDouble());
chestnut.setProperty("Doubleprop", "String", &um);
jassert(linden.getProperty("Doubleprop").isString()); // Fails
I would expect both of the marked asserts to either pass, or at least for both of them to fail.
As far as i remember, juce undo manager don’t apply the transaction until a new one is initiated
Apologies, this was my mistake. The following code works as expected.
juce::ValueTree chestnut{ "Node", {{"Doubleprop", 0.0}} };
jassert(chestnut.getProperty("Doubleprop").isDouble());
chestnut.setProperty("Doubleprop", "String", nullptr);
jassert(chestnut.getProperty("Doubleprop").isString());
juce::UndoManager um{};
juce::ValueTree linden{ "Node", {{"Doubleprop", 0.0}} };
jassert(linden.getProperty("Doubleprop").isDouble());
jassert(double(linden.getProperty("Doubleprop")) == 0.0);
linden.setProperty("Doubleprop", 0.1, &um);
jassert(linden.getProperty("Doubleprop").isDouble());
jassert(double(linden.getProperty("Doubleprop")) == 0.1);
linden.setProperty("Doubleprop", "String", &um);
jassert(linden.getProperty("Doubleprop").isString());
jassert(linden.getProperty("Doubleprop").toString() == "String");