buffet: Add support for "required" and "isRequired" parameters

Added support for "required" attribute for object types and
"isRequired" for command parameters and made all properties/parameters
optional by default.

BUG=brillo:354
TEST=`FEATURES=test emerge-link buffet`

Change-Id: Ie3c7607e4ac0319f8ed459875a823fed39890da9
Reviewed-on: https://chromium-review.googlesource.com/283646
Trybot-Ready: Alex Vakulenko <avakulenko@chromium.org>
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Reviewed-by: Vitaly Buka <vitalybuka@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/etc/buffet/commands/test.json b/buffet/etc/buffet/commands/test.json
index aac439d..911d8f0 100644
--- a/buffet/etc/buffet/commands/test.json
+++ b/buffet/etc/buffet/commands/test.json
@@ -2,13 +2,14 @@
   "base": {
     "_jump": {
       "parameters": {
-        "_height":"integer"
+        "_height": "integer"
       },
       "progress": {
         "progress": {
           "type": "integer",
           "minimum": 0,
-          "maximum": 100
+          "maximum": 100,
+          "isRequired": true
         }
       }
     }
diff --git a/buffet/etc/buffet/gcd.json b/buffet/etc/buffet/gcd.json
index ac85f6b..368704e 100644
--- a/buffet/etc/buffet/gcd.json
+++ b/buffet/etc/buffet/gcd.json
@@ -26,7 +26,8 @@
       "minimalRole": "manager",
       "parameters": {
         "description": "string",
-        "name": "string"
+        "name": "string",
+        "location": "string"
       },
       "results": {}
     }
diff --git a/libweave/src/base_api_handler_unittest.cc b/libweave/src/base_api_handler_unittest.cc
index a79abe0..c7456cf 100644
--- a/libweave/src/base_api_handler_unittest.cc
+++ b/libweave/src/base_api_handler_unittest.cc
@@ -179,6 +179,17 @@
   EXPECT_EQ("testName", config.name());
   EXPECT_EQ("testDescription", config.description());
   EXPECT_EQ("testLocation", config.location());
+
+  AddCommand(R"({
+    'name' : 'base.updateDeviceInfo',
+    'parameters': {
+      'location': 'newLocation'
+    }
+  })");
+
+  EXPECT_EQ("testName", config.name());
+  EXPECT_EQ("testDescription", config.description());
+  EXPECT_EQ("newLocation", config.location());
 }
 
 }  // namespace buffet
diff --git a/libweave/src/commands/command_dictionary.cc b/libweave/src/commands/command_dictionary.cc
index 92dd316..69932d7 100644
--- a/libweave/src/commands/command_dictionary.cc
+++ b/libweave/src/commands/command_dictionary.cc
@@ -217,7 +217,7 @@
       continue;
 
     std::unique_ptr<base::DictionaryValue> parameters =
-        pair.second->GetParameters()->ToJson(full_schema, error);
+        pair.second->GetParameters()->ToJson(full_schema, true, error);
     if (!parameters)
       return {};
     // Progress and results are not part of public commandDefs.
diff --git a/libweave/src/commands/command_dictionary_unittest.cc b/libweave/src/commands/command_dictionary_unittest.cc
index e087943..278364f 100644
--- a/libweave/src/commands/command_dictionary_unittest.cc
+++ b/libweave/src/commands/command_dictionary_unittest.cc
@@ -92,11 +92,11 @@
   EXPECT_EQ(UserRole::kViewer, cmd->GetMinimalRole());
 
   EXPECT_JSON_EQ("{'height': {'type': 'integer'}}",
-                 *cmd->GetParameters()->ToJson(true, nullptr));
+                 *cmd->GetParameters()->ToJson(true, true, nullptr));
   EXPECT_JSON_EQ("{'progress': {'type': 'integer'}}",
-                 *cmd->GetProgress()->ToJson(true, nullptr));
+                 *cmd->GetProgress()->ToJson(true, false, nullptr));
   EXPECT_JSON_EQ("{'success': {'type': 'boolean'}}",
-                 *cmd->GetResults()->ToJson(true, nullptr));
+                 *cmd->GetResults()->ToJson(true, false, nullptr));
 }
 
 TEST(CommandDictionary, LoadCommands_Failures) {
diff --git a/libweave/src/commands/object_schema.cc b/libweave/src/commands/object_schema.cc
index 2fec07b..27ace1c 100644
--- a/libweave/src/commands/object_schema.cc
+++ b/libweave/src/commands/object_schema.cc
@@ -287,12 +287,25 @@
   return p != properties_.end() ? p->second.get() : nullptr;
 }
 
