Index: clangd/JSONExpr.h =================================================================== --- clangd/JSONExpr.h +++ clangd/JSONExpr.h @@ -55,14 +55,14 @@ // object (json::obj) // // The kind can be queried directly, or implicitly via the typed accessors: -// if (Optional S = E.string()) +// if (Optional S = E.asString() // assert(E.kind() == Expr::String); // // Array and Object also have typed indexing accessors for easy traversal: // Expected E = parse(R"( {"options": {"font": "sans-serif"}} )"); -// if (json::obj* O = E->object()) -// if (json::obj* Opts = O->object("options")) -// if (Optional Font = Opts->string("font")) +// if (json::obj* O = E->asObject()) +// if (json::obj* Opts = O->getObject("options")) +// if (Optional Font = Opts->getString("font")) // assert(Opts->at("font").kind() == Expr::String); // // === Serialization === @@ -166,38 +166,38 @@ } // Typed accessors return None/nullptr if the Expr is not of this type. - llvm::Optional null() const { + llvm::Optional asNull() const { if (LLVM_LIKELY(Type == T_Null)) return nullptr; return llvm::None; } - llvm::Optional boolean() const { - if (LLVM_LIKELY(Type == T_Null)) + llvm::Optional asBoolean() const { + if (LLVM_LIKELY(Type == T_Boolean)) return as(); return llvm::None; } - llvm::Optional number() const { + llvm::Optional asNumber() const { if (LLVM_LIKELY(Type == T_Number)) return as(); return llvm::None; } - llvm::Optional string() const { + llvm::Optional asString() const { if (Type == T_String) return llvm::StringRef(as()); if (LLVM_LIKELY(Type == T_StringRef)) return as(); return llvm::None; } - const ObjectExpr *object() const { + const ObjectExpr *asObject() const { return LLVM_LIKELY(Type == T_Object) ? &as() : nullptr; } - ObjectExpr *object() { + ObjectExpr *asObject() { return LLVM_LIKELY(Type == T_Object) ? &as() : nullptr; } - const ArrayExpr *array() const { + const ArrayExpr *asArray() const { return LLVM_LIKELY(Type == T_Array) ? &as() : nullptr; } - ArrayExpr *array() { + ArrayExpr *asArray() { return LLVM_LIKELY(Type == T_Array) ? &as() : nullptr; } @@ -292,6 +292,63 @@ Expr &operator[](ObjectKey &&K) { return emplace(std::move(K), Expr(nullptr)).first->second; } + + // Look up a property, returning nullptr if it doesn't exist. + json::Expr *get(const ObjectKey &K) { + auto I = find(K); + if (I == end()) + return nullptr; + return &I->second; + } + const json::Expr *get(const ObjectKey &K) const { + auto I = find(K); + if (I == end()) + return nullptr; + return &I->second; + } + // Typed accessors return None/nullptr if + // - the property doesn't exist + // - or it has the wrong type + llvm::Optional getNull(const ObjectKey &K) const { + if (auto *V = get(K)) + return V->asNull(); + return llvm::None; + } + llvm::Optional getBoolean(const ObjectKey &K) const { + if (auto *V = get(K)) + return V->asBoolean(); + return llvm::None; + } + llvm::Optional getNumber(const ObjectKey &K) const { + if (auto *V = get(K)) + return V->asNumber(); + return llvm::None; + } + llvm::Optional getString(const ObjectKey &K) const { + if (auto *V = get(K)) + return V->asString(); + return llvm::None; + } + const ObjectExpr *getObject(const ObjectKey &K) const { + if (auto *V = get(K)) + return V->asObject(); + return nullptr; + } + ObjectExpr *getObject(const ObjectKey &K) { + if (auto *V = get(K)) + return V->asObject(); + return nullptr; + } + const ArrayExpr *getArray(const ObjectKey &K) const { + if (auto *V = get(K)) + return V->asArray(); + return nullptr; + } + ArrayExpr *getArray(const ObjectKey &K) { + if (auto *V = get(K)) + return V->asArray(); + return nullptr; + } }; class ArrayExpr : public std::vector { @@ -306,6 +363,26 @@ for (const auto &V : C) emplace_back(V); } + + // Typed accessors return None/nullptr if the element has the wrong type. + llvm::Optional getNull(size_t I) const { + return (*this)[I].asNull(); + } + llvm::Optional getBoolean(size_t I) const { + return (*this)[I].asBoolean(); + } + llvm::Optional getNumber(size_t I) const { + return (*this)[I].asNumber(); + } + llvm::Optional getString(size_t I) const { + return (*this)[I].asString(); + } + const ObjectExpr *getObject(size_t I) const { + return (*this)[I].asObject(); + } + ObjectExpr *getObject(size_t I) { return (*this)[I].asObject(); } + const ArrayExpr *getArray(size_t I) const { return (*this)[I].asArray(); } + ArrayExpr *getArray(size_t I) { return (*this)[I].asArray(); } }; private: Index: clangd/JSONExpr.cpp =================================================================== --- clangd/JSONExpr.cpp +++ clangd/JSONExpr.cpp @@ -161,7 +161,7 @@ } case '[': { Out = json::ary{}; - json::ary &A = *Out.array(); + json::ary &A = *Out.asArray(); eatWhitespace(); if (peek() == ']') { ++P; @@ -185,7 +185,7 @@ } case '{': { Out = json::obj{}; - json::obj &O = *Out.object(); + json::obj &O = *Out.asObject(); eatWhitespace(); if (peek() == '}') { ++P; @@ -507,17 +507,17 @@ return false; switch (L.kind()) { case Expr::Null: - return L.null() == R.null(); + return *L.asNull() == *R.asNull(); case Expr::Boolean: - return L.boolean() == R.boolean(); + return *L.asBoolean() == *R.asBoolean(); case Expr::Number: - return L.boolean() == R.boolean(); + return *L.asNumber() == *R.asNumber(); case Expr::String: - return L.string() == R.string(); + return *L.asString() == *R.asString(); case Expr::Array: - return *L.array() == *R.array(); + return *L.asArray() == *R.asArray(); case Expr::Object: - return *L.object() == *R.object(); + return *L.asObject() == *R.asObject(); } llvm_unreachable("Unknown expression kind"); } Index: unittests/clangd/JSONExprTests.cpp =================================================================== --- unittests/clangd/JSONExprTests.cpp +++ unittests/clangd/JSONExprTests.cpp @@ -167,7 +167,6 @@ ExpectErr("Unexpected EOF", ""); ExpectErr("Unexpected EOF", "["); ExpectErr("Text after end of document", "[][]"); - ExpectErr("Text after end of document", "[][]"); ExpectErr("Invalid bareword", "fuzzy"); ExpectErr("Expected , or ]", "[2?]"); ExpectErr("Expected object key", "{a:2}"); @@ -185,6 +184,49 @@ })"); } +TEST(JSONTest, Inspection) { + llvm::Expected Doc = parse(R"( + { + "null": null, + "boolean": false, + "number": 2.78, + "string": "json", + "array": [null, true, 3.14, "hello", [1,2,3], {"time": "arrow"}], + "object": {"fruit": "banana"} + } + )"); + EXPECT_TRUE(!!Doc); + + obj *O = Doc->asObject(); + ASSERT_TRUE(O); + + EXPECT_FALSE(O->getNull("missing")); + EXPECT_FALSE(O->getNull("boolean")); + EXPECT_TRUE(O->getNull("null")); + + EXPECT_EQ(O->getNumber("number"), llvm::Optional(2.78)); + EXPECT_EQ(O->getString("string"), llvm::Optional("json")); + ASSERT_FALSE(O->getObject("missing")); + ASSERT_FALSE(O->getObject("array")); + ASSERT_TRUE(O->getObject("object")); + EXPECT_EQ(*O->getObject("object"), (obj{{"fruit", "banana"}})); + + ary *A = O->getArray("array"); + ASSERT_TRUE(A); + EXPECT_EQ(A->getBoolean(1), llvm::Optional(true)); + ASSERT_TRUE(A->getArray(4)); + EXPECT_EQ(*A->getArray(4), (ary{1, 2, 3})); + int I = 0; + for (Expr &E : *A) { + if (I++ == 5) { + ASSERT_TRUE(E.asObject()); + EXPECT_EQ(E.asObject()->getString("time"), + llvm::Optional("arrow")); + } else + EXPECT_FALSE(E.asObject()); + } +} + } // namespace } // namespace json } // namespace clangd