From 007359675b0ee39f5a70ce1a7adfc9d268388050 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sat, 16 Apr 2016 16:39:20 +0200
Subject: [PATCH] added a flatten function

---
 src/json.hpp      | 111 +++++++++++++++++++++++++++++++++++-----------
 src/json.hpp.re2c | 111 +++++++++++++++++++++++++++++++++++-----------
 test/unit.cpp     |  47 ++++++++++++++++++++
 3 files changed, 217 insertions(+), 52 deletions(-)

diff --git a/src/json.hpp b/src/json.hpp
index 2d383136..6aca24ad 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -8907,32 +8907,6 @@ basic_json_parser_63:
         /// the reference tokens
         std::vector<std::string> reference_tokens {};
 
-        /*!
-        @brief replace all occurrences of a substring by another string
-
-        @param[in,out] s  the string to manipulate
-        @param[in]     f  the substring to replace with @a t
-        @param[out]    t  the string to replace @a f
-
-        @return The string @a s where all occurrences of @a f are replaced
-                with @a t.
-
-        @pre The search string @a f must not be empty.
-        */
-        static void replace_substring(std::string& s,
-                                      const std::string& f,
-                                      const std::string& t)
-        {
-            assert(not f.empty());
-
-            for (
-                size_t pos = s.find(f);         // find first occurrence of f
-                pos != std::string::npos;       // make sure f was found
-                s.replace(pos, f.size(), t),    // replace with t
-                pos = s.find(f, pos + t.size()) // find next occurrence of f
-            );
-        }
-
         /// split the string input to reference tokens
         void split(std::string reference_string)
         {
@@ -8993,7 +8967,92 @@ basic_json_parser_63:
                 reference_tokens.push_back(reference_token);
             }
         }
+
+        /*!
+        @brief replace all occurrences of a substring by another string
+
+        @param[in,out] s  the string to manipulate
+        @param[in]     f  the substring to replace with @a t
+        @param[out]    t  the string to replace @a f
+
+        @return The string @a s where all occurrences of @a f are replaced
+                with @a t.
+
+        @pre The search string @a f must not be empty.
+
+        @since version 2.0.0
+        */
+        static void replace_substring(std::string& s,
+                                      const std::string& f,
+                                      const std::string& t)
+        {
+            assert(not f.empty());
+
+            for (
+                size_t pos = s.find(f);         // find first occurrence of f
+                pos != std::string::npos;       // make sure f was found
+                s.replace(pos, f.size(), t),    // replace with t
+                pos = s.find(f, pos + t.size()) // find next occurrence of f
+            );
+        }
+
+        /*!
+        @param[in] reference_string  the reference string to the current value
+        @param[in] value             the value to consider
+        @param[in,out] result        the result object to insert values to
+        */
+        static void flatten(const std::string reference_string,
+                            const basic_json& value,
+                            basic_json& result)
+        {
+            switch (value.m_type)
+            {
+                case value_t::array:
+                {
+                    // iterate array and use index as reference string
+                    for (size_t i = 0; i < value.m_value.array->size(); ++i)
+                    {
+                        flatten(reference_string + "/" + std::to_string(i),
+                                value.m_value.array->operator[](i), result);
+                    }
+                    break;
+                }
+
+                case value_t::object:
+                {
+                    // iterate object and use keys as reference string
+                    for (const auto& element : *value.m_value.object)
+                    {
+                        // escape "~"" to "~0" and "/" to "~1"
+                        std::string key(element.first);
+                        replace_substring(key, "~", "~0");
+                        replace_substring(key, "/", "~1");
+
+                        flatten(reference_string + "/" + key,
+                                element.second, result);
+                    }
+                    break;
+                }
+
+                default:
+                {
+                    // add primitive value with its reference string
+                    result[reference_string] = value;
+                    break;
+                }
+            }
+        }
     };
+
+    /*!
+    @return an object that maps JSON pointers to primitve values
+    */
+    basic_json flatten() const
+    {
+        basic_json result(value_t::object);
+        json_pointer::flatten("", *this, result);
+        return result;
+    }
 };
 
 
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index dd4eeb83..74827e2c 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -8217,32 +8217,6 @@ class basic_json
         /// the reference tokens
         std::vector<std::string> reference_tokens {};
 