+bool ObjectSchema::MarkPropRequired(const std::string& name,
+                                    chromeos::ErrorPtr* error) {
+  auto p = properties_.find(name);
+  if (p == properties_.end()) {
+    chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                                  errors::commands::kUnknownProperty,
+                                  "Unknown property '%s'", name.c_str());
+    return false;
+  }
+  p->second->MakeRequired(true);
+  return true;
+}
 std::unique_ptr<base::DictionaryValue> ObjectSchema::ToJson(
     bool full_schema,
+    bool in_command_def,
     chromeos::ErrorPtr* error) const {
   std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue);
   for (const auto& pair : properties_) {
-    auto PropDef = pair.second->ToJson(full_schema, error);
+    auto PropDef = pair.second->ToJson(full_schema, in_command_def, error);
     if (!PropDef)
       return {};
     value->SetWithoutPathExpansion(pair.first, PropDef.release());
diff --git a/libweave/src/commands/object_schema.h b/libweave/src/commands/object_schema.h
index d6eafed..577a5d4 100644
--- a/libweave/src/commands/object_schema.h
+++ b/libweave/src/commands/object_schema.h
@@ -49,6 +49,11 @@
   // Gets the list of all the properties defined.
   const Properties& GetProps() const { return properties_; }
 
+  // Marks the property with given name as "required". If |name| specifies
+  // an unknown property, false is returned and |error| is set with detailed
+  // error message for the failure.
+  bool MarkPropRequired(const std::string& name, chromeos::ErrorPtr* error);
+
   // Specify whether extra properties are allowed on objects described by
   // this schema. When validating a value of an object type, we can
   // make sure that the value has only the properties explicitly defined by
@@ -64,6 +69,7 @@
   // the overridden (not inherited) ones are saved.
   std::unique_ptr<base::DictionaryValue> ToJson(
       bool full_schema,
+      bool in_command_def,
       chromeos::ErrorPtr* error) const;
 
   // Loads the object schema from JSON. If |object_schema| is not nullptr, it is
diff --git a/libweave/src/commands/object_schema_unittest.cc b/libweave/src/commands/object_schema_unittest.cc
index 12bf8a5..b05e315 100644
--- a/libweave/src/commands/object_schema_unittest.cc
+++ b/libweave/src/commands/object_schema_unittest.cc
@@ -68,25 +68,26 @@
 
 TEST(CommandSchema, IntPropType_ToJson) {
   IntPropType prop;
-  EXPECT_JSON_EQ("'integer'", *prop.ToJson(false, nullptr));
-  EXPECT_JSON_EQ("{'type':'integer'}", *prop.ToJson(true, nullptr));
+  EXPECT_JSON_EQ("'integer'", *prop.ToJson(false, false, nullptr));
+  EXPECT_JSON_EQ("{'type':'integer'}", *prop.ToJson(true, false, nullptr));
   IntPropType param2;
   param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
-  EXPECT_JSON_EQ("{}", *param2.ToJson(false, nullptr));
+  EXPECT_JSON_EQ("{}", *param2.ToJson(false, false, nullptr));
   param2.FromJson(CreateDictionaryValue("{'minimum':3}").get(), &prop, nullptr);
-  EXPECT_JSON_EQ("{'minimum':3}", *param2.ToJson(false, nullptr));
+  EXPECT_JSON_EQ("{'minimum':3}", *param2.ToJson(false, false, nullptr));
   param2.FromJson(CreateDictionaryValue("{'maximum':-7}").get(), &prop,
                   nullptr);
-  EXPECT_JSON_EQ("{'maximum':-7}", *param2.ToJson(false, nullptr));
+  EXPECT_JSON_EQ("{'maximum':-7}", *param2.ToJson(false, false, nullptr));
   param2.FromJson(CreateDictionaryValue("{'minimum':0,'maximum':5}").get(),
                   &prop, nullptr);
-  EXPECT_JSON_EQ("{'maximum':5,'minimum':0}", *param2.ToJson(false, nullptr));
+  EXPECT_JSON_EQ("{'maximum':5,'minimum':0}",
+                 *param2.ToJson(false, false, nullptr));
   param2.FromJson(CreateDictionaryValue("{'enum':[1,2,3]}").get(), &prop,
                   nullptr);
-  EXPECT_JSON_EQ("[1,2,3]", *param2.ToJson(false, nullptr));
+  EXPECT_JSON_EQ("[1,2,3]", *param2.ToJson(false, false, nullptr));
   param2.FromJson(CreateDictionaryValue("{'default':123}").get(), &prop,
                   nullptr);
-  EXPECT_JSON_EQ("{'default':123}", *param2.ToJson(false, nullptr));
+  EXPECT_JSON_EQ("{'default':123}", *param2.ToJson(false, false, nullptr));
 }
 
 TEST(CommandSchema, IntPropType_FromJson) {
@@ -172,6 +173,7 @@
   EXPECT_FALSE(prop.HasOverriddenAttributes());
   EXPECT_FALSE(prop.IsBasedOnSchema());
   EXPECT_EQ(nullptr, prop.GetDefaultValue());
+  EXPECT_FALSE(prop.IsRequired());
 }
 
 TEST(CommandSchema, BoolPropType_Types) {
@@ -186,19 +188,19 @@
 
 TEST(CommandSchema, BoolPropType_ToJson) {
   BooleanPropType prop;
-  EXPECT_JSON_EQ("'boolean'", *prop.ToJson(false, nullptr));
-  EXPECT_JSON_EQ("{'type':'boolean'}", *prop.ToJson(true, nullptr));
+  EXPECT_JSON_EQ("'boolean'", *prop.ToJson(false, false, nullptr));
+  EXPECT_JSON_EQ("{'type':'boolean'}", *prop.ToJson(true, false, nullptr));
   BooleanPropType param2;
   param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
-  EXPECT_JSON_EQ("{}", *param2.ToJson(false, nullptr));
+  EXPECT_JSON_EQ("{}", *param2.ToJson(false, false, nullptr));
   param2.FromJson(CreateDictionaryValue("{'enum':[true,false]}").get(), &prop,
                   nullptr);
-  EXPECT_JSON_EQ("[true,false]", *param2.ToJson(false, nullptr));
+  EXPECT_JSON_EQ("[true,false]", *param2.ToJson(false, false, nullptr));
   EXPECT_JSON_EQ("{'enum':[true,false],'type':'boolean'}",
-                 *param2.ToJson(true, nullptr));
+                 *param2.ToJson(true, false, nullptr));
   param2.FromJson(CreateDictionaryValue("{'default':true}").get(), &prop,
                   nullptr);
-  EXPECT_JSON_EQ("{'default':true}", *param2.ToJson(false, nullptr));
+  EXPECT_JSON_EQ("{'default':true}", *param2.ToJson(false, false, nullptr));
 }
 
 TEST(CommandSchema, BoolPropType_FromJson) {
@@ -261,6 +263,7 @@
   EXPECT_FALSE(prop.HasOverriddenAttributes());
   EXPECT_FALSE(prop.IsBasedOnSchema());
   EXPECT_EQ(nullptr, prop.GetDefaultValue());
+  EXPECT_FALSE(prop.IsRequired());
 }
 
 TEST(CommandSchema, DoublePropType_Types) {
@@ -275,23 +278,23 @@
 
 TEST(CommandSchema, DoublePropType_ToJson) {
   DoublePropType prop;
-  EXPECT_JSON_EQ("'number'", *prop.ToJson(false, nullptr));
-  EXPECT_JSON_EQ("{'type':'number'}", *prop.ToJson(true, nullptr));
+  EXPECT_JSON_EQ("'number'", *prop.ToJson(false, false, nullptr));
+  EXPECT_JSON_EQ("{'type':'number'}", *prop.ToJson(true, false, nullptr));
   DoublePropType param2;
   param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
-  EXPECT_JSON_EQ("{}", *param2.ToJson(false, nullptr));
+  EXPECT_JSON_EQ("{}", *param2.ToJson(false, false, nullptr));
   param2.FromJson(CreateDictionaryValue("{'minimum':3}").get(), &prop, nullptr);
-  EXPECT_JSON_EQ("{'minimum':3.0}", *param2.ToJson(false, nullptr));
+  EXPECT_JSON_EQ("{'minimum':3.0}", *param2.ToJson(false, false, nullptr));
   param2.FromJson(CreateDictionaryValue("{'maximum':-7}").get(), &prop,
                   nullptr);
-  EXPECT_JSON_EQ("{'maximum':-7.0}", *param2.ToJson(false, nullptr));
+  EXPECT_JSON_EQ("{'maximum':-7.0}", *param2.ToJson(false, false, nullptr));
   param2.FromJson(CreateDictionaryValue("{'minimum':0,'maximum':5}").get(),
                   &prop, nullptr);
   EXPECT_JSON_EQ("{'maximum':5.0,'minimum':0.0}",
-                 *param2.ToJson(false, nullptr));
+                 *param2.ToJson(false, false, nullptr));
   param2.FromJson(CreateDictionaryValue("{'default':12.3}").get(), &prop,
                   nullptr);
-  EXPECT_JSON_EQ("{'default':12.3}", *param2.ToJson(false, nullptr));
+  EXPECT_JSON_EQ("{'default':12.3}", *param2.ToJson(false, false, nullptr));
 }
 
 TEST(CommandSchema, DoublePropType_FromJson) {
@@ -378,6 +381,7 @@
   EXPECT_FALSE(prop.HasOverriddenAttributes());
   EXPECT_FALSE(prop.IsBasedOnSchema());
   EXPECT_EQ(nullptr, prop.GetDefaultValue());
+  EXPECT_FALSE(prop.IsRequired());
 }
 
 TEST(CommandSchema, StringPropType_Types) {
@@ -392,24 +396,24 @@
 
 TEST(CommandSchema, StringPropType_ToJson) {
   StringPropType prop;
-  EXPECT_JSON_EQ("'string'", *prop.ToJson(false, nullptr));
-  EXPECT_JSON_EQ("{'type':'string'}", *prop.ToJson(true, nullptr));
+  EXPECT_JSON_EQ("'string'", *prop.ToJson(false, false, nullptr));
+  EXPECT_JSON_EQ("{'type':'string'}", *prop.ToJson(true, false, nullptr));
   StringPropType param2;
   param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
-  EXPECT_JSON_EQ("{}", *param2.ToJson(false, nullptr));
+  EXPECT_JSON_EQ("{}", *param2.ToJson(false, false, nullptr));
   param2.FromJson(CreateDictionaryValue("{'minLength':3}").get(), &prop,
                   nullptr);
-  EXPECT_JSON_EQ("{'minLength':3}", *param2.ToJson(false, nullptr));
+  EXPECT_JSON_EQ("{'minLength':3}", *param2.ToJson(false, false, nullptr));
   param2.FromJson(CreateDictionaryValue("{'maxLength':7}").get(), &prop,
                   nullptr);
-  EXPECT_JSON_EQ("{'maxLength':7}", *param2.ToJson(false, nullptr));
+  EXPECT_JSON_EQ("{'maxLength':7}", *param2.ToJson(false, false, nullptr));
   param2.FromJson(CreateDictionaryValue("{'minLength':0,'maxLength':5}").get(),
                   &prop, nullptr);
   EXPECT_JSON_EQ("{'maxLength':5,'minLength':0}",
-                 *param2.ToJson(false, nullptr));
+                 *param2.ToJson(false, false, nullptr));
   param2.FromJson(CreateDictionaryValue("{'default':'abcd'}").get(), &prop,
                   nullptr);
-  EXPECT_JSON_EQ("{'default':'abcd'}", *param2.ToJson(false, nullptr));
+  EXPECT_JSON_EQ("{'default':'abcd'}", *param2.ToJson(false, false, nullptr));
 }
 
 TEST(CommandSchema, StringPropType_FromJson) {
@@ -501,6 +505,7 @@
   EXPECT_TRUE(prop.HasOverriddenAttributes());
   EXPECT_FALSE(prop.IsBasedOnSchema());
   EXPECT_EQ(nullptr, prop.GetDefaultValue());
+  EXPECT_FALSE(prop.IsRequired());
 }
 
 TEST(CommandSchema, ObjectPropType_Types) {
@@ -516,14 +521,14 @@
 TEST(CommandSchema, ObjectPropType_ToJson) {
   ObjectPropType prop;
   EXPECT_JSON_EQ("{'additionalProperties':false,'properties':{}}",
-                 *prop.ToJson(false, nullptr));
+                 *prop.ToJson(false, false, nullptr));
   EXPECT_JSON_EQ(
       "{'additionalProperties':false,'properties':{},'type':'object'}",
-      *prop.ToJson(true, nullptr));
+      *prop.ToJson(true, false, nullptr));
   EXPECT_FALSE(prop.IsBasedOnSchema());
   ObjectPropType prop2;
   prop2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
-  EXPECT_JSON_EQ("{}", *prop2.ToJson(false, nullptr));
+  EXPECT_JSON_EQ("{}", *prop2.ToJson(false, false, nullptr));
   EXPECT_TRUE(prop2.IsBasedOnSchema());
 
   auto schema = ObjectSchema::Create();
@@ -542,7 +547,7 @@
       }
     }
   })";
