From 5d4b4f39ecbe85cec0adec808f5b905dad4894a1 Mon Sep 17 00:00:00 2001
From: Jeff Young <jeff@rokeby.ie>
Date: Wed, 31 Jan 2024 14:09:00 +0000
Subject: [PATCH] Allow PARAM_WXSTRING_MAP to behave as an array.

In particular, overwrite file contents with current
contents of map rather than merging.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/16801
---
 common/project/project_file.cpp   |  3 ++-
 common/settings/json_settings.cpp | 10 ++++++++++
 include/settings/parameters.h     | 18 +++++++++++++++---
 3 files changed, 27 insertions(+), 4 deletions(-)

diff --git a/common/project/project_file.cpp b/common/project/project_file.cpp
index 4fb682cafe..90f05bb2ed 100644
--- a/common/project/project_file.cpp
+++ b/common/project/project_file.cpp
@@ -50,7 +50,8 @@ PROJECT_FILE::PROJECT_FILE( const wxString& aFullPath ) :
 
     m_params.emplace_back( new PARAM_LIST<FILE_INFO_PAIR>( "boards", &m_boards, {} ) );
 
-    m_params.emplace_back( new PARAM_WXSTRING_MAP( "text_variables", &m_TextVars, {} ) );
+    m_params.emplace_back( new PARAM_WXSTRING_MAP( "text_variables",
+            &m_TextVars, {}, false, true /* array behavior, even though stored as a map */ ) );
 
     m_params.emplace_back( new PARAM_LIST<wxString>( "libraries.pinned_symbol_libs",
             &m_PinnedSymbolLibs, {} ) );
diff --git a/common/settings/json_settings.cpp b/common/settings/json_settings.cpp
index f29b8d9150..3846ab78e5 100644
--- a/common/settings/json_settings.cpp
+++ b/common/settings/json_settings.cpp
@@ -466,6 +466,16 @@ bool JSON_SETTINGS::SaveToFile( const wxString& aDirectory, bool aForce )
 
     nlohmann::json toSave = m_internals->m_original;
 
+
+    for( PARAM_BASE* param : m_params )
+    {
+        if( PARAM_WXSTRING_MAP* stringMap = dynamic_cast<PARAM_WXSTRING_MAP*>( param ) )
+        {
+            if( stringMap->ClearUnknownKeys() )
+                toSave[ stringMap->GetJsonPath() ] = nlohmann::json( {} );
+        }
+    }
+
     toSave.update( m_internals->begin(), m_internals->end(), /* merge_objects = */ true );
 
     try
diff --git a/include/settings/parameters.h b/include/settings/parameters.h
index 785e6bfd11..931e08edde 100644
--- a/include/settings/parameters.h
+++ b/include/settings/parameters.h
@@ -35,7 +35,8 @@ class PARAM_BASE
 public:
     PARAM_BASE( std::string aJsonPath, bool aReadOnly ) :
             m_path( std::move( aJsonPath ) ),
-            m_readOnly( aReadOnly )
+            m_readOnly( aReadOnly ),
+            m_clearUnknownKeys( false )
     {}
 
     virtual ~PARAM_BASE() = default;
@@ -68,9 +69,18 @@ public:
      */
     const std::string& GetJsonPath() const { return m_path; }
 
+    /**
+     * @return true if keys should be cleared from source file rather than merged.  Useful for
+     *         things such as text variables that are semantically an array but stored as a map.
+     */
+    bool ClearUnknownKeys() const { return m_clearUnknownKeys; }
+
 protected:
     std::string m_path;               ///< Address of the param in the json files
     bool        m_readOnly;           ///< Indicates param pointer should never be overwritten
+    bool        m_clearUnknownKeys;   ///< Keys should be cleared from source rather than merged.
+                                      ///<   This is useful for things that are semantically an
+                                      ///<   array but stored as a map, such as textVars.
 };
 
 
@@ -575,11 +585,13 @@ class PARAM_WXSTRING_MAP : public PARAM_BASE
 public:
     PARAM_WXSTRING_MAP( const std::string& aJsonPath, std::map<wxString, wxString>* aPtr,
                         std::initializer_list<std::pair<const wxString, wxString>> aDefault,
-                        bool aReadOnly = false ) :
+                        bool aReadOnly = false, bool aArrayBehavior = false ) :
             PARAM_BASE( aJsonPath, aReadOnly ),
             m_ptr( aPtr ),
             m_default( aDefault )
-    { }
+    {
+        m_clearUnknownKeys = aArrayBehavior;
+    }
 
     void Load( JSON_SETTINGS* aSettings, bool aResetIfMissing = true ) const override;