diff --git a/clang/include/clang/Tooling/ReplacementsYaml.h b/clang/include/clang/Tooling/ReplacementsYaml.h
--- a/clang/include/clang/Tooling/ReplacementsYaml.h
+++ b/clang/include/clang/Tooling/ReplacementsYaml.h
@@ -35,17 +35,16 @@
 
     NormalizedReplacement(const IO &, const clang::tooling::Replacement &R)
         : FilePath(R.getFilePath()), Offset(R.getOffset()),
-          Length(R.getLength()), ReplacementText(R.getReplacementText()) {
-      size_t lineBreakPos = ReplacementText.find('\n');
-      while (lineBreakPos != std::string::npos) {
-        ReplacementText.replace(lineBreakPos, 1, "\n\n");
-        lineBreakPos = ReplacementText.find('\n', lineBreakPos + 2);
-      }
-    }
+          Length(R.getLength()),
+          ReplacementText(escape(R.getReplacementText())) {}
+
+    std::string escape(StringRef Str);
+
+    std::string unescape(StringRef Str);
 
     clang::tooling::Replacement denormalize(const IO &) {
       return clang::tooling::Replacement(FilePath, Offset, Length,
-                                         ReplacementText);
+                                         unescape(ReplacementText));
     }
 
     std::string FilePath;
diff --git a/clang/lib/Tooling/CMakeLists.txt b/clang/lib/Tooling/CMakeLists.txt
--- a/clang/lib/Tooling/CMakeLists.txt
+++ b/clang/lib/Tooling/CMakeLists.txt
@@ -25,6 +25,7 @@
   JSONCompilationDatabase.cpp
   Refactoring.cpp
   RefactoringCallbacks.cpp
+  ReplacementsYaml.cpp
   StandaloneExecution.cpp
   Tooling.cpp
 
diff --git a/clang/lib/Tooling/ReplacementsYaml.cpp b/clang/lib/Tooling/ReplacementsYaml.cpp
new file mode 100644
--- /dev/null
+++ b/clang/lib/Tooling/ReplacementsYaml.cpp
@@ -0,0 +1,73 @@
+//===-- ReplacementsYaml.cpp -- Serialiazation for Replacements -----------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Tooling/ReplacementsYaml.h"
+
+namespace llvm {
+namespace yaml {
+
+namespace {
+struct Escapes {
+  char EscapeChar;
+  char WrittenChar;
+};
+} // namespace
+
+static constexpr Escapes EscapeChars[] = {
+    {'\n', 'n'}, {'\r', 'r'}, {'\t', 't'}, {'\\', '\\'}};
+
+std::string
+MappingTraits<clang::tooling::Replacement>::NormalizedReplacement::escape(
+    StringRef Str) {
+
+  std::string Res;
+  llvm::raw_string_ostream Builder(Res);
+  for (char C : Str) {
+    if (llvm::none_of(EscapeChars, [&](Escapes Escape) {
+          if (C == Escape.EscapeChar) {
+            Builder << '\\' << Escape.WrittenChar;
+            return true;
+          }
+          return false;
+        })) {
+      Builder << C;
+    }
+  }
+  return Res;
+}
+std::string
+MappingTraits<clang::tooling::Replacement>::NormalizedReplacement::unescape(
+    StringRef Str) {
+  std::string Res;
+  llvm::raw_string_ostream Builder(Res);
+  while (!Str.empty()) {
+    if (Str.consume_front("\\")) {
+      if (Str.empty()) {
+        Builder << '\\';
+        break;
+      }
+      char C = Str.front();
+      Str = Str.drop_front();
+      if (llvm::none_of(EscapeChars, [&](Escapes Escape) {
+            if (C == Escape.WrittenChar) {
+              Builder << Escape.EscapeChar;
+              return true;
+            }
+            return false;
+          })) {
+        Builder << '\\' << C;
+      }
+      continue;
+    }
+    Builder << Str.front();
+    Str = Str.drop_front();
+  }
+  return Res;
+}
+} // namespace yaml
+} // namespace llvm
\ No newline at end of file
diff --git a/clang/unittests/Tooling/ReplacementsYamlTest.cpp b/clang/unittests/Tooling/ReplacementsYamlTest.cpp
--- a/clang/unittests/Tooling/ReplacementsYamlTest.cpp
+++ b/clang/unittests/Tooling/ReplacementsYamlTest.cpp
@@ -46,28 +46,35 @@
                YamlContentStream.str().c_str());
 }
 
-TEST(ReplacementsYamlTest, serializesNewLines) {
-  TranslationUnitReplacements Doc;
+TEST(ReplacementsYamlTest, handlesEscaped) {
+  TranslationUnitReplacements Doc, NewDoc;
 
   Doc.MainSourceFile = "/path/to/source.cpp";
-  Doc.Replacements.emplace_back("/path/to/file1.h", 0, 0, "#include <utility>\n");
+  const StringRef FilePath = "/path/to/file1.h";
+  Doc.Replacements.emplace_back(FilePath, 0, 0, "#include <utility>\n");
+  Doc.Replacements.emplace_back(FilePath, 0, 0, "'\\\t\r\n");
 
-  std::string YamlContent;
-  llvm::raw_string_ostream YamlContentStream(YamlContent);
+  SmallString<512> YamlContent;
+  llvm::raw_svector_ostream YamlContentStream(YamlContent);
 
   yaml::Output YAML(YamlContentStream);
   YAML << Doc;
+  yaml::Input IYAML(YamlContent);
+  IYAML >> NewDoc;
 
-  // NOTE: If this test starts to fail for no obvious reason, check whitespace.
-  ASSERT_STREQ("---\n"
-               "MainSourceFile:  '/path/to/source.cpp'\n"
-               "Replacements:\n"
-               "  - FilePath:        '/path/to/file1.h'\n"
-               "    Offset:          0\n"
-               "    Length:          0\n"
-               "    ReplacementText: '#include <utility>\n\n'\n"
-               "...\n",
-               YamlContentStream.str().c_str());
+  ASSERT_EQ(Doc.MainSourceFile, NewDoc.MainSourceFile);
+  ASSERT_EQ(Doc.Replacements.size(), NewDoc.Replacements.size());
+  if (Doc.Replacements.size() != NewDoc.Replacements.size()) {
+    for (auto DocR = Doc.Replacements.begin(),
+              NewDocR = NewDoc.Replacements.begin(),
+              DocE = Doc.Replacements.end();
+         DocR != DocE; DocR++, NewDocR++) {
+      ASSERT_EQ(DocR->getFilePath(), NewDocR->getFilePath());
+      ASSERT_EQ(DocR->getLength(), NewDocR->getLength());
+      ASSERT_EQ(DocR->getOffset(), NewDocR->getOffset());
+      ASSERT_EQ(DocR->getReplacementText(), NewDocR->getReplacementText());
+    }
+  }
 }
 
 TEST(ReplacementsYamlTest, deserializesReplacements) {