-  EXPECT_JSON_EQ(expected, *prop2.ToJson(false, nullptr));
+  EXPECT_JSON_EQ(expected, *prop2.ToJson(false, false, nullptr));
 
   expected = R"({
     'additionalProperties': false,
@@ -558,7 +563,7 @@
     },
     'type': 'object'
   })";
-  EXPECT_JSON_EQ(expected, *prop2.ToJson(true, nullptr));
+  EXPECT_JSON_EQ(expected, *prop2.ToJson(true, false, nullptr));
 
   ObjectPropType prop3;
   ASSERT_TRUE(
@@ -572,7 +577,7 @@
       'password': 'abracadabra'
     }
   })";
-  EXPECT_JSON_EQ(expected, *prop3.ToJson(false, nullptr));
+  EXPECT_JSON_EQ(expected, *prop3.ToJson(false, false, nullptr));
 
   expected = R"({
     'additionalProperties': false,
@@ -592,7 +597,7 @@
     },
     'type': 'object'
   })";
-  EXPECT_JSON_EQ(expected, *prop3.ToJson(true, nullptr));
+  EXPECT_JSON_EQ(expected, *prop3.ToJson(true, false, nullptr));
 
   ObjectPropType prop4;
   ASSERT_TRUE(
@@ -615,7 +620,7 @@
       }
     }
   })";
-  EXPECT_JSON_EQ(expected, *prop4.ToJson(false, nullptr));
+  EXPECT_JSON_EQ(expected, *prop4.ToJson(false, false, nullptr));
 
   expected = R"({
     'additionalProperties': true,
@@ -635,7 +640,7 @@
     },
     'type': 'object'
   })";
