[clang-tools-extra] r319478 - [clangd] New conventions for JSON-marshalling functions, centralize machinery

Sam McCall via cfe-commits cfe-commits at lists.llvm.org
Thu Nov 30 13:32:29 PST 2017


Author: sammccall
Date: Thu Nov 30 13:32:29 2017
New Revision: 319478

URL: http://llvm.org/viewvc/llvm-project?rev=319478&view=rev
Log:
[clangd] New conventions for JSON-marshalling functions, centralize machinery

Summary:
 - JSON<->Obj interface is now ADL functions, so they play nicely with enums
 - recursive vector/map parsing and ObjectMapper moved to JSONExpr and tested
 - renamed (un)parse to (de)serialize, since text -> JSON is called parse
 - Protocol.cpp gets a bit shorter

Sorry for the giant patch, it's prety mechanical though

Reviewers: ilya-biryukov

Subscribers: klimek, cfe-commits

Differential Revision: https://reviews.llvm.org/D40596

Modified:
    clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp
    clang-tools-extra/trunk/clangd/JSONExpr.h
    clang-tools-extra/trunk/clangd/Protocol.cpp
    clang-tools-extra/trunk/clangd/Protocol.h
    clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp
    clang-tools-extra/trunk/test/clangd/trace.test
    clang-tools-extra/trunk/unittests/clangd/JSONExprTests.cpp

