diff --git a/README.md b/README.md index 9d3bd37..e043abe 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,12 @@ render("{{ float(\"1.8\") > 2 }}", data); // false // Set default values if variables are not defined render("Hello {{ default(neighbour, \"my friend\") }}!", data); // "Hello Peter!" render("Hello {{ default(colleague, \"my friend\") }}!", data); // "Hello my friend!" + +// Check if a key exists in an object +render("{{ exists(\"guests\") }}", data); // "true" +render("{{ exists(\"city\") }}", data); // "false" +render("{{ existsIn(time, \"start\") }}", data); // "true" +render("{{ existsIn(time, neighbour) }}", data); // "false" ``` ### Callbacks diff --git a/src/inja.hpp b/src/inja.hpp index 1647eae..b1d4c65 100644 --- a/src/inja.hpp +++ b/src/inja.hpp @@ -279,6 +279,8 @@ struct Parsed { Sort, Upper, ReadJson, + Exists, + ExistsInObject, Default }; @@ -513,6 +515,15 @@ public: Parsed::CallbackSignature signature = std::make_pair(element.command, element.args.size()); return map_callbacks.at(signature)(element.args, data); } + case Parsed::Function::Exists: { + const std::string name = eval_expression(element.args[0], data); + return data.find(name) != data.end(); + } + case Parsed::Function::ExistsInObject: { + const std::string name = eval_expression(element.args[1], data); + const json d = eval_expression(element.args[0], data); + return d.find(name) != d.end(); + } } inja_throw("render_error", "unknown function in renderer: " + element.command); @@ -688,6 +699,8 @@ public: {Parsed::Function::Round, function_regex("round", 2)}, {Parsed::Function::Sort, function_regex("sort", 1)}, {Parsed::Function::Upper, function_regex("upper", 1)}, + {Parsed::Function::Exists, function_regex("exists", 1)}, + {Parsed::Function::ExistsInObject, function_regex("existsIn", 2)}, {Parsed::Function::ReadJson, Regex{"\\s*([^\\(\\)]*\\S)\\s*"}} }; diff --git a/test/src/unit-renderer.cpp b/test/src/unit-renderer.cpp index 41098c4..4acad8c 100644 --- a/test/src/unit-renderer.cpp +++ b/test/src/unit-renderer.cpp @@ -104,6 +104,9 @@ TEST_CASE("functions") { data["city"] = "New York"; data["names"] = {"Jeff", "Seb", "Peter", "Tom"}; data["temperature"] = 25.6789; + data["brother"]["name"] = "Chris"; + data["brother"]["daughters"] = {"Maria", "Helen"}; + data["property"] = "name"; SECTION("upper") { CHECK( env.render("{{ upper(name) }}", data) == "PETER" ); @@ -203,6 +206,22 @@ TEST_CASE("functions") { CHECK( env.render("{{ default(surname, \"nobody\") }}", data) == "nobody" ); CHECK_THROWS_WITH( env.render("{{ default(surname, lastname) }}", data), "[inja.exception.render_error] variable '/lastname' not found" ); } + + SECTION("exists") { + CHECK( env.render("{{ exists(\"name\") }}", data) == "true" ); + CHECK( env.render("{{ exists(\"zipcode\") }}", data) == "false" ); + CHECK( env.render("{{ exists(name) }}", data) == "false" ); + CHECK( env.render("{{ exists(property) }}", data) == "true" ); + } + + SECTION("existsIn") { + CHECK( env.render("{{ existsIn(brother, \"name\") }}", data) == "true" ); + CHECK( env.render("{{ existsIn(brother, \"parents\") }}", data) == "false" ); + CHECK( env.render("{{ existsIn(brother, property) }}", data) == "true" ); + CHECK( env.render("{{ existsIn(brother, name) }}", data) == "false" ); + CHECK_THROWS_WITH( env.render("{{ existsIn(sister, \"lastname\") }}", data), "[inja.exception.render_error] variable '/sister' not found" ); + CHECK_THROWS_WITH( env.render("{{ existsIn(brother, sister) }}", data), "[inja.exception.render_error] variable '/sister' not found" ); + } } TEST_CASE("callbacks") { diff --git a/test/src/unit-string-helper.cpp b/test/src/unit-string-helper.cpp index f6ee964..87f4618 100644 --- a/test/src/unit-string-helper.cpp +++ b/test/src/unit-string-helper.cpp @@ -155,6 +155,8 @@ TEST_CASE("match-functions") { CHECK( inja::match(" upper(lower()) ", map_regex).type() == inja::Parsed::Function::Upper ); CHECK( inja::match("lower(upper(test))", map_regex).type() == inja::Parsed::Function::Lower ); CHECK( inja::match("round(2, 3)", map_regex).type() == inja::Parsed::Function::Round ); + CHECK( inja::match("exists(\"var\")", map_regex).type() == inja::Parsed::Function::Exists ); + CHECK( inja::match("existsIn(var, \"othervar\")", map_regex).type() == inja::Parsed::Function::ExistsInObject ); } TEST_CASE("create-regex-functions") {