-  EXPECT_JSON_EQ(expected, *prop4.ToJson(true, nullptr));
+  EXPECT_JSON_EQ(expected, *prop4.ToJson(true, false, nullptr));
 }
 
 TEST(CommandSchema, ObjectPropType_FromJson) {
@@ -669,8 +674,8 @@
   ObjectPropType prop;
   prop.FromJson(CreateDictionaryValue(
                     "{'properties':{'expires':'integer',"
-                    "'password':{'maxLength':100,'minLength':6}}}")
-                    .get(),
+                    "'password':{'maxLength':100,'minLength':6}},"
+                    "'required':['expires','password']}").get(),
                 nullptr, nullptr);
   chromeos::ErrorPtr error;
   EXPECT_TRUE(prop.ValidateValue(
@@ -763,6 +768,7 @@
   EXPECT_TRUE(prop.HasOverriddenAttributes());
   EXPECT_FALSE(prop.IsBasedOnSchema());
   EXPECT_NE(nullptr, prop.GetItemTypePtr());
+  EXPECT_FALSE(prop.IsRequired());
 }
 
 TEST(CommandSchema, ArrayPropType_Types) {
@@ -778,20 +784,20 @@
 TEST(CommandSchema, ArrayPropType_ToJson) {
   ArrayPropType prop;
   prop.SetItemType(PropType::Create(ValueType::Int));
-  EXPECT_JSON_EQ("{'items':'integer'}", *prop.ToJson(false, nullptr));
+  EXPECT_JSON_EQ("{'items':'integer'}", *prop.ToJson(false, false, nullptr));
   EXPECT_JSON_EQ("{'items':{'type':'integer'},'type':'array'}",
-                 *prop.ToJson(true, nullptr));
+                 *prop.ToJson(true, false, nullptr));
   EXPECT_FALSE(prop.IsBasedOnSchema());
   ArrayPropType prop2;
   prop2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
-  EXPECT_JSON_EQ("{}", *prop2.ToJson(false, nullptr));
+  EXPECT_JSON_EQ("{}", *prop2.ToJson(false, false, nullptr));
   EXPECT_TRUE(prop2.IsBasedOnSchema());
   prop2.FromJson(CreateDictionaryValue("{'default':[1,2,3]}").get(), &prop,
                  nullptr);
-  EXPECT_JSON_EQ("{'default':[1,2,3]}", *prop2.ToJson(false, nullptr));
+  EXPECT_JSON_EQ("{'default':[1,2,3]}", *prop2.ToJson(false, false, nullptr));
   EXPECT_JSON_EQ(
       "{'default':[1,2,3],'items':{'type':'integer'},'type':'array'}",
-      *prop2.ToJson(true, nullptr));
+      *prop2.ToJson(true, false, nullptr));
 }
 
 TEST(CommandSchema, ArrayPropType_FromJson) {
@@ -1474,4 +1480,238 @@
   error.reset();
 }
 
