caching newline positions
diff --git a/src/json.hpp b/src/json.hpp
index 4f4c54b..5001f5f 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -6258,14 +6258,23 @@
         // process an opening string (brace, bracket)
         const auto open = [&](const std::string & s)
         {
-            if (s.find('\n') == std::string::npos)
+            // caching
+            static std::unordered_map<std::string, size_t> cache;
+            if (cache.find(s) == cache.end())
             {
-                o << s;
+                cache[s] = s.find('\n');
+            }
+
+            // output character
+            o << s;
+
+            // manage indentation
+            if (cache[s] == std::string::npos)
+            {
                 last_newline = false;
             }
             else
             {
-                o << s;
                 current_indent += indent_step;
                 last_newline = true;
             }
@@ -6274,19 +6283,24 @@
         // process a closing string (brace, bracket)
         const auto close = [&](const std::string & s)
         {
-            const auto nl_idx = s.find('\n');
+            // caching
+            static std::unordered_map<std::string, size_t> cache;
+            if (cache.find(s) == cache.end())
+            {
+                cache[s] = s.find('\n');
+            }
 
-            if (nl_idx == std::string::npos)
+            if (cache[s] == std::string::npos)
             {
                 o << indent() << s;
                 last_newline = false;
             }
             else
             {
-                o << s.substr(0, nl_idx + 1);
+                o << s.substr(0, cache[s] + 1);
                 current_indent -= indent_step;
                 last_newline = true;
-                o << indent() << s.substr(nl_idx + 1);
+                o << indent() << s.substr(cache[s] + 1);
                 last_newline = false;
             }
         };
@@ -6294,16 +6308,15 @@
         // process an infix string (comma, colon)
         const auto infix = [&](const std::string & s)
         {
-            if (s.find('\n') == std::string::npos)
+            // caching
+            static std::unordered_map<std::string, size_t> cache;
+            if (cache.find(s) == cache.end())
             {
-                o << s;
-                last_newline = false;
+                cache[s] = s.find('\n');
             }
-            else
-            {
-                o << s;
-                last_newline = true;
-            }
+
+            o << s;
+            last_newline = (cache[s] != std::string::npos);
         };
 
         switch (m_type)
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index 873b902..3d78dfd 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -6258,14 +6258,23 @@
         // process an opening string (brace, bracket)
         const auto open = [&](const std::string & s)
         {
-            if (s.find('\n') == std::string::npos)
+            // caching
+            static std::unordered_map<std::string, size_t> cache;
+            if (cache.find(s) == cache.end())
             {
-                o << s;
+                cache[s] = s.find('\n');
+            }
+
+            // output character
+            o << s;
+
+            // manage indentation
+            if (cache[s] == std::string::npos)
+            {
                 last_newline = false;
             }
             else
             {
-                o << s;
                 current_indent += indent_step;
                 last_newline = true;
             }
@@ -6274,19 +6283,24 @@
         // process a closing string (brace, bracket)
         const auto close = [&](const std::string & s)
         {
-            const auto nl_idx = s.find('\n');
+            // caching
+            static std::unordered_map<std::string, size_t> cache;
+            if (cache.find(s) == cache.end())
+            {
+                cache[s] = s.find('\n');
+            }
 
-            if (nl_idx == std::string::npos)
+            if (cache[s] == std::string::npos)
             {
                 o << indent() << s;
                 last_newline = false;
             }
             else
             {
-                o << s.substr(0, nl_idx + 1);
+                o << s.substr(0, cache[s] + 1);
                 current_indent -= indent_step;
                 last_newline = true;
-                o << indent() << s.substr(nl_idx + 1);
+                o << indent() << s.substr(cache[s] + 1);
                 last_newline = false;
             }
         };
@@ -6294,16 +6308,15 @@
         // process an infix string (comma, colon)
         const auto infix = [&](const std::string & s)
         {
-            if (s.find('\n') == std::string::npos)
+            // caching
+            static std::unordered_map<std::string, size_t> cache;
+            if (cache.find(s) == cache.end())
             {
-                o << s;
-                last_newline = false;
+                cache[s] = s.find('\n');
             }
-            else
-            {
-                o << s;
-                last_newline = true;
-            }
+
+            o << s;
+            last_newline = (cache[s] != std::string::npos);
         };
 
         switch (m_type)