Modified: clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp?rev=319478&r1=319477&r2=319478&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp (original)
+++ clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp Thu Nov 30 13:32:29 2017
@@ -117,7 +117,7 @@ void ClangdLSPServer::onCommand(Ctx C, E
     // We don't need the response so id == 1 is OK.
     // Ideally, we would wait for the response and if there is no error, we
     // would reply success/failure to the original RPC.
-    C.call("workspace/applyEdit", ApplyWorkspaceEditParams::unparse(ApplyEdit));
+    C.call("workspace/applyEdit", ApplyEdit);
   } else {
     // We should not get here because ExecuteCommandParams would not have
     // parsed in the first place and this handler should not be called. But if
@@ -140,7 +140,7 @@ void ClangdLSPServer::onRename(Ctx C, Re
   std::vector<TextEdit> Edits = replacementsToEdits(Code, *Replacements);
   WorkspaceEdit WE;
   WE.changes = {{Params.textDocument.uri.uri, Edits}};
-  C.reply(WorkspaceEdit::unparse(WE));
+  C.reply(WE);
 }
 
 void ClangdLSPServer::onDocumentDidClose(Ctx C,

Modified: clang-tools-extra/trunk/clangd/JSONExpr.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/JSONExpr.h?rev=319478&r1=319477&r2=319478&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/JSONExpr.h (original)
+++ clang-tools-extra/trunk/clangd/JSONExpr.h Thu Nov 30 13:32:29 2017
@@ -36,7 +36,7 @@ namespace json {
 //   - booleans
 //   - null: nullptr
 //   - arrays: {"foo", 42.0, false}
-//   - serializable things: any T with a T::unparse(const T&) -> Expr
+//   - serializable things: types with toJSON(const T&)->Expr, found by ADL
 //
 // They can also be constructed from object/array helpers:
 //   - json::obj is a type like map<StringExpr, Expr>
@@ -65,6 +65,28 @@ namespace json {
 //       if (Optional<StringRef> Font = Opts->getString("font"))
 //         assert(Opts->at("font").kind() == Expr::String);
 //
+// === Converting expressions to objects ===
+//
+// The convention is to have a deserializer function findable via ADL:
+//     fromJSON(const json::Expr&, T&)->bool
+// Deserializers are provided for:
+//   - bool
+//   - int
+//   - double
+//   - std::string
+//   - vector<T>, where T is deserializable
+//   - map<string, T>, where T is deserializable
+//   - Optional<T>, where T is deserializable
+//
+// ObjectMapper can help writing fromJSON() functions for object types:
+//   bool fromJSON(const Expr &E, MyStruct &R) {
+//     ObjectMapper O(E);
+//     if (!O || !O.map("mandatory_field", R.MandatoryField))
+//       return false;
+//     O.map("optional_field", R.OptionalField);
+//     return true;
+//   }
+//
 // === Serialization ===
 //
 // Exprs can be serialized to JSON:
@@ -127,12 +149,11 @@ public:
   Expr(T D) : Type(T_Number) {
     create<double>(D);
   }
-  // Types with a static T::unparse function returning an Expr.
-  // FIXME: should this be a free unparse() function found by ADL?
+  // Types with a toJSON(const T&)->Expr function, found by ADL.
   template <typename T,
             typename = typename std::enable_if<std::is_same<
-                Expr, decltype(T::unparse(*(const T *)nullptr))>::value>>
-  Expr(const T &V) : Expr(T::unparse(V)) {}
+                Expr, decltype(toJSON(*(const T *)nullptr))>::value>>
+  Expr(const T &V) : Expr(toJSON(V)) {}
 
   Expr &operator=(const Expr &M) {
     destroy();
@@ -432,6 +453,101 @@ inline Expr::ObjectExpr::ObjectExpr(std:
 using obj = Expr::ObjectExpr;
 using ary = Expr::ArrayExpr;
 
+// Standard deserializers.
+inline bool fromJSON(const json::Expr &E, std::string &Out) {
+  if (auto S = E.asString()) {
+    Out = *S;
+    return true;
+  }
+  return false;
+}
+inline bool fromJSON(const json::Expr &E, int &Out) {
+  if (auto S = E.asInteger()) {
+    Out = *S;
+    return true;
+  }
+  return false;
+}
+inline bool fromJSON(const json::Expr &E, double &Out) {
+  if (auto S = E.asNumber()) {
+    Out = *S;
+    return true;
+  }
+  return false;
+}
+inline bool fromJSON(const json::Expr &E, bool &Out) {
+  if (auto S = E.asBoolean()) {
+    Out = *S;
+    return true;
+  }
+  return false;
+}
+template <typename T>
+bool fromJSON(const json::Expr &E, llvm::Optional<T> &Out) {
+  if (E.asNull()) {
+    Out = llvm::None;
+    return true;
+  }
+  T Result;
+  if (!fromJSON(E, Result))
+    return false;
+  Out = std::move(Result);
+  return true;
+}
+template <typename T> bool fromJSON(const json::Expr &E, std::vector<T> &Out) {
+  if (auto *A = E.asArray()) {
+    Out.clear();
+    Out.resize(A->size());
+    for (size_t I = 0; I < A->size(); ++I)
+      if (!fromJSON((*A)[I], Out[I]))
+        return false;
+    return true;
+  }
+  return false;
+}
+template <typename T>
+bool fromJSON(const json::Expr &E, std::map<std::string, T> &Out) {
+  if (auto *O = E.asObject()) {
+    Out.clear();
+    for (const auto &KV : *O)
+      if (!fromJSON(KV.second, Out[llvm::StringRef(KV.first)]))
+        return false;
+    return true;
+  }
+  return false;
+}
+
+// Helper for mapping JSON objects onto protocol structs.
+// See file header for example.
+class ObjectMapper {
+public:
+  ObjectMapper(const json::Expr &E) : O(E.asObject()) {}
+
+  // True if the expression is an object.
+  // Must be checked before calling map().
+  operator bool() { return O; }
+
+  // Maps a property to a field, if it exists.
+  template <typename T> bool map(const char *Prop, T &Out) {
+    assert(*this && "Must check this is an object before calling map()");
+    if (const json::Expr *E = O->get(Prop))
+      return fromJSON(*E, Out);
+    return false;
+  }
+
+  // Optional requires special handling, because missing keys are OK.
+  template <typename T> bool map(const char *Prop, llvm::Optional<T> &Out) {
+    assert(*this && "Must check this is an object before calling map()");
+    if (const json::Expr *E = O->get(Prop))
+      return fromJSON(*E, Out);
+    Out = llvm::None;
+    return true;
+  }
+
+private:
+  const json::obj *O;
+};
+
 llvm::Expected<Expr> parse(llvm::StringRef JSON);
 
 class ParseError : public llvm::ErrorInfo<ParseError> {

Modified: clang-tools-extra/trunk/clangd/Protocol.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Protocol.cpp?rev=319478&r1=319477&r2=319478&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/Protocol.cpp (original)
+++ clang-tools-extra/trunk/clangd/Protocol.cpp Thu Nov 30 13:32:29 2017
@@ -8,7 +8,6 @@
 //===----------------------------------------------------------------------===//
 //
 // This file contains the serialization code for the LSP structs.
-// FIXME: This is extremely repetetive and ugly. Is there a better way?
 //
 //===----------------------------------------------------------------------===//
 
@@ -21,151 +20,8 @@
 #include "llvm/Support/Path.h"
 #include "llvm/Support/raw_ostream.h"
 
-using namespace clang;
-using namespace clang::clangd;
-
-namespace {
-// Helper for mapping JSON objects onto our protocol structs. Intended use:
-// Optional<Result> parse(json::Expr E) {
-//   ObjectParser O(E);
-//   if (!O || !O.parse("mandatory_field", Result.MandatoryField))
-//     return None;
-//   O.parse("optional_field", Result.OptionalField);
-//   return Result;
-// }
-// FIXME: the static methods here should probably become the public parse()
-// extension point. Overloading free functions allows us to uniformly handle
-// enums, vectors, etc.
-class ObjectParser {
-public:
-  ObjectParser(const json::Expr &E) : O(E.asObject()) {}
-
-  // True if the expression is an object.
-  operator bool() { return O; }
-
-  template <typename T> bool parse(const char *Prop, T &Out) {
-    assert(*this && "Must check this is an object before calling parse()");
-    if (const json::Expr *E = O->get(Prop))
-      return parse(*E, Out);
-    return false;
-  }
-
-  // Optional requires special handling, because missing keys are OK.
-  template <typename T> bool parse(const char *Prop, llvm::Optional<T> &Out) {
-    assert(*this && "Must check this is an object before calling parse()");
-    if (const json::Expr *E = O->get(Prop))
-      return parse(*E, Out);
-    Out = None;
-    return true;
-  }
-
-private:
-  // Primitives.
-  static bool parse(const json::Expr &E, std::string &Out) {
-    if (auto S = E.asString()) {
-      Out = *S;
-      return true;
-    }
-    return false;
-  }
-
-  static bool parse(const json::Expr &E, int &Out) {
-    if (auto S = E.asInteger()) {
-      Out = *S;
-      return true;
-    }
-    return false;
-  }
-
-  static bool parse(const json::Expr &E, bool &Out) {
-    if (auto S = E.asBoolean()) {
-      Out = *S;
-      return true;
-    }
-    return false;
-  }
-
-  // Types with a parse() function.
-  template <typename T> static bool parse(const json::Expr &E, T &Out) {
-    if (auto Parsed = std::remove_reference<T>::type::parse(E)) {
-      Out = std::move(*Parsed);
-      return true;
-    }
-    return false;
-  }
-
-  // Nullable values as Optional<T>.
-  template <typename T>
-  static bool parse(const json::Expr &E, llvm::Optional<T> &Out) {
-    if (E.asNull()) {
-      Out = None;
-      return true;
-    }
-    T Result;
-    if (!parse(E, Result))
-      return false;
-    Out = std::move(Result);
-    return true;
-  }
-
-  // Array values with std::vector type.
-  template <typename T>
-  static bool parse(const json::Expr &E, std::vector<T> &Out) {
-    if (auto *A = E.asArray()) {
-      Out.clear();
-      Out.resize(A->size());
-      for (size_t I = 0; I < A->size(); ++I)
-        if (!parse((*A)[I], Out[I]))
-          return false;
-      return true;
-    }
-    return false;
-  }
-
-  // Object values with std::map<std::string, ?>
-  template <typename T>
-  static bool parse(const json::Expr &E, std::map<std::string, T> &Out) {
-    if (auto *O = E.asObject()) {
-      for (const auto &KV : *O)
-        if (!parse(KV.second, Out[StringRef(KV.first)]))
-          return false;
-      return true;
-    }
-    return false;
-  }
-
-  // Special cased enums, which can't have T::parse() functions.
-  // FIXME: make everything free functions so there's no special casing.
-  static bool parse(const json::Expr &E, TraceLevel &Out) {
-    if (auto S = E.asString()) {
-      if (*S == "off") {
-        Out = TraceLevel::Off;
-        return true;
-      } else if (*S == "messages") {
-        Out = TraceLevel::Messages;
-        return true;
-      } else if (*S == "verbose") {
-        Out = TraceLevel::Verbose;
-        return true;
-      }
-    }
-    return false;
-  }
-
-  static bool parse(const json::Expr &E, FileChangeType &Out) {
-    if (auto T = E.asInteger()) {
-      if (*T < static_cast<int>(FileChangeType::Created) ||
-          *T > static_cast<int>(FileChangeType::Deleted))
-        return false;
-      Out = static_cast<FileChangeType>(*T);
-      return true;
-    }
-    return false;
-  }
-
-  const json::obj *O;
-};
-} // namespace
+namespace clang {
+namespace clangd {
 
 URI URI::fromUri(llvm::StringRef uri) {
   URI Result;
@@ -194,276 +50,224 @@ URI URI::fromFile(llvm::StringRef file)
   return Result;
 }
 
-llvm::Optional<URI> URI::parse(const json::Expr &E) {
-  if (auto S = E.asString())
-    return fromUri(*S);
-  return None;
+bool fromJSON(const json::Expr &E, URI &R) {
+  if (auto S = E.asString()) {
+    R = URI::fromUri(*S);
+    return true;
+  }
+  return false;
 }
 
-json::Expr URI::unparse(const URI &U) { return U.uri; }
+json::Expr toJSON(const URI &U) { return U.uri; }
 
-llvm::Optional<TextDocumentIdentifier>
-TextDocumentIdentifier::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  TextDocumentIdentifier R;
-  if (!O || !O.parse("uri", R.uri))
-    return None;
-  return R;
+bool fromJSON(const json::Expr &Params, TextDocumentIdentifier &R) {
+  json::ObjectMapper O(Params);
+  return O && O.map("uri", R.uri);
 }
 
-llvm::Optional<Position> Position::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  Position R;
-  if (!O || !O.parse("line", R.line) || !O.parse("character", R.character))
-    return None;
-  return R;
+bool fromJSON(const json::Expr &Params, Position &R) {
+  json::ObjectMapper O(Params);
+  return O && O.map("line", R.line) && O.map("character", R.character);
 }
 
-json::Expr Position::unparse(const Position &P) {
+json::Expr toJSON(const Position &P) {
   return json::obj{
       {"line", P.line},
       {"character", P.character},
   };
 }
 
-llvm::Optional<Range> Range::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  Range R;
-  if (!O || !O.parse("start", R.start) || !O.parse("end", R.end))
-    return None;
-  return R;
+bool fromJSON(const json::Expr &Params, Range &R) {
+  json::ObjectMapper O(Params);
+  return O && O.map("start", R.start) && O.map("end", R.end);
 }
 
-json::Expr Range::unparse(const Range &P) {
+json::Expr toJSON(const Range &P) {
   return json::obj{
       {"start", P.start},
       {"end", P.end},
   };
 }
 
-json::Expr Location::unparse(const Location &P) {
+json::Expr toJSON(const Location &P) {
   return json::obj{
       {"uri", P.uri},
       {"range", P.range},
   };
 }
 
-llvm::Optional<TextDocumentItem>
-TextDocumentItem::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  TextDocumentItem R;
-  if (!O || !O.parse("uri", R.uri) || !O.parse("languageId", R.languageId) ||
-      !O.parse("version", R.version) || !O.parse("text", R.text))
-    return None;
-  return R;
+bool fromJSON(const json::Expr &Params, TextDocumentItem &R) {
+  json::ObjectMapper O(Params);
+  return O && O.map("uri", R.uri) && O.map("languageId", R.languageId) &&
+         O.map("version", R.version) && O.map("text", R.text);
 }
 
-llvm::Optional<Metadata> Metadata::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  Metadata R;
+bool fromJSON(const json::Expr &Params, Metadata &R) {
+  json::ObjectMapper O(Params);
   if (!O)
-    return None;
-  O.parse("extraFlags", R.extraFlags);
-  return R;
+    return false;
+  O.map("extraFlags", R.extraFlags);
+  return true;
 }
 
-llvm::Optional<TextEdit> TextEdit::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  TextEdit R;
-  if (!O || !O.parse("range", R.range) || !O.parse("newText", R.newText))
-    return None;
-  return R;
+bool fromJSON(const json::Expr &Params, TextEdit &R) {
+  json::ObjectMapper O(Params);
+  return O && O.map("range", R.range) && O.map("newText", R.newText);
 }
 
-json::Expr TextEdit::unparse(const TextEdit &P) {
+json::Expr toJSON(const TextEdit &P) {
   return json::obj{
       {"range", P.range},
       {"newText", P.newText},
   };
 }
 
-llvm::Optional<InitializeParams>
-InitializeParams::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  InitializeParams R;
+bool fromJSON(const json::Expr &E, TraceLevel &Out) {
+  if (auto S = E.asString()) {
+    if (*S == "off") {
+      Out = TraceLevel::Off;
+      return true;
+    } else if (*S == "messages") {
+      Out = TraceLevel::Messages;
+      return true;
+    } else if (*S == "verbose") {
+      Out = TraceLevel::Verbose;
+      return true;
+    }
+  }
+  return false;
+}
+
+bool fromJSON(const json::Expr &Params, InitializeParams &R) {
+  json::ObjectMapper O(Params);
   if (!O)
-    return None;
+    return false;
   // We deliberately don't fail if we can't parse individual fields.
   // Failing to handle a slightly malformed initialize would be a disaster.
-  O.parse("processId", R.processId);
-  O.parse("rootUri", R.rootUri);
-  O.parse("rootPath", R.rootPath);
-  O.parse("trace", R.trace);
+  O.map("processId", R.processId);
+  O.map("rootUri", R.rootUri);
+  O.map("rootPath", R.rootPath);
+  O.map("trace", R.trace);
   // initializationOptions, capabilities unused
-  return R;
+  return true;
+}
+
+bool fromJSON(const json::Expr &Params, DidOpenTextDocumentParams &R) {
+  json::ObjectMapper O(Params);
+  return O && O.map("textDocument", R.textDocument) &&
+         O.map("metadata", R.metadata);
+}
+
+bool fromJSON(const json::Expr &Params, DidCloseTextDocumentParams &R) {
+  json::ObjectMapper O(Params);
+  return O && O.map("textDocument", R.textDocument);
+}
+
+bool fromJSON(const json::Expr &Params, DidChangeTextDocumentParams &R) {
+  json::ObjectMapper O(Params);
+  return O && O.map("textDocument", R.textDocument) &&
+         O.map("contentChanges", R.contentChanges);
+}
+
+bool fromJSON(const json::Expr &E, FileChangeType &Out) {
+  if (auto T = E.asInteger()) {
+    if (*T < static_cast<int>(FileChangeType::Created) ||
+        *T > static_cast<int>(FileChangeType::Deleted))
+      return false;
+    Out = static_cast<FileChangeType>(*T);
+    return true;
+  }
+  return false;
 }
 
-llvm::Optional<DidOpenTextDocumentParams>
-DidOpenTextDocumentParams::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  DidOpenTextDocumentParams R;
-  if (!O || !O.parse("textDocument", R.textDocument) ||
-      !O.parse("metadata", R.metadata))
-    return None;
-  return R;
-}
-
-llvm::Optional<DidCloseTextDocumentParams>
-DidCloseTextDocumentParams::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  DidCloseTextDocumentParams R;
-  if (!O || !O.parse("textDocument", R.textDocument))
-    return None;
-  return R;
-}
-
-llvm::Optional<DidChangeTextDocumentParams>
-DidChangeTextDocumentParams::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  DidChangeTextDocumentParams R;
-  if (!O || !O.parse("textDocument", R.textDocument) ||
-      !O.parse("contentChanges", R.contentChanges))
-    return None;
-  return R;
-}
-
-llvm::Optional<FileEvent> FileEvent::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  FileEvent R;
-  if (!O || !O.parse("uri", R.uri) || !O.parse("type", R.type))
-    return None;
-  return R;
-}
-
-llvm::Optional<DidChangeWatchedFilesParams>
-DidChangeWatchedFilesParams::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  DidChangeWatchedFilesParams R;
-  if (!O || !O.parse("changes", R.changes))
-    return None;
-  return R;
-}
-
-llvm::Optional<TextDocumentContentChangeEvent>
-TextDocumentContentChangeEvent::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  TextDocumentContentChangeEvent R;
-  if (!O || !O.parse("text", R.text))
-    return None;
-  return R;
-}
-
-llvm::Optional<FormattingOptions>
-FormattingOptions::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  FormattingOptions R;
-  if (!O || !O.parse("tabSize", R.tabSize) ||
-      !O.parse("insertSpaces", R.insertSpaces))
-    return None;
-  return R;
+bool fromJSON(const json::Expr &Params, FileEvent &R) {
+  json::ObjectMapper O(Params);
+  return O && O.map("uri", R.uri) && O.map("type", R.type);
 }
 
-json::Expr FormattingOptions::unparse(const FormattingOptions &P) {
+bool fromJSON(const json::Expr &Params, DidChangeWatchedFilesParams &R) {
+  json::ObjectMapper O(Params);
+  return O && O.map("changes", R.changes);
+}
+
+bool fromJSON(const json::Expr &Params, TextDocumentContentChangeEvent &R) {
+  json::ObjectMapper O(Params);
+  return O && O.map("text", R.text);
+}
+
+bool fromJSON(const json::Expr &Params, FormattingOptions &R) {
+  json::ObjectMapper O(Params);
+  return O && O.map("tabSize", R.tabSize) &&
+         O.map("insertSpaces", R.insertSpaces);
+}
+
+json::Expr toJSON(const FormattingOptions &P) {
   return json::obj{
       {"tabSize", P.tabSize},
       {"insertSpaces", P.insertSpaces},
   };
 }
 
-llvm::Optional<DocumentRangeFormattingParams>
-DocumentRangeFormattingParams::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  DocumentRangeFormattingParams R;
-  if (!O || !O.parse("textDocument", R.textDocument) ||
-      !O.parse("range", R.range) || !O.parse("options", R.options))
-    return None;
-  return R;
-}
-
-llvm::Optional<DocumentOnTypeFormattingParams>
-DocumentOnTypeFormattingParams::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  DocumentOnTypeFormattingParams R;
-  if (!O || !O.parse("textDocument", R.textDocument) ||
-      !O.parse("position", R.position) || !O.parse("ch", R.ch) ||
-      !O.parse("options", R.options))
-    return None;
-  return R;
-}
-
-llvm::Optional<DocumentFormattingParams>
-DocumentFormattingParams::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  DocumentFormattingParams R;
-  if (!O || !O.parse("textDocument", R.textDocument) ||
-      !O.parse("options", R.options))
-    return None;
-  return R;
-}
-
-llvm::Optional<Diagnostic> Diagnostic::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  Diagnostic R;
-  if (!O || !O.parse("range", R.range) || !O.parse("message", R.message))
-    return None;
-  O.parse("severity", R.severity);
-  return R;
-}
-
-llvm::Optional<CodeActionContext>
-CodeActionContext::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  CodeActionContext R;
-  if (!O || !O.parse("diagnostics", R.diagnostics))
-    return None;
-  return R;
-}
-
-llvm::Optional<CodeActionParams>
-CodeActionParams::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  CodeActionParams R;
-  if (!O || !O.parse("textDocument", R.textDocument) ||
-      !O.parse("range", R.range) || !O.parse("context", R.context))
-    return None;
-  return R;
-}
-
-llvm::Optional<WorkspaceEdit> WorkspaceEdit::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  WorkspaceEdit R;
-  if (!O || !O.parse("changes", R.changes))
-    return None;
-  return R;
+bool fromJSON(const json::Expr &Params, DocumentRangeFormattingParams &R) {
+  json::ObjectMapper O(Params);
+  return O && O.map("textDocument", R.textDocument) &&
+         O.map("range", R.range) && O.map("options", R.options);
+}
+
+bool fromJSON(const json::Expr &Params, DocumentOnTypeFormattingParams &R) {
+  json::ObjectMapper O(Params);
+  return O && O.map("textDocument", R.textDocument) &&
+         O.map("position", R.position) && O.map("ch", R.ch) &&
+         O.map("options", R.options);
+}
+
+bool fromJSON(const json::Expr &Params, DocumentFormattingParams &R) {
+  json::ObjectMapper O(Params);
+  return O && O.map("textDocument", R.textDocument) &&
+         O.map("options", R.options);
+}
+
+bool fromJSON(const json::Expr &Params, Diagnostic &R) {
+  json::ObjectMapper O(Params);
+  if (!O || !O.map("range", R.range) || !O.map("message", R.message))
+    return false;
+  O.map("severity", R.severity);
+  return true;
+}
+
+bool fromJSON(const json::Expr &Params, CodeActionContext &R) {
+  json::ObjectMapper O(Params);
+  return O && O.map("diagnostics", R.diagnostics);
+}
+
+bool fromJSON(const json::Expr &Params, CodeActionParams &R) {
+  json::ObjectMapper O(Params);
+  return O && O.map("textDocument", R.textDocument) &&
+         O.map("range", R.range) && O.map("context", R.context);
+}
+
+bool fromJSON(const json::Expr &Params, WorkspaceEdit &R) {
+  json::ObjectMapper O(Params);
+  return O && O.map("changes", R.changes);
 }
 
 const std::string ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND =
     "clangd.applyFix";
 
-llvm::Optional<ExecuteCommandParams>
-ExecuteCommandParams::parse(const json::Expr &Params) {
-  const json::obj *O = Params.asObject();
-  if (!O)
-    return None;
+bool fromJSON(const json::Expr &Params, ExecuteCommandParams &R) {
+  json::ObjectMapper O(Params);
+  if (!O || !O.map("command", R.command))
+    return false;
 
-  ExecuteCommandParams Result;
-  if (auto Command = O->getString("command"))
-    Result.command = *Command;
-  auto Args = O->getArray("arguments");
-
-  if (Result.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND) {
-    if (!Args || Args->size() != 1)
-      return llvm::None;
-    if (auto Parsed = WorkspaceEdit::parse(Args->front()))
-      Result.workspaceEdit = std::move(*Parsed);
-    else
-      return llvm::None;
-  } else
-    return llvm::None; // Unrecognized command.
-  return Result;
+  auto Args = Params.asObject()->getArray("arguments");
+  if (R.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND) {
+    return Args && Args->size() == 1 &&
+           fromJSON(Args->front(), R.workspaceEdit);
+  }
+  return false; // Unrecognized command.
 }
 
-json::Expr WorkspaceEdit::unparse(const WorkspaceEdit &WE) {
+json::Expr toJSON(const WorkspaceEdit &WE) {
   if (!WE.changes)
     return json::obj{};
   json::obj FileChanges;
@@ -472,22 +276,17 @@ json::Expr WorkspaceEdit::unparse(const
   return json::obj{{"changes", std::move(FileChanges)}};
 }
 
-json::Expr
-ApplyWorkspaceEditParams::unparse(const ApplyWorkspaceEditParams &Params) {
+json::Expr toJSON(const ApplyWorkspaceEditParams &Params) {
   return json::obj{{"edit", Params.edit}};
 }
 
-llvm::Optional<TextDocumentPositionParams>
-TextDocumentPositionParams::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  TextDocumentPositionParams R;
-  if (!O || !O.parse("textDocument", R.textDocument) ||
-      !O.parse("position", R.position))
-    return None;
-  return R;
+bool fromJSON(const json::Expr &Params, TextDocumentPositionParams &R) {
+  json::ObjectMapper O(Params);
+  return O && O.map("textDocument", R.textDocument) &&
+         O.map("position", R.position);
 }
 
-json::Expr CompletionItem::unparse(const CompletionItem &CI) {
+json::Expr toJSON(const CompletionItem &CI) {
   assert(!CI.label.empty() && "completion item label is required");
   json::obj Result{{"label", CI.label}};
   if (CI.kind != CompletionItemKind::Missing)
@@ -511,19 +310,19 @@ json::Expr CompletionItem::unparse(const
   return std::move(Result);
 }
 
-bool clangd::operator<(const CompletionItem &L, const CompletionItem &R) {
+bool operator<(const CompletionItem &L, const CompletionItem &R) {
   return (L.sortText.empty() ? L.label : L.sortText) <
          (R.sortText.empty() ? R.label : R.sortText);
 }
 
-json::Expr CompletionList::unparse(const CompletionList &L) {
+json::Expr toJSON(const CompletionList &L) {
   return json::obj{
       {"isIncomplete", L.isIncomplete},
       {"items", json::ary(L.items)},
   };
 }
 
-json::Expr ParameterInformation::unparse(const ParameterInformation &PI) {
+json::Expr toJSON(const ParameterInformation &PI) {
   assert(!PI.label.empty() && "parameter information label is required");
   json::obj Result{{"label", PI.label}};
   if (!PI.documentation.empty())
@@ -531,7 +330,7 @@ json::Expr ParameterInformation::unparse
   return std::move(Result);
 }
 
-json::Expr SignatureInformation::unparse(const SignatureInformation &SI) {
+json::Expr toJSON(const SignatureInformation &SI) {
   assert(!SI.label.empty() && "signature information label is required");
   json::obj Result{
       {"label", SI.label},
@@ -542,7 +341,7 @@ json::Expr SignatureInformation::unparse
   return std::move(Result);
 }
 
-json::Expr SignatureHelp::unparse(const SignatureHelp &SH) {
+json::Expr toJSON(const SignatureHelp &SH) {
   assert(SH.activeSignature >= 0 &&
          "Unexpected negative value for number of active signatures.");
   assert(SH.activeParameter >= 0 &&
@@ -554,11 +353,11 @@ json::Expr SignatureHelp::unparse(const
   };
 }
 
-llvm::Optional<RenameParams> RenameParams::parse(const json::Expr &Params) {
-  ObjectParser O(Params);
-  RenameParams R;
-  if (!O || !O.parse("textDocument", R.textDocument) ||
-      !O.parse("position", R.position) || !O.parse("newName", R.newName))
-    return None;
-  return R;
+bool fromJSON(const json::Expr &Params, RenameParams &R) {
+  json::ObjectMapper O(Params);
+  return O && O.map("textDocument", R.textDocument) &&
+         O.map("position", R.position) && O.map("newName", R.newName);
 }
+
+} // namespace clangd
+} // namespace clang

Modified: clang-tools-extra/trunk/clangd/Protocol.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Protocol.h?rev=319478&r1=319477&r2=319478&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/Protocol.h (original)
+++ clang-tools-extra/trunk/clangd/Protocol.h Thu Nov 30 13:32:29 2017
@@ -13,8 +13,8 @@
 // This is not meant to be a complete implementation, new interfaces are added
 // when they're needed.
 //
-// Each struct has a parse and unparse function, that converts back and forth
-// between the struct and a JSON representation.
+// Each struct has a toJSON and fromJSON function, that converts between
+// the struct and a JSON representation. (See JSONExpr.h)
 //
 //===----------------------------------------------------------------------===//
 
@@ -51,9 +51,6 @@ struct URI {
   static URI fromUri(llvm::StringRef uri);
   static URI fromFile(llvm::StringRef file);
 
-  static llvm::Optional<URI> parse(const json::Expr &U);
-  static json::Expr unparse(const URI &U);
-
   friend bool operator==(const URI &LHS, const URI &RHS) {
     return LHS.uri == RHS.uri;
   }
@@ -66,13 +63,14 @@ struct URI {
     return LHS.uri < RHS.uri;
   }
 };
+json::Expr toJSON(const URI &U);
+bool fromJSON(const json::Expr &, URI &);
 
 struct TextDocumentIdentifier {
   /// The text document's URI.
   URI uri;
-
-  static llvm::Optional<TextDocumentIdentifier> parse(const json::Expr &Params);
 };
+bool fromJSON(const json::Expr &, TextDocumentIdentifier &);
 
 struct Position {
   /// Line position in a document (zero-based).
@@ -89,10 +87,9 @@ struct Position {
     return std::tie(LHS.line, LHS.character) <
            std::tie(RHS.line, RHS.character);
   }
-
-  static llvm::Optional<Position> parse(const json::Expr &Params);
-  static json::Expr unparse(const Position &P);
 };
+bool fromJSON(const json::Expr &, Position &);
+json::Expr toJSON(const Position &);
 
 struct Range {
   /// The range's start position.
@@ -107,10 +104,9 @@ struct Range {
   friend bool operator<(const Range &LHS, const Range &RHS) {
     return std::tie(LHS.start, LHS.end) < std::tie(RHS.start, RHS.end);
   }
-
-  static llvm::Optional<Range> parse(const json::Expr &Params);
-  static json::Expr unparse(const Range &P);
 };
+bool fromJSON(const json::Expr &, Range &);
+json::Expr toJSON(const Range &);
 
 struct Location {
   /// The text document's URI.
@@ -128,15 +124,13 @@ struct Location {
   friend bool operator<(const Location &LHS, const Location &RHS) {
     return std::tie(LHS.uri, LHS.range) < std::tie(RHS.uri, RHS.range);
   }
-
-  static json::Expr unparse(const Location &P);
 };
+json::Expr toJSON(const Location &);
 
 struct Metadata {
   std::vector<std::string> extraFlags;
-
-  static llvm::Optional<Metadata> parse(const json::Expr &Params);
 };
+bool fromJSON(const json::Expr &, Metadata &);
 
 struct TextEdit {
   /// The range of the text document to be manipulated. To insert
@@ -146,10 +140,9 @@ struct TextEdit {
   /// The string to be inserted. For delete operations use an
   /// empty string.
   std::string newText;
-
-  static llvm::Optional<TextEdit> parse(const json::Expr &Params);
-  static json::Expr unparse(const TextEdit &P);
 };
+bool fromJSON(const json::Expr &, TextEdit &);
+json::Expr toJSON(const TextEdit &);
 
 struct TextDocumentItem {
   /// The text document's URI.
@@ -163,21 +156,18 @@ struct TextDocumentItem {
 
   /// The content of the opened text document.
   std::string text;
-
-  static llvm::Optional<TextDocumentItem> parse(const json::Expr &Params);
 };
+bool fromJSON(const json::Expr &, TextDocumentItem &);
 
 enum class TraceLevel {
   Off = 0,
   Messages = 1,
   Verbose = 2,
 };
+bool fromJSON(const json::Expr &E, TraceLevel &Out);
 
-struct NoParams {
-  static llvm::Optional<NoParams> parse(const json::Expr &Params) {
-    return NoParams{};
-  }
-};
+struct NoParams {};
+inline bool fromJSON(const json::Expr &, NoParams &) { return true; }
 using ShutdownParams = NoParams;
 using ExitParams = NoParams;
 
@@ -208,8 +198,8 @@ struct InitializeParams {
 
   /// The initial trace setting. If omitted trace is disabled ('off').
   llvm::Optional<TraceLevel> trace;
-  static llvm::Optional<InitializeParams> parse(const json::Expr &Params);
 };
+bool fromJSON(const json::Expr &, InitializeParams &);
 
 struct DidOpenTextDocumentParams {
   /// The document that was opened.
@@ -217,26 +207,20 @@ struct DidOpenTextDocumentParams {
 
   /// Extension storing per-file metadata, such as compilation flags.
   llvm::Optional<Metadata> metadata;
-
-  static llvm::Optional<DidOpenTextDocumentParams>
-  parse(const json::Expr &Params);
 };
+bool fromJSON(const json::Expr &, DidOpenTextDocumentParams &);
 
 struct DidCloseTextDocumentParams {
   /// The document that was closed.
   TextDocumentIdentifier textDocument;
-
-  static llvm::Optional<DidCloseTextDocumentParams>
-  parse(const json::Expr &Params);
 };
+bool fromJSON(const json::Expr &, DidCloseTextDocumentParams &);
 
 struct TextDocumentContentChangeEvent {
   /// The new text of the document.
   std::string text;
-
-  static llvm::Optional<TextDocumentContentChangeEvent>
-  parse(const json::Expr &Params);
 };
+bool fromJSON(const json::Expr &, TextDocumentContentChangeEvent &);
 
 struct DidChangeTextDocumentParams {
   /// The document that did change. The version number points
@@ -246,10 +230,8 @@ struct DidChangeTextDocumentParams {
 
   /// The actual content changes.
   std::vector<TextDocumentContentChangeEvent> contentChanges;
-
-  static llvm::Optional<DidChangeTextDocumentParams>
-  parse(const json::Expr &Params);
 };
+bool fromJSON(const json::Expr &, DidChangeTextDocumentParams &);
 
 enum class FileChangeType {
   /// The file got created.
@@ -259,23 +241,21 @@ enum class FileChangeType {
   /// The file got deleted.
   Deleted = 3
 };
+bool fromJSON(const json::Expr &E, FileChangeType &Out);
 
 struct FileEvent {
   /// The file's URI.
   URI uri;
   /// The change type.
   FileChangeType type;
-
-  static llvm::Optional<FileEvent> parse(const json::Expr &Params);
 };
+bool fromJSON(const json::Expr &, FileEvent &);
 
 struct DidChangeWatchedFilesParams {
   /// The actual file events.
   std::vector<FileEvent> changes;
-
-  static llvm::Optional<DidChangeWatchedFilesParams>
-  parse(const json::Expr &Params);
 };
+bool fromJSON(const json::Expr &, DidChangeWatchedFilesParams &);
 
 struct FormattingOptions {
   /// Size of a tab in spaces.
@@ -283,10 +263,9 @@ struct FormattingOptions {
 
   /// Prefer spaces over tabs.
   bool insertSpaces;
-
-  static llvm::Optional<FormattingOptions> parse(const json::Expr &Params);
-  static json::Expr unparse(const FormattingOptions &P);
 };
+bool fromJSON(const json::Expr &, FormattingOptions &);
+json::Expr toJSON(const FormattingOptions &);
 
 struct DocumentRangeFormattingParams {
   /// The document to format.
@@ -297,10 +276,8 @@ struct DocumentRangeFormattingParams {
 
   /// The format options
   FormattingOptions options;
-
-  static llvm::Optional<DocumentRangeFormattingParams>
-  parse(const json::Expr &Params);
 };
+bool fromJSON(const json::Expr &, DocumentRangeFormattingParams &);
 
 struct DocumentOnTypeFormattingParams {
   /// The document to format.
@@ -314,10 +291,8 @@ struct DocumentOnTypeFormattingParams {
 
   /// The format options.
   FormattingOptions options;
-
-  static llvm::Optional<DocumentOnTypeFormattingParams>
-  parse(const json::Expr &Params);
 };
+bool fromJSON(const json::Expr &, DocumentOnTypeFormattingParams &);
 
 struct DocumentFormattingParams {
   /// The document to format.
@@ -325,10 +300,8 @@ struct DocumentFormattingParams {
 
   /// The format options
   FormattingOptions options;
-
-  static llvm::Optional<DocumentFormattingParams>
-  parse(const json::Expr &Params);
 };
+bool fromJSON(const json::Expr &, DocumentFormattingParams &);
 
 struct Diagnostic {
   /// The range at which the message applies.
@@ -358,16 +331,14 @@ struct Diagnostic {
     return std::tie(LHS.range, LHS.severity, LHS.message) <
            std::tie(RHS.range, RHS.severity, RHS.message);
   }
-
-  static llvm::Optional<Diagnostic> parse(const json::Expr &Params);
 };
+bool fromJSON(const json::Expr &, Diagnostic &);
 
 struct CodeActionContext {
   /// An array of diagnostics.
   std::vector<Diagnostic> diagnostics;
-
-  static llvm::Optional<CodeActionContext> parse(const json::Expr &Params);
 };
+bool fromJSON(const json::Expr &, CodeActionContext &);
 
 struct CodeActionParams {
   /// The document in which the command was invoked.
@@ -378,9 +349,8 @@ struct CodeActionParams {
 
   /// Context carrying additional information.
   CodeActionContext context;
-
-  static llvm::Optional<CodeActionParams> parse(const json::Expr &Params);
 };
+bool fromJSON(const json::Expr &, CodeActionParams &);
 
 struct WorkspaceEdit {
   /// Holds changes to existing resources.
@@ -388,10 +358,9 @@ struct WorkspaceEdit {
 
   /// Note: "documentChanges" is not currently used because currently there is
   /// no support for versioned edits.
-
-  static llvm::Optional<WorkspaceEdit> parse(const json::Expr &Params);
-  static json::Expr unparse(const WorkspaceEdit &WE);
 };
+bool fromJSON(const json::Expr &, WorkspaceEdit &);
+json::Expr toJSON(const WorkspaceEdit &WE);
 
 /// Exact commands are not specified in the protocol so we define the
 /// ones supported by Clangd here. The protocol specifies the command arguments
@@ -411,14 +380,13 @@ struct ExecuteCommandParams {
   // Arguments
 
   llvm::Optional<WorkspaceEdit> workspaceEdit;
-
-  static llvm::Optional<ExecuteCommandParams> parse(const json::Expr &Params);
 };
+bool fromJSON(const json::Expr &, ExecuteCommandParams &);
 
 struct ApplyWorkspaceEditParams {
   WorkspaceEdit edit;
-  static json::Expr unparse(const ApplyWorkspaceEditParams &Params);
 };
+json::Expr toJSON(const ApplyWorkspaceEditParams &);
 
 struct TextDocumentPositionParams {
   /// The text document.
@@ -426,10 +394,8 @@ struct TextDocumentPositionParams {
 
   /// The position inside the text document.
   Position position;
-
-  static llvm::Optional<TextDocumentPositionParams>
-  parse(const json::Expr &Params);
 };
+bool fromJSON(const json::Expr &, TextDocumentPositionParams &);
 
 /// The kind of a completion entry.
 enum class CompletionItemKind {
@@ -524,8 +490,8 @@ struct CompletionItem {
   //
   // data?: any - A data entry field that is preserved on a completion item
   //              between a completion and a completion resolve request.
-  static json::Expr unparse(const CompletionItem &P);
 };
+json::Expr toJSON(const CompletionItem &);
 
 bool operator<(const CompletionItem &, const CompletionItem &);
 
@@ -537,9 +503,8 @@ struct CompletionList {
 
   /// The completion items.
   std::vector<CompletionItem> items;
-
-  static json::Expr unparse(const CompletionList &);
 };
+json::Expr toJSON(const CompletionList &);
 
 /// A single parameter of a particular signature.
 struct ParameterInformation {
@@ -549,9 +514,8 @@ struct ParameterInformation {
 
   /// The documentation of this parameter. Optional.
   std::string documentation;
-
-  static json::Expr unparse(const ParameterInformation &);
 };
+json::Expr toJSON(const ParameterInformation &);
 
 /// Represents the signature of something callable.
 struct SignatureInformation {
@@ -564,9 +528,8 @@ struct SignatureInformation {
 
   /// The parameters of this signature.
   std::vector<ParameterInformation> parameters;
-
-  static json::Expr unparse(const SignatureInformation &);
 };
+json::Expr toJSON(const SignatureInformation &);
 
 /// Represents the signature of a callable.
 struct SignatureHelp {
@@ -579,9 +542,8 @@ struct SignatureHelp {
 
   /// The active parameter of the active signature.
   int activeParameter = 0;
-
-  static json::Expr unparse(const SignatureHelp &);
 };
+json::Expr toJSON(const SignatureHelp &);
 
 struct RenameParams {
   /// The document that was opened.
@@ -592,9 +554,8 @@ struct RenameParams {
 
   /// The new name of the symbol.
   std::string newName;
-
-  static llvm::Optional<RenameParams> parse(const json::Expr &Params);
 };
+bool fromJSON(const json::Expr &, RenameParams &);
 
 } // namespace clangd
 } // namespace clang

Modified: clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp?rev=319478&r1=319477&r2=319478&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp (original)
+++ clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp Thu Nov 30 13:32:29 2017
@@ -21,7 +21,7 @@ namespace {
 // Helper for attaching ProtocolCallbacks methods to a JSONRPCDispatcher.
 // Invoke like: Registerer("foo", &ProtocolCallbacks::onFoo)
 // onFoo should be: void onFoo(Ctx &C, FooParams &Params)
-// FooParams should have a static factory method: parse(const json::Expr&).
+// FooParams should have a fromJSON function.
 struct HandlerRegisterer {
   template <typename Param>
   void operator()(StringRef Method,
@@ -31,11 +31,9 @@ struct HandlerRegisterer {
     auto *Callbacks = this->Callbacks;
     Dispatcher.registerHandler(
         Method, [=](RequestContext C, const json::Expr &RawParams) {
-          if (auto P = [&] {
-                trace::Span Tracer("Parse");
-                return std::decay<Param>::type::parse(RawParams);
-              }()) {
-            (Callbacks->*Handler)(std::move(C), *P);
+          typename std::remove_reference<Param>::type P;
+          if (fromJSON(RawParams, P)) {
+            (Callbacks->*Handler)(std::move(C), P);
           } else {
             Out->log("Failed to decode " + Method + " request.\n");
           }

Modified: clang-tools-extra/trunk/test/clangd/trace.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/trace.test?rev=319478&r1=319477&r2=319478&view=diff
==============================================================================
--- clang-tools-extra/trunk/test/clangd/trace.test (original)
+++ clang-tools-extra/trunk/test/clangd/trace.test Thu Nov 30 13:32:29 2017
@@ -11,7 +11,7 @@ Content-Length: 152
 #      CHECK: {"displayTimeUnit":"ns","traceEvents":[
 # Start opening the doc.
 #      CHECK: "name": "textDocument/didOpen"
-#      CHECK: "ph": "E"
+#      CHECK: "ph": "B"
 # Start building the preamble.
 #      CHECK: "name": "Preamble"
 #      CHECK: },

Modified: clang-tools-extra/trunk/unittests/clangd/JSONExprTests.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/JSONExprTests.cpp?rev=319478&r1=319477&r2=319478&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/clangd/JSONExprTests.cpp (original)
+++ clang-tools-extra/trunk/unittests/clangd/JSONExprTests.cpp Thu Nov 30 13:32:29 2017
@@ -229,6 +229,64 @@ TEST(JSONTest, Inspection) {
   }
 }
 
+// Sample struct with typical JSON-mapping rules.
+struct CustomStruct {
+  CustomStruct() : B(false) {}
+  CustomStruct(std::string S, llvm::Optional<int> I, bool B)
+      : S(S), I(I), B(B) {}
+  std::string S;
+  llvm::Optional<int> I;
+  bool B;
+};
+inline bool operator==(const CustomStruct &L, const CustomStruct &R) {
+  return L.S == R.S && L.I == R.I && L.B == R.B;
+}
+inline std::ostream &operator<<(std::ostream &OS, const CustomStruct &S) {
+  return OS << "(" << S.S << ", " << (S.I ? std::to_string(*S.I) : "None")
+            << ", " << S.B << ")";
+}
+bool fromJSON(const json::Expr &E, CustomStruct &R) {
+  ObjectMapper O(E);
+  if (!O || !O.map("str", R.S) || !O.map("int", R.I))
+    return false;
+  O.map("bool", R.B);
+  return true;
+}
+
+TEST(JSONTest, Deserialize) {
+  std::map<std::string, std::vector<CustomStruct>> R;
+  CustomStruct ExpectedStruct = {"foo", 42, true};
+  std::map<std::string, std::vector<CustomStruct>> Expected;
+  Expr J = obj{{"foo", ary{
+                           obj{
+                               {"str", "foo"},
+                               {"int", 42},
+                               {"bool", true},
+                               {"unknown", "ignored"},
+                           },
+                           obj{{"str", "bar"}},
+                           obj{
+                               {"str", "baz"},
+                               {"bool", "string"}, // OK, deserialize ignores.
+                           },
+                       }}};
+  Expected["foo"] = {
+      CustomStruct("foo", 42, true),
+      CustomStruct("bar", llvm::None, false),
+      CustomStruct("baz", llvm::None, false),
+  };
+  ASSERT_TRUE(fromJSON(J, R));
+  EXPECT_EQ(R, Expected);
+
+  CustomStruct V;
+  EXPECT_FALSE(fromJSON(nullptr, V)) << "Not an object " << V;
+  EXPECT_FALSE(fromJSON(obj{}, V)) << "Missing required field " << V;
+  EXPECT_FALSE(fromJSON(obj{{"str", 1}}, V)) << "Wrong type " << V;
+  // Optional<T> must parse as the correct type if present.
+  EXPECT_FALSE(fromJSON(obj{{"str", 1}, {"int", "string"}}, V))
+      << "Wrong type for Optional<T> " << V;
+}
+
 } // namespace
 } // namespace json
 } // namespace clangd




More information about the cfe-commits mailing list