+TEST(CommandSchema, RequiredProperties_Integral) {
+  IntPropType prop;
+
+  prop.MakeRequired(false);
+  EXPECT_JSON_EQ("{'type':'integer'}", *prop.ToJson(true, false, nullptr));
+  EXPECT_JSON_EQ("{'isRequired':false,'type':'integer'}",
+                 *prop.ToJson(true, true, nullptr));
+
+  prop.MakeRequired(true);
+  EXPECT_JSON_EQ("{'type':'integer'}", *prop.ToJson(true, false, nullptr));
+  EXPECT_JSON_EQ("{'isRequired':true,'type':'integer'}",
+                 *prop.ToJson(true, true, nullptr));
+
+  IntPropType prop2;
+  EXPECT_TRUE(prop2.FromJson(CreateDictionaryValue("{}").get(), &prop,
+                             nullptr));
+  EXPECT_TRUE(prop2.IsRequired());
+
+  EXPECT_TRUE(prop2.FromJson(
+      CreateDictionaryValue("{'isRequired': false}").get(), &prop, nullptr));
+  EXPECT_FALSE(prop2.IsRequired());
+
+  EXPECT_JSON_EQ("{'type':'integer'}", *prop2.ToJson(true, false, nullptr));
+  EXPECT_JSON_EQ("{'isRequired':false,'type':'integer'}",
+                 *prop2.ToJson(true, true, nullptr));
+}
+
+TEST(CommandSchema, RequiredProperties_Object) {
+  ObjectPropType obj_type;
+  auto schema = ObjectSchema::Create();
+  auto type = PropType::Create(ValueType::Int);
+  type->MakeRequired(true);
+  schema->AddProp("prop1", std::move(type));
+  type = PropType::Create(ValueType::String);
+  type->MakeRequired(false);
+  schema->AddProp("prop2", std::move(type));
+  type = PropType::Create(ValueType::Boolean);
+  type->MakeRequired(true);
+  schema->AddProp("prop3", std::move(type));
+  type = PropType::Create(ValueType::Array);
+  type->GetArray()->SetItemType(PropType::Create(ValueType::String));
+  schema->AddProp("prop4", std::move(type));
+  auto expected1 = R"({
+    'prop1': 'integer',
+    'prop2': 'string',
+    'prop3': 'boolean',
+    'prop4': {'items': 'string'}
+  })";
+  EXPECT_JSON_EQ(expected1, *schema->ToJson(false, false, nullptr));
+  auto expected2 = R"({
+    'prop1': {'type':'integer','isRequired': true},
+    'prop2': {'type':'string','isRequired': false},
+    'prop3': {'type':'boolean','isRequired': true},
+    'prop4': {'items': 'string'}
+  })";
+  EXPECT_JSON_EQ(expected2, *schema->ToJson(false, true, nullptr));
+
+  obj_type.SetObjectSchema(std::move(schema));
+  auto expected3 = R"({
+    'additionalProperties': false,
+    'properties': {
+      'prop1': 'integer',
+      'prop2': 'string',
+      'prop3': 'boolean',
+      'prop4': {'items': 'string'}
+    },
+    'required': ['prop1','prop3']
+  })";
+  EXPECT_JSON_EQ(expected3, *obj_type.ToJson(false, false, nullptr));
+  EXPECT_JSON_EQ(expected3, *obj_type.ToJson(false, true, nullptr));
+}
+
+TEST(CommandSchema, RequiredProperties_Schema_FromJson) {
+  ObjectSchema schema;
+  auto schema_str = R"({
+    'prop1': {'type':'integer','isRequired': true},
+    'prop2': {'type':'string','isRequired': false},
+    'prop3': 'boolean'
+  })";
+  EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                              nullptr));
+  EXPECT_TRUE(schema.GetProp("prop1")->IsRequired());
+  EXPECT_FALSE(schema.GetProp("prop2")->IsRequired());
+  EXPECT_FALSE(schema.GetProp("prop3")->IsRequired());
+  EXPECT_JSON_EQ(schema_str, *schema.ToJson(false, true, nullptr));
+}
+
+TEST(CommandSchema, RequiredProperties_Schema_FromJson_Inherit) {
+  ObjectSchema base_schema;
+  auto base_schema_str = R"({
+    'prop1': {'type':'integer','isRequired': true},
+    'prop2': {'type':'integer','isRequired': false},
+    'prop3': {'type':'integer','isRequired': true},
+    'prop4': {'type':'integer','isRequired': false}
+  })";
+  EXPECT_TRUE(base_schema.FromJson(CreateDictionaryValue(base_schema_str).get(),
+                                   nullptr, nullptr));
+  ObjectSchema schema;
+  auto schema_str = R"({
+    'prop1': {'isRequired': false},
+    'prop2': {'isRequired': true},
+    'prop3': {},
+    'prop4': 'integer'
+  })";
+  EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(),
+                              &base_schema, nullptr));
+  EXPECT_FALSE(schema.GetProp("prop1")->IsRequired());
+  EXPECT_TRUE(schema.GetProp("prop2")->IsRequired());
+  EXPECT_TRUE(schema.GetProp("prop3")->IsRequired());
+  EXPECT_FALSE(schema.GetProp("prop4")->IsRequired());
+  auto expected = R"({
+    'prop1': {'type':'integer','isRequired': false},
+    'prop2': {'type':'integer','isRequired': true},
+    'prop3': {},
+    'prop4': {}
+  })";
+  EXPECT_JSON_EQ(expected, *schema.ToJson(false, true, nullptr));
+}
+
+TEST(CommandSchema, RequiredProperties_ObjectPropType_FromJson) {
+  ObjectPropType obj_type;
+  auto type_str = R"({
+    'properties': {
+      'prop1': 'integer',
+      'prop2': 'string',
+      'prop3': {'type':'boolean','isRequired':true},
+      'prop4': {'items': 'string','isRequired':false},
+      'prop5': {'type':'number','isRequired':true}
+    },
+    'required': ['prop1','prop3','prop4','prop5']
+  })";
+  EXPECT_TRUE(obj_type.FromJson(CreateDictionaryValue(type_str).get(), nullptr,
+                                nullptr));
+  EXPECT_TRUE(obj_type.GetObjectSchemaPtr()->GetProp("prop1")->IsRequired());
+  EXPECT_FALSE(obj_type.GetObjectSchemaPtr()->GetProp("prop2")->IsRequired());
+  EXPECT_TRUE(obj_type.GetObjectSchemaPtr()->GetProp("prop3")->IsRequired());
+  // 'required' takes precedence over 'isRequired'.
+  EXPECT_TRUE(obj_type.GetObjectSchemaPtr()->GetProp("prop4")->IsRequired());
+  EXPECT_TRUE(obj_type.GetObjectSchemaPtr()->GetProp("prop5")->IsRequired());
+}
+
+TEST(CommandSchema, RequiredProperties_Failures) {
+  ObjectPropType obj_type;
+  chromeos::ErrorPtr error;
+
+  auto type_str = R"({
+    'properties': {
+      'prop1': 'integer',
+      'prop2': 'string'
+    },
+    'required': ['prop1','prop3','prop4']
+  })";
+  EXPECT_FALSE(obj_type.FromJson(CreateDictionaryValue(type_str).get(), nullptr,
+                                 &error));
+  EXPECT_EQ(errors::commands::kUnknownProperty, error->GetCode());
+  EXPECT_EQ("Unknown property 'prop3'", error->GetMessage());
+  error.reset();
+
+  type_str = R"({
+    'properties': {
+      'prop1': 'integer',
+      'prop2': 'string'
+    },
+    'required': 'prop1'
+  })";
+  EXPECT_FALSE(obj_type.FromJson(CreateDictionaryValue(type_str).get(), nullptr,
+                                 &error));
+  EXPECT_EQ(errors::commands::kInvalidObjectSchema, error->GetCode());
+  EXPECT_EQ("Property 'required' must be an array", error->GetMessage());
+  error.reset();
+}
+
+TEST(CommandSchema, ObjectSchema_UseRequired) {
+  ObjectPropType prop;
+  auto schema_str = R"({
+    'properties':{
+      'param1':'integer',
+      'param2':'integer',
+      'param3':{'default':3},
+      'param4':{'default':4}
+    },
+    'required':['param1','param3']
+  })";
+  ASSERT_TRUE(prop.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                            nullptr));
+
+  auto value = prop.CreateValue();
+  auto val_json = R"({
+    'param1':10,
+    'param2':20,
+    'param3':30,
+    'param4':40
+  })";
+  ASSERT_TRUE(value->FromJson(CreateDictionaryValue(val_json).get(), nullptr));
+  native_types::Object obj = value->GetObject()->GetValue();
+  EXPECT_EQ(10, obj["param1"]->GetInt()->GetValue());
+  EXPECT_EQ(20, obj["param2"]->GetInt()->GetValue());
+  EXPECT_EQ(30, obj["param3"]->GetInt()->GetValue());
+  EXPECT_EQ(40, obj["param4"]->GetInt()->GetValue());
+
+  value = prop.CreateValue();
+  val_json = "{'param1':100}";
+  ASSERT_TRUE(value->FromJson(CreateDictionaryValue(val_json).get(), nullptr));
+  obj = value->GetObject()->GetValue();
+  EXPECT_EQ(3, obj.size());
+
+  EXPECT_EQ(100, obj["param1"]->GetInt()->GetValue());
+  EXPECT_EQ(obj.end(), obj.find("param2"));
+  EXPECT_EQ(3, obj["param3"]->GetInt()->GetValue());
+  EXPECT_EQ(4, obj["param4"]->GetInt()->GetValue());
+}
+
+TEST(CommandSchema, ObjectSchema_UseRequired_Failure) {
+  ObjectPropType prop;
+  auto schema_str = R"({
+    'properties':{
+      'param1':'integer',
+      'param2':'integer',
+      'param3':{'default':3},
+      'param4':{'default':4}
+    },
+    'required':['param1','param3']
+  })";
+  ASSERT_TRUE(prop.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                            nullptr));
+
+  auto value = prop.CreateValue();
+  auto val_json = "{'param2':20}";
+  chromeos::ErrorPtr error;
+  ASSERT_FALSE(value->FromJson(CreateDictionaryValue(val_json).get(), &error));
+  EXPECT_EQ(errors::commands::kPropertyMissing, error->GetCode());
+  EXPECT_EQ("Required parameter missing: param1", error->GetMessage());
+}
+
 }  // namespace buffet