-        /*!
-        @brief replace all occurrences of a substring by another string
-
-        @param[in,out] s  the string to manipulate
-        @param[in]     f  the substring to replace with @a t
-        @param[out]    t  the string to replace @a f
-
-        @return The string @a s where all occurrences of @a f are replaced
-                with @a t.
-
-        @pre The search string @a f must not be empty.
-        */
-        static void replace_substring(std::string& s,
-                                      const std::string& f,
-                                      const std::string& t)
-        {
-            assert(not f.empty());
-
-            for (
-                size_t pos = s.find(f);         // find first occurrence of f
-                pos != std::string::npos;       // make sure f was found
-                s.replace(pos, f.size(), t),    // replace with t
-                pos = s.find(f, pos + t.size()) // find next occurrence of f
-            );
-        }
-
         /// split the string input to reference tokens
         void split(std::string reference_string)
         {
@@ -8303,7 +8277,92 @@ class basic_json
                 reference_tokens.push_back(reference_token);
             }
         }
+
+        /*!
+        @brief replace all occurrences of a substring by another string
+
+        @param[in,out] s  the string to manipulate
+        @param[in]     f  the substring to replace with @a t
+        @param[out]    t  the string to replace @a f
+
+        @return The string @a s where all occurrences of @a f are replaced
+                with @a t.
+
+        @pre The search string @a f must not be empty.
+
+        @since version 2.0.0
+        */
+        static void replace_substring(std::string& s,
+                                      const std::string& f,
+                                      const std::string& t)
+        {
+            assert(not f.empty());
+
+            for (
+                size_t pos = s.find(f);         // find first occurrence of f
+                pos != std::string::npos;       // make sure f was found
+                s.replace(pos, f.size(), t),    // replace with t
+                pos = s.find(f, pos + t.size()) // find next occurrence of f
+            );
+        }
+
+        /*!
+        @param[in] reference_string  the reference string to the current value
+        @param[in] value             the value to consider
+        @param[in,out] result        the result object to insert values to
+        */
+        static void flatten(const std::string reference_string,
+                            const basic_json& value,
+                            basic_json& result)
+        {
+            switch (value.m_type)
+            {
+                case value_t::array:
+                {
+                    // iterate array and use index as reference string
+                    for (size_t i = 0; i < value.m_value.array->size(); ++i)
+                    {
+                        flatten(reference_string + "/" + std::to_string(i),
+                                value.m_value.array->operator[](i), result);
+                    }
+                    break;
+                }
+
+                case value_t::object:
+                {
+                    // iterate object and use keys as reference string
+                    for (const auto& element : *value.m_value.object)
+                    {
+                        // escape "~"" to "~0" and "/" to "~1"
+                        std::string key(element.first);
+                        replace_substring(key, "~", "~0");
+                        replace_substring(key, "/", "~1");
+
+                        flatten(reference_string + "/" + key,
+                                element.second, result);
+                    }
+                    break;
+                }
+
+                default:
+                {
+                    // add primitive value with its reference string
+                    result[reference_string] = value;
+                    break;
+                }
+            }
+        }
     };
+
+    /*!
+    @return an object that maps JSON pointers to primitve values
+    */
+    basic_json flatten() const
+    {
+        basic_json result(value_t::object);
+        json_pointer::flatten("", *this, result);
+        return result;
+    }
 };
 
 
diff --git a/test/unit.cpp b/test/unit.cpp
index 223de2c2..0d9aa0a7 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12168,6 +12168,53 @@ TEST_CASE("JSON pointers")
             CHECK_THROWS_WITH(json::json_pointer("/~"), "escape error: '~' must be followed with '0' or '1'");
         }
     }
+
+    SECTION("flatten")
+    {
+        json j =
+        {
+            {"pi", 3.141},
+            {"happy", true},
+            {"name", "Niels"},
+            {"nothing", nullptr},
+            {
+                "answer", {
+                    {"everything", 42}
+                }
+            },
+            {"list", {1, 0, 2}},
+            {
+                "object", {
+                    {"currency", "USD"},
+                    {"value", 42.99},
+                    {"", "empty string"},
+                    {"/", "slash"},
+                    {"~", "tilde"},
+                    {"~1", "tilde1"}
+                }
+            }
+        };
+
+        json j_flatten =
+        {
+            {"/pi", 3.141},
+            {"/happy", true},
+            {"/name", "Niels"},
+            {"/nothing", nullptr},
+            {"/answer/everything", 42},
+            {"/list/0", 1},
+            {"/list/1", 0},
+            {"/list/2", 2},
+            {"/object/currency", "USD"},
+            {"/object/value", 42.99},
+            {"/object/", "empty string"},
+            {"/object/~1", "slash"},
+            {"/object/~0", "tilde"},
+            {"/object/~01", "tilde1"}
+        };
+
+        CHECK(j.flatten() == j_flatten);
+    }
 }
 
 TEST_CASE("regression tests")