diff --git a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt
index 900c73c5a3..ede822f0de 100644
--- a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt
+++ b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt
@@ -1016,6 +1016,8 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_core/javascript/juce_JSON.h"
"../../../../../modules/juce_core/javascript/juce_JSONSerialisation.h"
"../../../../../modules/juce_core/javascript/juce_JSONSerialisation_test.cpp"
+ "../../../../../modules/juce_core/javascript/juce_JSONUtils.cpp"
+ "../../../../../modules/juce_core/javascript/juce_JSONUtils.h"
"../../../../../modules/juce_core/logging/juce_FileLogger.cpp"
"../../../../../modules/juce_core/logging/juce_FileLogger.h"
"../../../../../modules/juce_core/logging/juce_Logger.cpp"
@@ -3082,6 +3084,8 @@ set_source_files_properties(
"../../../../../modules/juce_core/javascript/juce_JSON.h"
"../../../../../modules/juce_core/javascript/juce_JSONSerialisation.h"
"../../../../../modules/juce_core/javascript/juce_JSONSerialisation_test.cpp"
+ "../../../../../modules/juce_core/javascript/juce_JSONUtils.cpp"
+ "../../../../../modules/juce_core/javascript/juce_JSONUtils.h"
"../../../../../modules/juce_core/logging/juce_FileLogger.cpp"
"../../../../../modules/juce_core/logging/juce_FileLogger.h"
"../../../../../modules/juce_core/logging/juce_Logger.cpp"
diff --git a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj
index a56f53e087..1db5c45c96 100644
--- a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj
+++ b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj
@@ -1313,6 +1313,9 @@
true
+
+ true
+
true
@@ -3332,6 +3335,7 @@
+
diff --git a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters
index c7f46a60be..9a235c7283 100644
--- a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters
+++ b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters
@@ -1930,6 +1930,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
@@ -5196,6 +5199,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
diff --git a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj
index ed44a0263b..a42fe6a8ec 100644
--- a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj
+++ b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj
@@ -1313,6 +1313,9 @@
true
+
+ true
+
true
@@ -3332,6 +3335,7 @@
+
diff --git a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters
index 8ea21b802c..e755256d7a 100644
--- a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters
+++ b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters
@@ -1930,6 +1930,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
@@ -5196,6 +5199,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
diff --git a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj
index bb93ee018a..2ac4138911 100644
--- a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj
+++ b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj
@@ -1313,6 +1313,9 @@
true
+
+ true
+
true
@@ -3332,6 +3335,7 @@
+
diff --git a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters
index 922f3319b5..c0383e7013 100644
--- a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters
+++ b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters
@@ -1930,6 +1930,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
@@ -5196,6 +5199,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
diff --git a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt
index fd039e8652..1f929bf994 100644
--- a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt
+++ b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt
@@ -892,6 +892,8 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_core/javascript/juce_JSON.h"
"../../../../../modules/juce_core/javascript/juce_JSONSerialisation.h"
"../../../../../modules/juce_core/javascript/juce_JSONSerialisation_test.cpp"
+ "../../../../../modules/juce_core/javascript/juce_JSONUtils.cpp"
+ "../../../../../modules/juce_core/javascript/juce_JSONUtils.h"
"../../../../../modules/juce_core/logging/juce_FileLogger.cpp"
"../../../../../modules/juce_core/logging/juce_FileLogger.h"
"../../../../../modules/juce_core/logging/juce_Logger.cpp"
@@ -2658,6 +2660,8 @@ set_source_files_properties(
"../../../../../modules/juce_core/javascript/juce_JSON.h"
"../../../../../modules/juce_core/javascript/juce_JSONSerialisation.h"
"../../../../../modules/juce_core/javascript/juce_JSONSerialisation_test.cpp"
+ "../../../../../modules/juce_core/javascript/juce_JSONUtils.cpp"
+ "../../../../../modules/juce_core/javascript/juce_JSONUtils.h"
"../../../../../modules/juce_core/logging/juce_FileLogger.cpp"
"../../../../../modules/juce_core/logging/juce_FileLogger.h"
"../../../../../modules/juce_core/logging/juce_Logger.cpp"
diff --git a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj
index b30591fbc8..a0fb61a960 100644
--- a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj
+++ b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj
@@ -1153,6 +1153,9 @@
true
+
+ true
+
true
@@ -2888,6 +2891,7 @@
+
diff --git a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters
index 2e0c11fcd1..c4870e885a 100644
--- a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters
+++ b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters
@@ -1639,6 +1639,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
@@ -4476,6 +4479,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
diff --git a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt
index 3d86aba9be..f862d52a88 100644
--- a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt
+++ b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt
@@ -925,6 +925,8 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_core/javascript/juce_JSON.h"
"../../../../../modules/juce_core/javascript/juce_JSONSerialisation.h"
"../../../../../modules/juce_core/javascript/juce_JSONSerialisation_test.cpp"
+ "../../../../../modules/juce_core/javascript/juce_JSONUtils.cpp"
+ "../../../../../modules/juce_core/javascript/juce_JSONUtils.h"
"../../../../../modules/juce_core/logging/juce_FileLogger.cpp"
"../../../../../modules/juce_core/logging/juce_FileLogger.h"
"../../../../../modules/juce_core/logging/juce_Logger.cpp"
@@ -2844,6 +2846,8 @@ set_source_files_properties(
"../../../../../modules/juce_core/javascript/juce_JSON.h"
"../../../../../modules/juce_core/javascript/juce_JSONSerialisation.h"
"../../../../../modules/juce_core/javascript/juce_JSONSerialisation_test.cpp"
+ "../../../../../modules/juce_core/javascript/juce_JSONUtils.cpp"
+ "../../../../../modules/juce_core/javascript/juce_JSONUtils.h"
"../../../../../modules/juce_core/logging/juce_FileLogger.cpp"
"../../../../../modules/juce_core/logging/juce_FileLogger.h"
"../../../../../modules/juce_core/logging/juce_Logger.cpp"
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj
index 7f7009fed5..6d98bca894 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj
+++ b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj
@@ -1161,6 +1161,9 @@
true
+
+ true
+
true
@@ -3068,6 +3071,7 @@
+
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters
index 6412eaaf0e..4328bd6980 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters
+++ b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters
@@ -1714,6 +1714,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
@@ -4752,6 +4755,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj
index 8e4d3b2c39..bdb62a984e 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj
+++ b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj
@@ -1161,6 +1161,9 @@
true
+
+ true
+
true
@@ -3068,6 +3071,7 @@
+
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters
index fab70583a2..51192816ed 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters
+++ b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters
@@ -1714,6 +1714,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
@@ -4752,6 +4755,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj
index a60475887e..da3cfab86d 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj
+++ b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj
@@ -1161,6 +1161,9 @@
true
+
+ true
+
true
@@ -3068,6 +3071,7 @@
+
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters
index a1354ba663..374d5b4ee6 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters
+++ b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters
@@ -1714,6 +1714,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
@@ -4752,6 +4755,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
diff --git a/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj b/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj
index 111da4d5a0..efced8d6f4 100644
--- a/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj
+++ b/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj
@@ -220,6 +220,9 @@
true
+
+ true
+
true
@@ -528,6 +531,7 @@
+
diff --git a/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj.filters b/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj.filters
index 89603e456a..9c939bbb83 100644
--- a/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj.filters
+++ b/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj.filters
@@ -160,6 +160,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
@@ -558,6 +561,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
diff --git a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt
index 41381d1f81..ddcb2c4cac 100644
--- a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt
+++ b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt
@@ -896,6 +896,8 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_core/javascript/juce_JSON.h"
"../../../../../modules/juce_core/javascript/juce_JSONSerialisation.h"
"../../../../../modules/juce_core/javascript/juce_JSONSerialisation_test.cpp"
+ "../../../../../modules/juce_core/javascript/juce_JSONUtils.cpp"
+ "../../../../../modules/juce_core/javascript/juce_JSONUtils.h"
"../../../../../modules/juce_core/logging/juce_FileLogger.cpp"
"../../../../../modules/juce_core/logging/juce_FileLogger.h"
"../../../../../modules/juce_core/logging/juce_Logger.cpp"
@@ -2742,6 +2744,8 @@ set_source_files_properties(
"../../../../../modules/juce_core/javascript/juce_JSON.h"
"../../../../../modules/juce_core/javascript/juce_JSONSerialisation.h"
"../../../../../modules/juce_core/javascript/juce_JSONSerialisation_test.cpp"
+ "../../../../../modules/juce_core/javascript/juce_JSONUtils.cpp"
+ "../../../../../modules/juce_core/javascript/juce_JSONUtils.h"
"../../../../../modules/juce_core/logging/juce_FileLogger.cpp"
"../../../../../modules/juce_core/logging/juce_FileLogger.h"
"../../../../../modules/juce_core/logging/juce_Logger.cpp"
diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj
index 38228cb131..f01c042d9a 100644
--- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj
+++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj
@@ -1153,6 +1153,9 @@
true
+
+ true
+
true
@@ -2979,6 +2982,7 @@
+
diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters
index 8b36f6bdf8..8efe2dcdbd 100644
--- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters
+++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters
@@ -1669,6 +1669,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
@@ -4617,6 +4620,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
diff --git a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj
index e5b0dc5d3f..7a82b45dca 100644
--- a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj
+++ b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj
@@ -356,6 +356,9 @@
true
+
+ true
+
true
@@ -1821,6 +1824,7 @@
+
diff --git a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters
index 2c6d9092d7..66c254e7a2 100644
--- a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters
+++ b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters
@@ -637,6 +637,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
@@ -2631,6 +2634,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
diff --git a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj
index 3aa9a26508..8ceca47eae 100644
--- a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj
+++ b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj
@@ -356,6 +356,9 @@
true
+
+ true
+
true
@@ -1821,6 +1824,7 @@
+
diff --git a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters
index 62bf8c6eb6..96316102a1 100644
--- a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters
+++ b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters
@@ -637,6 +637,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
@@ -2631,6 +2634,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
diff --git a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj
index f4bd7575a3..a543d57519 100644
--- a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj
+++ b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj
@@ -356,6 +356,9 @@
true
+
+ true
+
true
@@ -1821,6 +1824,7 @@
+
diff --git a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters
index 649e69130c..879f8c7344 100644
--- a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters
+++ b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters
@@ -637,6 +637,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
@@ -2631,6 +2634,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj
index 4b1fee2c6c..b980d5f172 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj
+++ b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj
@@ -1169,6 +1169,9 @@
true
+
+ true
+
true
@@ -3134,6 +3137,7 @@
+
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters
index 4dfd10ecb3..4979cae04f 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters
+++ b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters
@@ -1738,6 +1738,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
@@ -4842,6 +4845,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj
index e6cacf8e8b..b5357458f4 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj
+++ b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj
@@ -1169,6 +1169,9 @@
true
+
+ true
+
true
@@ -3134,6 +3137,7 @@
+
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters
index 1cee45cd03..fafb374c90 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters
+++ b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters
@@ -1738,6 +1738,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
@@ -4842,6 +4845,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj
index d98f2d81f3..0e89de1f5b 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj
+++ b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj
@@ -1169,6 +1169,9 @@
true
+
+ true
+
true
@@ -3134,6 +3137,7 @@
+
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters
index 513b91f96f..e3710f917c 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters
+++ b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters
@@ -1738,6 +1738,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
@@ -4842,6 +4845,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
diff --git a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj
index e0876552a7..05bbebd5b0 100644
--- a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj
+++ b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj
@@ -1152,6 +1152,9 @@
true
+
+ true
+
true
@@ -2955,6 +2958,7 @@
+
diff --git a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj.filters b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj.filters
index 619101a3b3..a0e2a5a947 100644
--- a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj.filters
+++ b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj.filters
@@ -1666,6 +1666,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
@@ -4584,6 +4587,9 @@
JUCE Modules\juce_core\javascript
+
+ JUCE Modules\juce_core\javascript
+
JUCE Modules\juce_core\logging
diff --git a/modules/juce_core/javascript/juce_JSONSerialisation_test.cpp b/modules/juce_core/javascript/juce_JSONSerialisation_test.cpp
index 780ececdb6..3357c1f079 100644
--- a/modules/juce_core/javascript/juce_JSONSerialisation_test.cpp
+++ b/modules/juce_core/javascript/juce_JSONSerialisation_test.cpp
@@ -398,29 +398,32 @@ public:
"hello world",
{ 5, 6, 7 },
{ { "foo", 4 }, { "bar", 5 } } }),
- makeObject ({ { "__version__", 2 },
- { "a", 7 },
- { "b", "hello world" },
- { "c", Array { 5, 6, 7 } },
- { "d", Array { makeObject ({ { "first", "bar" }, { "second", 5 } }),
- makeObject ({ { "first", "foo" }, { "second", 4 } }) } } }));
+ JSONUtils::makeObject ({ { "__version__", 2 },
+ { "a", 7 },
+ { "b", "hello world" },
+ { "c", Array { 5, 6, 7 } },
+ { "d",
+ Array { JSONUtils::makeObject ({ { "first", "bar" },
+ { "second", 5 } }),
+ JSONUtils::makeObject ({ { "first", "foo" },
+ { "second", 4 } }) } } }));
expectDeepEqual (ToVar::convert (TypeWithInternalUnifiedSerialisation { 7.89,
4.321f,
"custom string",
{ "foo", "bar", "baz" } }),
- makeObject ({ { "__version__", 5 },
- { "a", 7.89 },
- { "b", 4.321f },
- { "c", "custom string" },
- { "d", Array { "foo", "bar", "baz" } } }));
+ JSONUtils::makeObject ({ { "__version__", 5 },
+ { "a", 7.89 },
+ { "b", 4.321f },
+ { "c", "custom string" },
+ { "d", Array { "foo", "bar", "baz" } } }));
expectDeepEqual (ToVar::convert (TypeWithExternalSplitSerialisation { "string", { 1, 2, 3 } }),
- makeObject ({ { "__version__", 10 },
- { "a", makeObject ({ { "engaged", true }, { "value", "string" } }) },
- { "b", Array { "0x1", "0x2", "0x3" } } }));
+ JSONUtils::makeObject ({ { "__version__", 10 },
+ { "a", JSONUtils::makeObject ({ { "engaged", true }, { "value", "string" } }) },
+ { "b", Array { "0x1", "0x2", "0x3" } } }));
expectDeepEqual (ToVar::convert (TypeWithInternalSplitSerialisation { "string", { 16, 32, 48 } }),
- makeObject ({ { "__version__", 1 },
- { "a", "string" },
- { "b", Array { "0x10", "0x20", "0x30" } } }));
+ JSONUtils::makeObject ({ { "__version__", 1 },
+ { "a", "string" },
+ { "b", Array { "0x10", "0x20", "0x30" } } }));
expect (ToVar::convert (TypeWithBrokenObjectSerialisation { 1, 2 }) == std::nullopt);
expect (ToVar::convert (TypeWithBrokenPrimitiveSerialisation { 1, 2 }) == std::nullopt);
@@ -429,69 +432,69 @@ public:
expect (ToVar::convert (TypeWithBrokenDynamicSerialisation { std::vector (10) }) == std::nullopt);
expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }),
- makeObject ({ { "__version__", 3 },
- { "a", 1 },
- { "b", 2 },
- { "c", 3 },
- { "d", 4 } }));
- expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }, ToVar::Options{}.withVersionIncluded (false)),
- makeObject ({ { "a", 1 },
- { "b", 2 },
- { "c", 3 },
- { "d", 4 } }));
+ JSONUtils::makeObject ({ { "__version__", 3 },
+ { "a", 1 },
+ { "b", 2 },
+ { "c", 3 },
+ { "d", 4 } }));
+ expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }, ToVar::Options {}.withVersionIncluded (false)),
+ JSONUtils::makeObject ({ { "a", 1 },
+ { "b", 2 },
+ { "c", 3 },
+ { "d", 4 } }));
// Requested explicit version is higher than the type's declared version
- expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }, ToVar::Options{}.withExplicitVersion (4)),
+ expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }, ToVar::Options {}.withExplicitVersion (4)),
std::nullopt);
expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }, ToVar::Options {}.withExplicitVersion (3)),
- makeObject ({ { "__version__", 3 },
- { "a", 1 },
- { "b", 2 },
- { "c", 3 },
- { "d", 4 } }));
+ JSONUtils::makeObject ({ { "__version__", 3 },
+ { "a", 1 },
+ { "b", 2 },
+ { "c", 3 },
+ { "d", 4 } }));
expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }, ToVar::Options {}.withExplicitVersion (2)),
- makeObject ({ { "__version__", 2 },
- { "a", 1 },
- { "b", 2 },
- { "c", 3 } }));
+ JSONUtils::makeObject ({ { "__version__", 2 },
+ { "a", 1 },
+ { "b", 2 },
+ { "c", 3 } }));
expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }, ToVar::Options {}.withExplicitVersion (1)),
- makeObject ({ { "__version__", 1 },
- { "a", 1 },
- { "b", 2 } }));
+ JSONUtils::makeObject ({ { "__version__", 1 },
+ { "a", 1 },
+ { "b", 2 } }));
expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }, ToVar::Options {}.withExplicitVersion (0)),
- makeObject ({ { "__version__", 0 },
- { "a", 1 } }));
+ JSONUtils::makeObject ({ { "__version__", 0 },
+ { "a", 1 } }));
expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }, ToVar::Options {}.withExplicitVersion (std::nullopt)),
- makeObject ({ { "a", 1 } }));
+ JSONUtils::makeObject ({ { "a", 1 } }));
expectDeepEqual (ToVar::convert (TypeWithRawVarLast { 200, "success", true }),
- makeObject ({ { "status", 200 }, { "message", "success" }, { "extended", true } }));
+ JSONUtils::makeObject ({ { "status", 200 }, { "message", "success" }, { "extended", true } }));
expectDeepEqual (ToVar::convert (TypeWithRawVarLast { 200,
"success",
- makeObject ({ { "status", 123.456 },
- { "message", "failure" },
- { "extended", true } }) }),
- makeObject ({ { "status", 200 },
- { "message", "success" },
- { "extended", makeObject ({ { "status", 123.456 },
- { "message", "failure" },
- { "extended", true } }) } }));
+ JSONUtils::makeObject ({ { "status", 123.456 },
+ { "message", "failure" },
+ { "extended", true } }) }),
+ JSONUtils::makeObject ({ { "status", 200 },
+ { "message", "success" },
+ { "extended", JSONUtils::makeObject ({ { "status", 123.456 },
+ { "message", "failure" },
+ { "extended", true } }) } }));
expectDeepEqual (ToVar::convert (TypeWithRawVarFirst { 200, "success", true }),
- makeObject ({ { "status", 200 }, { "message", "success" }, { "extended", true } }));
+ JSONUtils::makeObject ({ { "status", 200 }, { "message", "success" }, { "extended", true } }));
expectDeepEqual (ToVar::convert (TypeWithRawVarFirst { 200,
"success",
- makeObject ({ { "status", 123.456 },
- { "message", "failure" },
- { "extended", true } }) }),
- makeObject ({ { "status", 200 },
- { "message", "success" },
- { "extended", makeObject ({ { "status", 123.456 },
- { "message", "failure" },
- { "extended", true } }) } }));
+ JSONUtils::makeObject ({ { "status", 123.456 },
+ { "message", "failure" },
+ { "extended", true } }) }),
+ JSONUtils::makeObject ({ { "status", 200 },
+ { "message", "success" },
+ { "extended", JSONUtils::makeObject ({ { "status", 123.456 },
+ { "message", "failure" },
+ { "extended", true } }) } }));
- const auto payload = makeObject ({ { "foo", 1 }, { "bar", 2 } });
+ const auto payload = JSONUtils::makeObject ({ { "foo", 1 }, { "bar", 2 } });
expectDeepEqual (ToVar::convert (TypeWithInnerVar { 404, payload }),
- makeObject ({ { "eventId", 404 }, { "payload", payload } }));
+ JSONUtils::makeObject ({ { "eventId", 404 }, { "payload", payload } }));
}
beginTest ("FromVar");
@@ -505,34 +508,37 @@ public:
expect (FromVar::convert (JSON::fromString ("6")) == 6);
expect (FromVar::convert (JSON::fromString ("\"hello world\"")) == "hello world");
expect (FromVar::convert> (JSON::fromString ("[1,2,3]")) == std::vector { 1, 2, 3 });
- expect (FromVar::convert (makeObject ({ { "__version__", 2 },
- { "a", 7 },
- { "b", "hello world" },
- { "c", Array { 5, 6, 7 } },
- { "d", Array { makeObject ({ { "first", "bar" }, { "second", 5 } }),
- makeObject ({ { "first", "foo" }, { "second", 4 } }) } } }))
+ expect (FromVar::convert (JSONUtils::makeObject ({ { "__version__", 2 },
+ { "a", 7 },
+ { "b", "hello world" },
+ { "c", Array { 5, 6, 7 } },
+ { "d",
+ Array { JSONUtils::makeObject ({ { "first", "bar" },
+ { "second", 5 } }),
+ JSONUtils::makeObject ({ { "first", "foo" },
+ { "second", 4 } }) } } }))
== TypeWithExternalUnifiedSerialisation { 7,
"hello world",
{ 5, 6, 7 },
{ { "foo", 4 }, { "bar", 5 } } });
- expect (FromVar::convert (makeObject ({ { "__version__", 5 },
- { "a", 7.89 },
- { "b", 4.321f },
- { "c", "custom string" },
- { "d", Array { "foo", "bar", "baz" } } }))
+ expect (FromVar::convert (JSONUtils::makeObject ({ { "__version__", 5 },
+ { "a", 7.89 },
+ { "b", 4.321f },
+ { "c", "custom string" },
+ { "d", Array { "foo", "bar", "baz" } } }))
== TypeWithInternalUnifiedSerialisation { 7.89,
4.321f,
"custom string",
{ "foo", "bar", "baz" } });
- expect (FromVar::convert (makeObject ({ { "__version__", 10 },
- { "a", makeObject ({ { "engaged", true }, { "value", "string" } }) },
- { "b", Array { "0x1", "0x2", "0x3" } } }))
+ expect (FromVar::convert (JSONUtils::makeObject ({ { "__version__", 10 },
+ { "a", JSONUtils::makeObject ({ { "engaged", true }, { "value", "string" } }) },
+ { "b", Array { "0x1", "0x2", "0x3" } } }))
== TypeWithExternalSplitSerialisation { "string", { 1, 2, 3 } });
- expect (FromVar::convert (makeObject ({ { "__version__", 1 },
- { "a", "string" },
- { "b", Array { "0x10", "0x20", "0x30" } } }))
+ expect (FromVar::convert (JSONUtils::makeObject ({ { "__version__", 1 },
+ { "a", "string" },
+ { "b", Array { "0x10", "0x20", "0x30" } } }))
== TypeWithInternalSplitSerialisation { "string", { 16, 32, 48 } });
expect (FromVar::convert (JSON::fromString ("null")) == std::nullopt);
@@ -541,47 +547,48 @@ public:
expect (FromVar::convert (JSON::fromString ("null")) == std::nullopt);
expect (FromVar::convert (JSON::fromString ("null")) == std::nullopt);
- expect (FromVar::convert (makeObject ({ { "a", 7.89 },
- { "b", 4.321f } })) == std::nullopt);
+ expect (FromVar::convert (JSONUtils::makeObject ({ { "a", 7.89 },
+ { "b", 4.321f } }))
+ == std::nullopt);
- expect (FromVar::convert (makeObject ({ { "__version__", 3 },
- { "a", 1 },
- { "b", 2 },
- { "c", 3 },
- { "d", 4 } }))
+ expect (FromVar::convert (JSONUtils::makeObject ({ { "__version__", 3 },
+ { "a", 1 },
+ { "b", 2 },
+ { "c", 3 },
+ { "d", 4 } }))
== TypeWithVersionedSerialisation { 1, 2, 3, 4 });
- expect (FromVar::convert (makeObject ({ { "__version__", 4 },
- { "a", 1 },
- { "b", 2 },
- { "c", 3 },
- { "d", 4 } }))
+ expect (FromVar::convert (JSONUtils::makeObject ({ { "__version__", 4 },
+ { "a", 1 },
+ { "b", 2 },
+ { "c", 3 },
+ { "d", 4 } }))
== TypeWithVersionedSerialisation { 1, 2, 3, 4 });
- expect (FromVar::convert (makeObject ({ { "__version__", 2 },
- { "a", 1 },
- { "b", 2 },
- { "c", 3 } }))
+ expect (FromVar::convert (JSONUtils::makeObject ({ { "__version__", 2 },
+ { "a", 1 },
+ { "b", 2 },
+ { "c", 3 } }))
== TypeWithVersionedSerialisation { 1, 2, 3, 0 });
- expect (FromVar::convert (makeObject ({ { "__version__", 1 },
- { "a", 1 },
- { "b", 2 } }))
+ expect (FromVar::convert (JSONUtils::makeObject ({ { "__version__", 1 },
+ { "a", 1 },
+ { "b", 2 } }))
== TypeWithVersionedSerialisation { 1, 2, 0, 0 });
- expect (FromVar::convert (makeObject ({ { "__version__", 0 },
- { "a", 1 } }))
+ expect (FromVar::convert (JSONUtils::makeObject ({ { "__version__", 0 },
+ { "a", 1 } }))
== TypeWithVersionedSerialisation { 1, 0, 0, 0 });
- expect (FromVar::convert (makeObject ({ { "a", 1 } }))
+ expect (FromVar::convert (JSONUtils::makeObject ({ { "a", 1 } }))
== TypeWithVersionedSerialisation { 1, 0, 0, 0 });
- const auto raw = makeObject ({ { "status", 200 }, { "message", "success" }, { "extended", "another string" } });
+ const auto raw = JSONUtils::makeObject ({ { "status", 200 }, { "message", "success" }, { "extended", "another string" } });
expect (FromVar::convert (raw) == TypeWithRawVarLast { 200, "success", "another string" });
expect (FromVar::convert (raw) == TypeWithRawVarFirst { 200, "success", "another string" });
- const var payloads[] { makeObject ({ { "foo", 1 }, { "bar", 2 } }),
+ const var payloads[] { JSONUtils::makeObject ({ { "foo", 1 }, { "bar", 2 } }),
var (Array { 1, 2 }),
var() };
for (const auto& payload : payloads)
{
- const auto objectWithPayload = makeObject ({ { "eventId", 404 }, { "payload", payload } });
+ const auto objectWithPayload = JSONUtils::makeObject ({ { "eventId", 404 }, { "payload", payload } });
expect (FromVar::convert (objectWithPayload) == TypeWithInnerVar { 404, payload });
}
}
@@ -593,57 +600,12 @@ private:
expect (deepEqual (a, b), a.has_value() && b.has_value() ? JSON::toString (*a) + " != " + JSON::toString (*b) : String());
}
- static var makeObject (const std::map& map)
- {
- auto obj = std::make_unique();
-
- for (auto& [key, value] : map)
- obj->setProperty (key, value);
-
- return obj.release();
- }
-
- static bool deepEqual (const DynamicObject& a, const DynamicObject& b)
- {
- if (a.getProperties().size() != b.getProperties().size())
- return false;
-
- for (const auto& [key, value] : a.getProperties())
- {
- if (! b.hasProperty (key))
- return false;
-
- if (! deepEqual (value, b.getProperty (key)))
- return false;
- }
-
- return true;
- }
-
- static bool deepEqual (const Array& a, const Array& b)
- {
- return std::equal (a.begin(), a.end(), b.begin(), b.end(), [] (const var& i, const var& j) { return deepEqual (i, j); });
- }
-
- static bool deepEqual (const var& a, const var& b)
- {
- if (auto* i = a.getDynamicObject())
- if (auto* j = b.getDynamicObject())
- return deepEqual (*i, *j);
-
- if (auto* i = a.getArray())
- if (auto* j = b.getArray())
- return deepEqual (*i, *j);
-
- return a == b;
- }
-
static bool deepEqual (const std::optional& a, const std::optional& b)
{
- if (a.has_value() != b.has_value())
- return false;
+ if (a.has_value() && b.has_value())
+ return JSONUtils::deepEqual (*a, *b);
- return ! a.has_value() || deepEqual (*a, *b);
+ return a == b;
}
};
diff --git a/modules/juce_core/javascript/juce_JSONUtils.cpp b/modules/juce_core/javascript/juce_JSONUtils.cpp
new file mode 100644
index 0000000000..36bdf1f6f0
--- /dev/null
+++ b/modules/juce_core/javascript/juce_JSONUtils.cpp
@@ -0,0 +1,215 @@
+/*
+ ==============================================================================
+
+ This file is part of the JUCE library.
+ Copyright (c) 2022 - Raw Material Software Limited
+
+ JUCE is an open source library subject to commercial or open-source
+ licensing.
+
+ The code included in this file is provided under the terms of the ISC license
+ http://www.isc.org/downloads/software-support-policy/isc-license. Permission
+ To use, copy, modify, and/or distribute this software for any purpose with or
+ without fee is hereby granted provided that the above copyright notice and
+ this permission notice appear in all copies.
+
+ JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
+ EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
+ DISCLAIMED.
+
+ ==============================================================================
+*/
+
+namespace juce
+{
+
+var JSONUtils::makeObject (const std::map& source)
+{
+ auto result = std::make_unique();
+
+ for (const auto& [name, value] : source)
+ result->setProperty (name, value);
+
+ return var (result.release());
+}
+
+var JSONUtils::makeObjectWithKeyFirst (const std::map& source,
+ Identifier key)
+{
+ auto result = std::make_unique();
+
+ if (const auto iter = source.find (key); iter != source.end())
+ result->setProperty (key, iter->second);
+
+ for (const auto& [name, value] : source)
+ if (name != key)
+ result->setProperty (name, value);
+
+ return var (result.release());
+}
+
+std::optional JSONUtils::setPointer (const var& v,
+ String pointer,
+ const var& newValue)
+{
+ if (pointer.isEmpty())
+ return newValue;
+
+ if (! pointer.startsWith ("/"))
+ {
+ // This is not a well-formed JSON pointer
+ jassertfalse;
+ return {};
+ }
+
+ const auto findResult = pointer.indexOfChar (1, '/');
+ const auto pos = findResult < 0 ? pointer.length() : findResult;
+ const String head (pointer.begin() + 1, pointer.begin() + pos);
+ const String tail (pointer.begin() + pos, pointer.end());
+
+ const auto unescaped = head.replace ("~1", "/").replace ("~0", "~");
+
+ if (auto* object = v.getDynamicObject())
+ {
+ if (const auto newProperty = setPointer (object->getProperty (unescaped), tail, newValue))
+ {
+ auto cloned = object->clone();
+ cloned->setProperty (unescaped, *newProperty);
+ return var (cloned.release());
+ }
+ }
+ else if (auto* array = v.getArray())
+ {
+ const auto index = [&]() -> size_t
+ {
+ if (unescaped == "-")
+ return (size_t) array->size();
+
+ if (unescaped == "0")
+ return 0;
+
+ if (! unescaped.startsWith ("0"))
+ return (size_t) unescaped.getLargeIntValue();
+
+ return std::numeric_limits::max();
+ }();
+
+ if (const auto newIndex = setPointer ((*array)[(int) index], tail, newValue))
+ {
+ auto copied = *array;
+
+ if ((int) index == copied.size())
+ copied.add ({});
+
+ if (isPositiveAndBelow (index, copied.size()))
+ {
+ copied.getReference ((int) index) = *newIndex;
+ return var (copied);
+ }
+ }
+ }
+
+ return {};
+}
+
+bool JSONUtils::deepEqual (const var& a, const var& b)
+{
+ const auto compareObjects = [] (const DynamicObject& x, const DynamicObject& y)
+ {
+ if (x.getProperties().size() != y.getProperties().size())
+ return false;
+
+ for (const auto& [key, value] : x.getProperties())
+ {
+ if (! y.hasProperty (key))
+ return false;
+
+ if (! deepEqual (value, y.getProperty (key)))
+ return false;
+ }
+
+ return true;
+ };
+
+ if (auto* i = a.getDynamicObject())
+ if (auto* j = b.getDynamicObject())
+ return compareObjects (*i, *j);
+
+ if (auto* i = a.getArray())
+ if (auto* j = b.getArray())
+ return std::equal (i->begin(), i->end(), j->begin(), j->end(), [] (const var& x, const var& y) { return deepEqual (x, y); });
+
+ return a == b;
+}
+
+//==============================================================================
+//==============================================================================
+#if JUCE_UNIT_TESTS
+
+class JSONUtilsTests : public UnitTest
+{
+public:
+ JSONUtilsTests() : UnitTest ("JSONUtils", UnitTestCategories::json) {}
+
+ void runTest() override
+ {
+ beginTest ("JSON pointers");
+ {
+ const auto obj = JSON::parse (R"({ "name": "PIANO 4"
+ , "lfoSpeed": 30
+ , "lfoWaveform": "triangle"
+ , "pitchEnvelope": { "rates": [94,67,95,60], "levels": [50,50,50,50] }
+ })");
+ expectDeepEqual (JSONUtils::setPointer (obj, "", "hello world"), var ("hello world"));
+ expectDeepEqual (JSONUtils::setPointer (obj, "/lfoWaveform/foobar", "str"), std::nullopt);
+ expectDeepEqual (JSONUtils::setPointer (JSON::parse (R"({"foo":0,"bar":1})"), "/foo", 2), JSON::parse (R"({"foo":2,"bar":1})"));
+ expectDeepEqual (JSONUtils::setPointer (JSON::parse (R"({"foo":0,"bar":1})"), "/baz", 2), JSON::parse (R"({"foo":0,"bar":1,"baz":2})"));
+ expectDeepEqual (JSONUtils::setPointer (JSON::parse (R"({"foo":{},"bar":{}})"), "/foo/bar", 2), JSON::parse (R"({"foo":{"bar":2},"bar":{}})"));
+ expectDeepEqual (JSONUtils::setPointer (obj, "/pitchEnvelope/rates/01", "str"), std::nullopt);
+ expectDeepEqual (JSONUtils::setPointer (obj, "/pitchEnvelope/rates/10", "str"), std::nullopt);
+ expectDeepEqual (JSONUtils::setPointer (obj, "/lfoSpeed", 10), JSON::parse (R"({ "name": "PIANO 4"
+ , "lfoSpeed": 10
+ , "lfoWaveform": "triangle"
+ , "pitchEnvelope": { "rates": [94,67,95,60], "levels": [50,50,50,50] }
+ })"));
+ expectDeepEqual (JSONUtils::setPointer (JSON::parse (R"([0,1,2])"), "/0", "bang"), JSON::parse (R"(["bang",1,2])"));
+ expectDeepEqual (JSONUtils::setPointer (JSON::parse (R"([0,1,2])"), "/0", "bang"), JSON::parse (R"(["bang",1,2])"));
+ expectDeepEqual (JSONUtils::setPointer (JSON::parse (R"({"/":"fizz"})"), "/~1", "buzz"), JSON::parse (R"({"/":"buzz"})"));
+ expectDeepEqual (JSONUtils::setPointer (JSON::parse (R"({"~":"fizz"})"), "/~0", "buzz"), JSON::parse (R"({"~":"buzz"})"));
+ expectDeepEqual (JSONUtils::setPointer (obj, "/pitchEnvelope/rates/0", 80), JSON::parse (R"({ "name": "PIANO 4"
+ , "lfoSpeed": 30
+ , "lfoWaveform": "triangle"
+ , "pitchEnvelope": { "rates": [80,67,95,60], "levels": [50,50,50,50] }
+ })"));
+ expectDeepEqual (JSONUtils::setPointer (obj, "/pitchEnvelope/levels/0", 80), JSON::parse (R"({ "name": "PIANO 4"
+ , "lfoSpeed": 30
+ , "lfoWaveform": "triangle"
+ , "pitchEnvelope": { "rates": [94,67,95,60], "levels": [80,50,50,50] }
+ })"));
+ expectDeepEqual (JSONUtils::setPointer (obj, "/pitchEnvelope/levels/-", 100), JSON::parse (R"({ "name": "PIANO 4"
+ , "lfoSpeed": 30
+ , "lfoWaveform": "triangle"
+ , "pitchEnvelope": { "rates": [94,67,95,60], "levels": [50,50,50,50,100] }
+ })"));
+ }
+ }
+
+ void expectDeepEqual (const std::optional& a, const std::optional& b)
+ {
+ expect (deepEqual (a, b), a.has_value() && b.has_value() ? JSON::toString (*a) + " != " + JSON::toString (*b) : String());
+ }
+
+ static bool deepEqual (const std::optional& a, const std::optional& b)
+ {
+ if (a.has_value() && b.has_value())
+ return JSONUtils::deepEqual (*a, *b);
+
+ return a == b;
+ }
+};
+
+static JSONUtilsTests jsonUtilsTests;
+
+#endif
+
+} // namespace juce
diff --git a/modules/juce_core/javascript/juce_JSONUtils.h b/modules/juce_core/javascript/juce_JSONUtils.h
new file mode 100644
index 0000000000..52ab5e0662
--- /dev/null
+++ b/modules/juce_core/javascript/juce_JSONUtils.h
@@ -0,0 +1,67 @@
+/*
+ ==============================================================================
+
+ This file is part of the JUCE library.
+ Copyright (c) 2022 - Raw Material Software Limited
+
+ JUCE is an open source library subject to commercial or open-source
+ licensing.
+
+ The code included in this file is provided under the terms of the ISC license
+ http://www.isc.org/downloads/software-support-policy/isc-license. Permission
+ To use, copy, modify, and/or distribute this software for any purpose with or
+ without fee is hereby granted provided that the above copyright notice and
+ this permission notice appear in all copies.
+
+ JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
+ EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
+ DISCLAIMED.
+
+ ==============================================================================
+*/
+
+namespace juce
+{
+
+/**
+ A mini namespace to hold utility functions for working with juce::vars.
+
+ @tags{Core}
+*/
+struct JSONUtils
+{
+ /** No constructor. */
+ JSONUtils() = delete;
+
+ /** Given a JSON array/object 'v', a string representing a JSON pointer,
+ and a new property value 'newValue', returns a copy of 'v' where the
+ property or array index referenced by the pointer has been set to 'newValue'.
+
+ If the pointer cannot be followed, due to referencing missing array indices
+ or fields, then this returns nullopt.
+
+ For more details, check the JSON Pointer RFC 6901:
+ https://datatracker.ietf.org/doc/html/rfc6901
+ */
+ static std::optional setPointer (const var& v, String pointer, const var& newValue);
+
+ /** Converts the provided key/value pairs into a JSON object. */
+ static var makeObject (const std::map& source);
+
+ /** Converts the provided key/value pairs into a JSON object with the provided
+ key at the first position in the object.
+
+ This is useful because the MIDI-CI spec requires that certain fields (e.g.
+ status) should be placed at the beginning of a MIDI-CI header.
+ */
+ static var makeObjectWithKeyFirst (const std::map& source, Identifier key);
+
+ /** Returns true if and only if the contents of a match the contents of b.
+
+ Unlike var::operator==, this will recursively check that contained DynamicObject and Array
+ instances compare equal.
+ */
+ static bool deepEqual (const var& a, const var& b);
+};
+
+} // namespace juce
diff --git a/modules/juce_core/juce_core.cpp b/modules/juce_core/juce_core.cpp
index 0e6b23fb37..c187355fd7 100644
--- a/modules/juce_core/juce_core.cpp
+++ b/modules/juce_core/juce_core.cpp
@@ -178,6 +178,7 @@
#include "unit_tests/juce_UnitTest.cpp"
#include "containers/juce_Variant.cpp"
#include "javascript/juce_JSON.cpp"
+#include "javascript/juce_JSONUtils.cpp"
#include "javascript/juce_Javascript.cpp"
#include "containers/juce_DynamicObject.cpp"
#include "xml/juce_XmlDocument.cpp"
diff --git a/modules/juce_core/juce_core.h b/modules/juce_core/juce_core.h
index 011dd00c68..bd4d52856e 100644
--- a/modules/juce_core/juce_core.h
+++ b/modules/juce_core/juce_core.h
@@ -309,6 +309,7 @@ JUCE_END_IGNORE_WARNINGS_MSVC
#include "streams/juce_FileInputSource.h"
#include "logging/juce_FileLogger.h"
#include "javascript/juce_JSON.h"
+#include "javascript/juce_JSONUtils.h"
#include "serialisation/juce_Serialisation.h"
#include "javascript/juce_JSONSerialisation.h"
#include "javascript/juce_Javascript.h"