diff --git a/libweave/src/commands/prop_types.cc b/libweave/src/commands/prop_types.cc
index bfd1b50..eb4fc6e 100644
--- a/libweave/src/commands/prop_types.cc
+++ b/libweave/src/commands/prop_types.cc
@@ -42,8 +42,27 @@
   return false;
 }
 
+bool PropType::IsRequired() const {
+  return required_.value;
+}
+
+void PropType::MakeRequired(bool required) {
+  required_.value = required;
+  required_.is_inherited = false;
+}
+
 std::unique_ptr<base::Value> PropType::ToJson(bool full_schema,
+                                              bool in_command_def,
                                               chromeos::ErrorPtr* error) const {
+  // Determine if we need to output "isRequired" attribute.
+  const bool include_required = in_command_def && !required_.is_inherited;
+
+  // If we must include "isRequired" attribute, then treat this as "full schema"
+  // request because there could be cases where we have just this attribute and
+  // won't be able to infer the type from the constraints only.
+  if (include_required)
+    full_schema = true;
+
   if (!full_schema && !HasOverriddenAttributes()) {
     if (based_on_schema_)
       return std::unique_ptr<base::Value>(new base::DictionaryValue);
@@ -90,6 +109,8 @@
     dict->Set(commands::attributes::kDefault, defval.release());
   }
 
+  if (include_required)
+    dict->SetBoolean(commands::attributes::kIsRequired, required_.value);
   return std::unique_ptr<base::Value>(dict.release());
 }
 
@@ -102,6 +123,7 @@
   cloned->default_.is_inherited = default_.is_inherited;
   if (default_.value)
     cloned->default_.value = default_.value->Clone();
+  cloned->required_ = required_;
   return cloned;
 }
 
@@ -125,6 +147,7 @@
       commands::attributes::kType,
       commands::attributes::kDisplayName,
       commands::attributes::kDefault,
+      commands::attributes::kIsRequired,
   };
   if (!ObjectSchemaFromJson(value, base_schema, &processed_keys, error))
     return false;
@@ -150,6 +173,18 @@
     iter.Advance();
   }
 
+  // Read the "isRequired" attribute, if specified.
+  bool required = false;
+  if (value->GetBoolean(commands::attributes::kIsRequired, &required)) {
+    required_.value = required;
+    required_.is_inherited = false;
+  } else if (base_schema) {
+    // If we have the base schema, inherit the type's required value from it.
+    if (base_schema->required_.value)
+      required_.value = base_schema->required_.value;
+    required_.is_inherited = true;
+  }
+
   // Read the default value, if specified.
   // We need to do this last since the current type definition must be complete,
   // so we can parse and validate the value of the default.
@@ -462,13 +497,16 @@
 
 std::unique_ptr<base::Value> ObjectPropType::ToJson(
     bool full_schema,
+    bool in_command_def,
     chromeos::ErrorPtr* error) const {
-  std::unique_ptr<base::Value> value = PropType::ToJson(full_schema, error);
+  std::unique_ptr<base::Value> value =
+      PropType::ToJson(full_schema, in_command_def, error);
   if (value) {
     base::DictionaryValue* dict = nullptr;
     CHECK(value->GetAsDictionary(&dict)) << "Expecting a JSON object";
     if (!object_schema_.is_inherited || full_schema) {
-      auto object_schema = object_schema_.value->ToJson(full_schema, error);
+      auto object_schema =
+          object_schema_.value->ToJson(full_schema, false, error);
       if (!object_schema) {
         value.reset();
         return value;
@@ -478,6 +516,14 @@
       dict->SetBooleanWithoutPathExpansion(
           commands::attributes::kObject_AdditionalProperties,
           object_schema_.value->GetExtraPropertiesAllowed());
+      std::unique_ptr<base::ListValue> required{new base::ListValue};
+      for (const auto& pair : object_schema_.value->GetProps()) {
+        if (pair.second->IsRequired())
+          required->AppendString(pair.first);
+      }
+      if (required->GetSize() > 0) {
+        dict->Set(commands::attributes::kObject_Required, required.release());
+      }
     }
   }
   return value;
@@ -498,35 +544,20 @@
     base_object_schema = base_schema->GetObject()->GetObjectSchemaPtr();
 
   const base::DictionaryValue* props = nullptr;
+  std::unique_ptr<ObjectSchema> object_schema;
+  bool inherited = false;
   if (value->GetDictionaryWithoutPathExpansion(kObject_Properties, &props)) {
     processed_keys->insert(kObject_Properties);
-    std::unique_ptr<ObjectSchema> object_schema{new ObjectSchema};
+    object_schema.reset(new ObjectSchema);
     if (!object_schema->FromJson(props, base_object_schema, error)) {
       chromeos::Error::AddTo(error, FROM_HERE, errors::commands::kDomain,
                              errors::commands::kInvalidObjectSchema,
                              "Error parsing object property schema");
       return false;
     }
-    bool extra_properties_allowed = false;
-    if (value->GetBooleanWithoutPathExpansion(kObject_AdditionalProperties,
-                                              &extra_properties_allowed)) {
-      processed_keys->insert(kObject_AdditionalProperties);
-      object_schema->SetExtraPropertiesAllowed(extra_properties_allowed);
-    }
-    object_schema_.value = std::move(object_schema);
-    object_schema_.is_inherited = false;
   } else if (base_object_schema) {
-    auto cloned_object_schema = base_object_schema->Clone();
-    bool extra_properties_allowed = false;
-    if (value->GetBooleanWithoutPathExpansion(kObject_AdditionalProperties,
-                                              &extra_properties_allowed)) {
-      processed_keys->insert(kObject_AdditionalProperties);
-      cloned_object_schema->SetExtraPropertiesAllowed(extra_properties_allowed);
-      object_schema_.is_inherited = false;
-    } else {
-      object_schema_.is_inherited = true;
-    }
-    object_schema_.value = std::move(cloned_object_schema);
+    object_schema = base_object_schema->Clone();
+    inherited = true;
   } else {
     chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
                                  errors::commands::kInvalidObjectSchema,
@@ -535,6 +566,43 @@
                                  kObject_Properties);
     return false;
   }
+  bool extra_properties_allowed = false;
+  if (value->GetBooleanWithoutPathExpansion(kObject_AdditionalProperties,
+                                            &extra_properties_allowed)) {
+    processed_keys->insert(kObject_AdditionalProperties);
+    object_schema->SetExtraPropertiesAllowed(extra_properties_allowed);
+    inherited = false;
+  }
+  const base::Value* required = nullptr;
+  if (value->Get(commands::attributes::kObject_Required, &required)) {
+    processed_keys->insert(commands::attributes::kObject_Required);
+    const base::ListValue* required_list = nullptr;
+    if (!required->GetAsList(&required_list)) {
+      chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                                   errors::commands::kInvalidObjectSchema,
+                                   "Property '%s' must be an array",
+                                   commands::attributes::kObject_Required);
+      return false;
+    }
+    for (const base::Value* value : *required_list) {
+      std::string name;
+      if (!value->GetAsString(&name)) {
+        std::string json_value;
+        CHECK(base::JSONWriter::Write(*value, &json_value));
+        chromeos::Error::AddToPrintf(
+            error, FROM_HERE, errors::commands::kDomain,
+            errors::commands::kInvalidObjectSchema,
+            "Property '%s' contains invalid element (%s). String expected",
+            commands::attributes::kObject_Required, json_value.c_str());
+        return false;
+      }
+      if (!object_schema->MarkPropRequired(name, error))
+        return false;
+      inherited = false;
+    }
+  }
+  object_schema_.value = std::move(object_schema);
+  object_schema_.is_inherited = inherited;
 
   return true;
 }
@@ -594,20 +662,20 @@
 
 std::unique_ptr<base::Value> ArrayPropType::ToJson(
     bool full_schema,
+    bool in_command_def,
     chromeos::ErrorPtr* error) const {
-  std::unique_ptr<base::Value> value = PropType::ToJson(full_schema, error);
-  if (value) {
+  std::unique_ptr<base::Value> value =
+      PropType::ToJson(full_schema, in_command_def, error);
+  if (value && (!item_type_.is_inherited || full_schema)) {
     base::DictionaryValue* dict = nullptr;
     CHECK(value->GetAsDictionary(&dict)) << "Expecting a JSON object";
-    if (!item_type_.is_inherited || full_schema) {
-      auto type = item_type_.value->ToJson(full_schema, error);
+    auto type = item_type_.value->ToJson(full_schema, false, error);
       if (!type) {
         value.reset();
         return value;
       }
       dict->SetWithoutPathExpansion(commands::attributes::kItems,
                                     type.release());
-    }
   }
   return value;
 }
diff --git a/libweave/src/commands/prop_types.h b/libweave/src/commands/prop_types.h
index 156b556..9acab29 100644
--- a/libweave/src/commands/prop_types.h
+++ b/libweave/src/commands/prop_types.h
@@ -55,6 +55,12 @@
   const PropValue* GetDefaultValue() const { return default_.value.get(); }
   // Gets the constraints specified for the parameter, if any.
   const ConstraintMap& GetConstraints() const { return constraints_; }
+  // Returns true if this value is required. Properties are marked as required
+  // by using "isRequired" attribute or listed in "required" array.
+  bool IsRequired() const;
+  // Sets the required attribute to the value of |required| and marks it as
+  // overridden (not-inherited).
+  void MakeRequired(bool required);
   // Checks if any of the type attributes were overridden from the base
   // schema definition. If this type does not inherit from a base schema,
   // this method returns true.
@@ -109,7 +115,12 @@
   // saved.
   // If it fails, returns "nullptr" and fills in the |error| with additional
   // error information.
+  // |in_command_def| is set to true if the property type describes a
+  // GCD command parameter, otherwise it is for an object property.
+  // Command definitions handle required parameters differently (using
+  // "isRequired" property as opposed to "required" list for object properties).
   virtual std::unique_ptr<base::Value> ToJson(bool full_schema,
+                                              bool in_command_def,
                                               chromeos::ErrorPtr* error) const;
   // Parses an JSON parameter type definition. Optional |base_schema| may
   // specify the base schema type definition this type should be based upon.
@@ -190,6 +201,11 @@
   // Otherwise the parameter is treated as required and, if it is omitted,
   // this is treated as an error.
   InheritableAttribute<std::unique_ptr<PropValue>> default_;
+  // Specifies whether the parameter/property is required and must be specified
+  // (either directly, or by the default value being provided in the schema).
+  // Non-required parameters can be omitted completely and their values will not
+  // be present in the object instance.
+  InheritableAttribute<bool> required_;
 };
 
 // Base class for all the derived concrete implementations of property
@@ -346,6 +362,7 @@
   std::unique_ptr<PropType> Clone() const override;
 
   std::unique_ptr<base::Value> ToJson(bool full_schema,
+                                      bool in_command_def,
                                       chromeos::ErrorPtr* error) const override;
   bool ObjectSchemaFromJson(const base::DictionaryValue* value,
                             const PropType* base_schema,
@@ -385,6 +402,7 @@
   std::unique_ptr<PropType> Clone() const override;
 
   std::unique_ptr<base::Value> ToJson(bool full_schema,
+                                      bool in_command_def,
                                       chromeos::ErrorPtr* error) const override;
 
   bool ObjectSchemaFromJson(const base::DictionaryValue* value,
diff --git a/libweave/src/commands/schema_constants.cc b/libweave/src/commands/schema_constants.cc
index 733c66d..2cb1dfb 100644
--- a/libweave/src/commands/schema_constants.cc
+++ b/libweave/src/commands/schema_constants.cc
@@ -35,6 +35,7 @@
 const char kDisplayName[] = "displayName";
 const char kDefault[] = "default";
 const char kItems[] = "items";
+const char kIsRequired[] = "isRequired";
 
 const char kNumeric_Min[] = "minimum";
 const char kNumeric_Max[] = "maximum";
@@ -47,6 +48,7 @@
 
 const char kObject_Properties[] = "properties";
 const char kObject_AdditionalProperties[] = "additionalProperties";
+const char kObject_Required[] = "required";
 
 const char kCommand_Id[] = "id";
 const char kCommand_Name[] = "name";
diff --git a/libweave/src/commands/schema_constants.h b/libweave/src/commands/schema_constants.h
index e00bae5..379c729 100644
--- a/libweave/src/commands/schema_constants.h
+++ b/libweave/src/commands/schema_constants.h
@@ -38,6 +38,7 @@
 extern const char kDisplayName[];
 extern const char kDefault[];
 extern const char kItems[];
+extern const char kIsRequired[];
 
 extern const char kNumeric_Min[];
 extern const char kNumeric_Max[];
@@ -50,6 +51,7 @@
 
 extern const char kObject_Properties[];
 extern const char kObject_AdditionalProperties[];
+extern const char kObject_Required[];
 
 extern const char kCommand_Id[];
 extern const char kCommand_Name[];
diff --git a/libweave/src/commands/schema_utils.cc b/libweave/src/commands/schema_utils.cc
index c0067ab..1575fdc 100644
--- a/libweave/src/commands/schema_utils.cc
+++ b/libweave/src/commands/schema_utils.cc
@@ -17,12 +17,13 @@
 namespace buffet {
 namespace {
 // Helper function to report "type mismatch" errors when parsing JSON.
-void ReportJsonTypeMismatch(const base::Value* value_in,
+void ReportJsonTypeMismatch(const tracked_objects::Location& location,
+                            const base::Value* value_in,
                             const std::string& expected_type,
                             chromeos::ErrorPtr* error) {
   std::string value_as_string;
   base::JSONWriter::Write(*value_in, &value_as_string);
-  chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+  chromeos::Error::AddToPrintf(error, location, errors::commands::kDomain,
                                errors::commands::kTypeMismatch,
                                "Unable to convert value %s into %s",
                                value_as_string.c_str(), expected_type.c_str());
@@ -32,16 +33,20 @@
 // data from the value_out parameter passed to particular overload of
 // TypedValueFromJson() function. Always returns false.
 template <typename T>
-bool ReportUnexpectedJson(const base::Value* value_in,
+bool ReportUnexpectedJson(const tracked_objects::Location& location,
+                          const base::Value* value_in,
                           T*,
                           chromeos::ErrorPtr* error) {
-  ReportJsonTypeMismatch(
-      value_in, PropType::GetTypeStringFromType(GetValueType<T>()), error);
+  ReportJsonTypeMismatch(location, value_in,
+                         PropType::GetTypeStringFromType(GetValueType<T>()),
+                         error);
   return false;
 }
 
-bool ErrorMissingProperty(chromeos::ErrorPtr* error, const char* param_name) {
-  chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+bool ErrorMissingProperty(chromeos::ErrorPtr* error,
+                          const tracked_objects::Location& location,
+                          const char* param_name) {
+  chromeos::Error::AddToPrintf(error, location, errors::commands::kDomain,
                                errors::commands::kPropertyMissing,
                                "Required parameter missing: %s", param_name);
   return false;
@@ -98,7 +103,7 @@
                         bool* value_out,
                         chromeos::ErrorPtr* error) {
   return value_in->GetAsBoolean(value_out) ||
-         ReportUnexpectedJson(value_in, value_out, error);
+         ReportUnexpectedJson(FROM_HERE, value_in, value_out, error);
 }
 
 bool TypedValueFromJson(const base::Value* value_in,
@@ -106,7 +111,7 @@
                         int* value_out,
                         chromeos::ErrorPtr* error) {
   return value_in->GetAsInteger(value_out) ||
-         ReportUnexpectedJson(value_in, value_out, error);
+         ReportUnexpectedJson(FROM_HERE, value_in, value_out, error);
 }
 
 bool TypedValueFromJson(const base::Value* value_in,
@@ -114,7 +119,7 @@
                         double* value_out,
                         chromeos::ErrorPtr* error) {
   return value_in->GetAsDouble(value_out) ||
-         ReportUnexpectedJson(value_in, value_out, error);
+         ReportUnexpectedJson(FROM_HERE, value_in, value_out, error);
 }
 
 bool TypedValueFromJson(const base::Value* value_in,
@@ -122,7 +127,7 @@
                         std::string* value_out,
                         chromeos::ErrorPtr* error) {
   return value_in->GetAsString(value_out) ||
-         ReportUnexpectedJson(value_in, value_out, error);
+         ReportUnexpectedJson(FROM_HERE, value_in, value_out, error);
 }
 
 bool TypedValueFromJson(const base::Value* value_in,
@@ -131,7 +136,7 @@
                         chromeos::ErrorPtr* error) {
   const base::DictionaryValue* dict = nullptr;
   if (!value_in->GetAsDictionary(&dict))
-    return ReportUnexpectedJson(value_in, value_out, error);
+    return ReportUnexpectedJson(FROM_HERE, value_in, value_out, error);
 
   CHECK(type) << "Object definition must be provided";
   CHECK(ValueType::Object == type->GetType()) << "Type must be Object";
@@ -155,12 +160,13 @@
         return false;
       }
       value_out->emplace_hint(value_out->end(), pair.first, std::move(value));
+      keys_processed.insert(pair.first);
     } else if (def_value) {
       value_out->emplace_hint(value_out->end(), pair.first, def_value->Clone());
-    } else {
-      return ErrorMissingProperty(error, pair.first.c_str());
+      keys_processed.insert(pair.first);
+    } else if (pair.second->IsRequired()) {
+      return ErrorMissingProperty(error, FROM_HERE, pair.first.c_str());
     }
-    keys_processed.insert(pair.first);
   }
 
   // Just for sanity, make sure that we processed all the necessary properties
@@ -200,7 +206,7 @@
                         chromeos::ErrorPtr* error) {
   const base::ListValue* list = nullptr;
   if (!value_in->GetAsList(&list))
-    return ReportUnexpectedJson(value_in, value_out, error);
+    return ReportUnexpectedJson(FROM_HERE, value_in, value_out, error);
 
   CHECK(type) << "Array type definition must be provided";
   CHECK(ValueType::Array == type->GetType()) << "Type must be Array";
@@ -355,7 +361,7 @@
     } else if (def_value) {
       obj->emplace_hint(obj->end(), pair.first, def_value->Clone());
     } else {
-      ErrorMissingProperty(error, pair.first.c_str());
+      ErrorMissingProperty(error, FROM_HERE, pair.first.c_str());
       return false;
     }
     keys_processed.insert(pair.first);
diff --git a/libweave/src/states/state_package_unittest.cc b/libweave/src/states/state_package_unittest.cc
index 0b860cd..c6372a9 100644
--- a/libweave/src/states/state_package_unittest.cc
+++ b/libweave/src/states/state_package_unittest.cc
@@ -109,7 +109,7 @@
       'type': 'boolean'
     }
   })";
-  EXPECT_JSON_EQ(expected, *GetTypes(package).ToJson(true, nullptr));
+  EXPECT_JSON_EQ(expected, *GetTypes(package).ToJson(true, false, nullptr));
 
   expected = R"({
     'color': '',
@@ -171,7 +171,7 @@
       'type': 'boolean'
     }
   })";
-  EXPECT_JSON_EQ(expected, *GetTypes(*package_).ToJson(true, nullptr));
+  EXPECT_JSON_EQ(expected, *GetTypes(*package_).ToJson(true, false, nullptr));
 
   expected = R"({
     'brightness': '',