buffet: Add support for 'default' properties in CDD

The internals of supporting optional command parameters/state
properties was built into buffet earlier, but this was not exposed
in JSON reading/writing routines, so it was impossible to use this
feature.

Added JSON serialization/deserialization code and unit tests
to verify the operation.

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

Change-Id: I29c8d3d7c0894a9c837e73d0fdb16bafdfadfeca
Reviewed-on: https://chromium-review.googlesource.com/253070
Trybot-Ready: Alex Vakulenko <avakulenko@chromium.org>
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Reviewed-by: Christopher Wiley <wiley@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/commands/object_schema.cc b/buffet/commands/object_schema.cc
index 41e7639..6437262 100644
--- a/buffet/commands/object_schema.cc
+++ b/buffet/commands/object_schema.cc
@@ -236,6 +236,18 @@
       commands::attributes::kOneOf_Enum, &list))
     return DetectArrayType(list, base_schema);
 
+  // If we have "default", try to use it for type detection.
+  if (dict->Get(commands::attributes::kDefault, &value)) {
+    if (value->IsType(base::Value::TYPE_DOUBLE))
+      return PropType::GetTypeStringFromType(ValueType::Double);
+    if (value->IsType(base::Value::TYPE_INTEGER))
+      return PropType::GetTypeStringFromType(ValueType::Int);
+    if (value->IsType(base::Value::TYPE_BOOLEAN))
+      return PropType::GetTypeStringFromType(ValueType::Boolean);
+    if (value->IsType(base::Value::TYPE_STRING))
+      return PropType::GetTypeStringFromType(ValueType::String);
+  }
+
   return std::string();
 }
 
diff --git a/buffet/commands/object_schema_unittest.cc b/buffet/commands/object_schema_unittest.cc
index 505d6a6..0f429a1 100644
--- a/buffet/commands/object_schema_unittest.cc
+++ b/buffet/commands/object_schema_unittest.cc
@@ -27,6 +27,7 @@
   EXPECT_TRUE(prop.GetConstraints().empty());
   EXPECT_FALSE(prop.HasOverriddenAttributes());
   EXPECT_FALSE(prop.IsBasedOnSchema());
+  EXPECT_EQ(nullptr, prop.GetDefaultValue());
 }
 
 TEST(CommandSchema, IntPropType_Types) {
@@ -62,6 +63,10 @@
                   nullptr);
   EXPECT_EQ("[1,2,3]",
             ValueToString(param2.ToJson(false, nullptr).get()));
+  param2.FromJson(CreateDictionaryValue("{'default':123}").get(),
+                  &prop, nullptr);
+  EXPECT_EQ("{'default':123}",
+            ValueToString(param2.ToJson(false, nullptr).get()));
 }
 
 TEST(CommandSchema, IntPropType_FromJson) {
@@ -86,12 +91,12 @@
   EXPECT_TRUE(param2.IsBasedOnSchema());
   EXPECT_EQ(-2, param2.GetMinValue());
   EXPECT_EQ(17, param2.GetMaxValue());
-  param2.FromJson(CreateDictionaryValue("{'minimum':0,'maximum':6}").get(),
-                  &prop, nullptr);
+
+  ASSERT_TRUE(param2.FromJson(CreateDictionaryValue("{'default':3}").get(),
+                              &prop, nullptr));
   EXPECT_TRUE(param2.HasOverriddenAttributes());
-  EXPECT_TRUE(param2.IsBasedOnSchema());
-  EXPECT_EQ(0, param2.GetMinValue());
-  EXPECT_EQ(6, param2.GetMaxValue());
+  ASSERT_NE(nullptr, param2.GetDefaultValue());
+  EXPECT_EQ(3, param2.GetDefaultValue()->GetInt()->GetValue());
 }
 
 TEST(CommandSchema, IntPropType_Validate) {
@@ -147,6 +152,7 @@
   EXPECT_TRUE(prop.GetConstraints().empty());
   EXPECT_FALSE(prop.HasOverriddenAttributes());
   EXPECT_FALSE(prop.IsBasedOnSchema());
+  EXPECT_EQ(nullptr, prop.GetDefaultValue());
 }
 
 TEST(CommandSchema, BoolPropType_Types) {
@@ -171,6 +177,10 @@
   EXPECT_EQ("[true,false]", ValueToString(param2.ToJson(false, nullptr).get()));
   EXPECT_EQ("{'enum':[true,false],'type':'boolean'}",
             ValueToString(param2.ToJson(true, nullptr).get()));
+  param2.FromJson(CreateDictionaryValue("{'default':true}").get(),
+                  &prop, nullptr);
+  EXPECT_EQ("{'default':true}",
+            ValueToString(param2.ToJson(false, nullptr).get()));
 }
 
 TEST(CommandSchema, BoolPropType_FromJson) {
@@ -182,6 +192,14 @@
   EXPECT_FALSE(param2.HasOverriddenAttributes());
   EXPECT_TRUE(param2.IsBasedOnSchema());
   EXPECT_EQ(std::vector<bool>{true}, prop.GetOneOfValues());
+
+  buffet::BooleanPropType prop_base;
+  buffet::BooleanPropType param3;
+  ASSERT_TRUE(param3.FromJson(CreateDictionaryValue("{'default':false}").get(),
+                              &prop_base, nullptr));
+  EXPECT_TRUE(param3.HasOverriddenAttributes());
+  ASSERT_NE(nullptr, param3.GetDefaultValue());
+  EXPECT_FALSE(param3.GetDefaultValue()->GetBoolean()->GetValue());
 }
 
 TEST(CommandSchema, BoolPropType_Validate) {
@@ -226,6 +244,7 @@
   EXPECT_DOUBLE_EQ((std::numeric_limits<double>::max)(), prop.GetMaxValue());
   EXPECT_FALSE(prop.HasOverriddenAttributes());
   EXPECT_FALSE(prop.IsBasedOnSchema());
+  EXPECT_EQ(nullptr, prop.GetDefaultValue());
 }
 
 TEST(CommandSchema, DoublePropType_Types) {
@@ -257,6 +276,10 @@
                   &prop, nullptr);
   EXPECT_EQ("{'maximum':5.0,'minimum':0.0}",
             ValueToString(param2.ToJson(false, nullptr).get()));
+  param2.FromJson(CreateDictionaryValue("{'default':12.3}").get(),
+                  &prop, nullptr);
+  EXPECT_EQ("{'default':12.3}",
+            ValueToString(param2.ToJson(false, nullptr).get()));
 }
 
 TEST(CommandSchema, DoublePropType_FromJson) {
@@ -287,6 +310,12 @@
   EXPECT_TRUE(param2.IsBasedOnSchema());
   EXPECT_DOUBLE_EQ(0.0, param2.GetMinValue());
   EXPECT_DOUBLE_EQ(6.1, param2.GetMaxValue());
+
+  ASSERT_TRUE(param2.FromJson(CreateDictionaryValue("{'default':-1.234}").get(),
+                              &prop, nullptr));
+  EXPECT_TRUE(param2.HasOverriddenAttributes());
+  ASSERT_NE(nullptr, param2.GetDefaultValue());
+  EXPECT_DOUBLE_EQ(-1.234, param2.GetDefaultValue()->GetDouble()->GetValue());
 }
 
 TEST(CommandSchema, DoublePropType_Validate) {
@@ -337,6 +366,7 @@
   EXPECT_EQ((std::numeric_limits<int>::max)(), prop.GetMaxLength());
   EXPECT_FALSE(prop.HasOverriddenAttributes());
   EXPECT_FALSE(prop.IsBasedOnSchema());
+  EXPECT_EQ(nullptr, prop.GetDefaultValue());
 }
 
 TEST(CommandSchema, StringPropType_Types) {
@@ -368,6 +398,10 @@
                   &prop, nullptr);
   EXPECT_EQ("{'maxLength':5,'minLength':0}",
             ValueToString(param2.ToJson(false, nullptr).get()));
+  param2.FromJson(CreateDictionaryValue("{'default':'abcd'}").get(),
+                  &prop, nullptr);
+  EXPECT_EQ("{'default':'abcd'}",
+            ValueToString(param2.ToJson(false, nullptr).get()));
 }
 
 TEST(CommandSchema, StringPropType_FromJson) {
@@ -398,6 +432,12 @@
   EXPECT_TRUE(param2.IsBasedOnSchema());
   EXPECT_EQ(1, param2.GetMinLength());
   EXPECT_EQ(7, param2.GetMaxLength());
+
+  ASSERT_TRUE(param2.FromJson(CreateDictionaryValue("{'default':'foo'}").get(),
+                              &prop, nullptr));
+  EXPECT_TRUE(param2.HasOverriddenAttributes());
+  ASSERT_NE(nullptr, param2.GetDefaultValue());
+  EXPECT_EQ("foo", param2.GetDefaultValue()->GetString()->GetValue());
 }
 
 TEST(CommandSchema, StringPropType_Validate) {
@@ -452,6 +492,7 @@
   buffet::ObjectPropType prop;
   EXPECT_TRUE(prop.HasOverriddenAttributes());
   EXPECT_FALSE(prop.IsBasedOnSchema());
+  EXPECT_EQ(nullptr, prop.GetDefaultValue());
 }
 
 TEST(CommandSchema, ObjectPropType_Types) {
@@ -488,6 +529,18 @@
             "'password':{'maxLength':100,'minLength':6,'type':'string'}},"
             "'type':'object'}",
             ValueToString(prop2.ToJson(true, nullptr).get()));
+
+  buffet::ObjectPropType prop3;
+  ASSERT_TRUE(prop3.FromJson(CreateDictionaryValue(
+      "{'default':{'expires':3,'password':'abracadabra'}}").get(), &prop2,
+      nullptr));
+  EXPECT_EQ("{'default':{'expires':3,'password':'abracadabra'}}",
+            ValueToString(prop3.ToJson(false, nullptr).get()));
+  EXPECT_EQ("{'default':{'expires':3,'password':'abracadabra'},"
+            "'properties':{'expires':{'type':'integer'},"
+            "'password':{'maxLength':100,'minLength':6,'type':'string'}},"
+            "'type':'object'}",
+            ValueToString(prop3.ToJson(true, nullptr).get()));
 }
 
 TEST(CommandSchema, ObjectPropType_FromJson) {
@@ -500,6 +553,17 @@
   EXPECT_EQ(buffet::ValueType::String, prop->GetType());
   prop = schema->GetProp("age");
   EXPECT_EQ(buffet::ValueType::Int, prop->GetType());
+
+  buffet::ObjectPropType prop2;
+  ASSERT_TRUE(prop2.FromJson(CreateDictionaryValue(
+      "{'properties':{'name':'string','age':'integer'},"
+      "'default':{'name':'Bob','age':33}}").get(), nullptr, nullptr));
+  ASSERT_NE(nullptr, prop2.GetDefaultValue());
+  const buffet::ObjectValue* defval = prop2.GetDefaultValue()->GetObject();
+  ASSERT_NE(nullptr, defval);
+  buffet::native_types::Object objval = defval->GetValue();
+  EXPECT_EQ("Bob", objval["name"]->GetString()->GetValue());
+  EXPECT_EQ(33, objval["age"]->GetInt()->GetValue());
 }
 
 TEST(CommandSchema, ObjectPropType_Validate) {
@@ -588,10 +652,10 @@
 TEST(CommandSchema, ObjectSchema_FromJson_Shorthand_TypeName) {
   buffet::ObjectSchema schema;
   const char* schema_str = "{"
-  "'param1':'integer',"
-  "'param2':'number',"
-  "'param3':'string'"
-  "}";
+      "'param1':'integer',"
+      "'param2':'number',"
+      "'param3':'string'"
+      "}";
   EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
                               nullptr));
   EXPECT_EQ(buffet::ValueType::Int, schema.GetProp("param1")->GetType());
@@ -617,10 +681,10 @@
 TEST(CommandSchema, ObjectSchema_FromJson_Full_TypeName) {
   buffet::ObjectSchema schema;
   const char* schema_str = "{"
-  "'param1':{'type':'integer'},"
-  "'param2':{'type':'number'},"
-  "'param3':{'type':'string'}"
-  "}";
+      "'param1':{'type':'integer'},"
+      "'param2':{'type':'number'},"
+      "'param3':{'type':'string'}"
+      "}";
   EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
                               nullptr));
   EXPECT_EQ(buffet::ValueType::Int, schema.GetProp("param1")->GetType());
@@ -646,18 +710,23 @@
 TEST(CommandSchema, ObjectSchema_FromJson_Shorthand_TypeDeduction_Scalar) {
   buffet::ObjectSchema schema;
   const char* schema_str = "{"
-  "'param1' :{'minimum':2},"
-  "'param2' :{'maximum':10},"
-  "'param3' :{'maximum':8, 'minimum':2},"
-  "'param4' :{'minimum':2.1},"
-  "'param5' :{'maximum':10.1},"
-  "'param6' :{'maximum':8.1, 'minimum':3.1},"
-  "'param7' :{'maximum':8, 'minimum':3.1},"
-  "'param8' :{'maximum':8.1, 'minimum':3},"
-  "'param9' :{'minLength':2},"
-  "'param10':{'maxLength':10},"
-  "'param11':{'maxLength':8, 'minLength':3}"
-  "}";
+      "'param1' :{'minimum':2},"
+      "'param2' :{'maximum':10},"
+      "'param3' :{'maximum':8, 'minimum':2},"
+      "'param4' :{'minimum':2.1},"
+      "'param5' :{'maximum':10.1},"
+      "'param6' :{'maximum':8.1, 'minimum':3.1},"
+      "'param7' :{'maximum':8, 'minimum':3.1},"
+      "'param8' :{'maximum':8.1, 'minimum':3},"
+      "'param9' :{'minLength':2},"
+      "'param10':{'maxLength':10},"
+      "'param11':{'maxLength':8, 'minLength':3},"
+      "'param12':{'default':12},"
+      "'param13':{'default':13.5},"
+      "'param14':{'default':true},"
+      "'param15':{'default':false},"
+      "'param16':{'default':'foobar'}"
+      "}";
   EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
                               nullptr));
   EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString());
@@ -671,6 +740,11 @@
   EXPECT_EQ("string", schema.GetProp("param9")->GetTypeAsString());
   EXPECT_EQ("string", schema.GetProp("param10")->GetTypeAsString());
   EXPECT_EQ("string", schema.GetProp("param11")->GetTypeAsString());
+  EXPECT_EQ("integer", schema.GetProp("param12")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param13")->GetTypeAsString());
+  EXPECT_EQ("boolean", schema.GetProp("param14")->GetTypeAsString());
+  EXPECT_EQ("boolean", schema.GetProp("param15")->GetTypeAsString());
+  EXPECT_EQ("string", schema.GetProp("param16")->GetTypeAsString());
 
   int min_int = (std::numeric_limits<int>::min)();
   int max_int = (std::numeric_limits<int>::max)();
@@ -700,22 +774,32 @@
   EXPECT_EQ(10, schema.GetProp("param10")->GetString()->GetMaxLength());
   EXPECT_EQ(3, schema.GetProp("param11")->GetString()->GetMinLength());
   EXPECT_EQ(8, schema.GetProp("param11")->GetString()->GetMaxLength());
+  const buffet::PropValue* val = schema.GetProp("param12")->GetDefaultValue();
+  EXPECT_EQ(12, val->GetInt()->GetValue());
+  val = schema.GetProp("param13")->GetDefaultValue();
+  EXPECT_DOUBLE_EQ(13.5, val->GetDouble()->GetValue());
+  val = schema.GetProp("param14")->GetDefaultValue();
+  EXPECT_TRUE(val->GetBoolean()->GetValue());
+  val = schema.GetProp("param15")->GetDefaultValue();
+  EXPECT_FALSE(val->GetBoolean()->GetValue());
+  val = schema.GetProp("param16")->GetDefaultValue();
+  EXPECT_EQ("foobar", val->GetString()->GetValue());
 }
 
 TEST(CommandSchema, ObjectSchema_FromJson_Shorthand_TypeDeduction_Array) {
   buffet::ObjectSchema schema;
   const char* schema_str = "{"
-  "'param1' :[0,1,2,3],"
-  "'param2' :[0.0,1.1,2.2],"
-  "'param3' :['id1', 'id2'],"
-  "'param4' :{'enum':[1,2,3]},"
-  "'param5' :{'enum':[-1.1,2.2,3]},"
-  "'param6' :{'enum':['id0', 'id1']},"
-  "'param7' :{'type':'integer', 'enum':[1,2,3]},"
-  "'param8' :{'type':'number',  'enum':[1,2,3]},"
-  "'param9' :{'type':'number',  'enum':[]},"
-  "'param10':{'type':'integer', 'enum':[]}"
-  "}";
+      "'param1' :[0,1,2,3],"
+      "'param2' :[0.0,1.1,2.2],"
+      "'param3' :['id1', 'id2'],"
+      "'param4' :{'enum':[1,2,3]},"
+      "'param5' :{'enum':[-1.1,2.2,3]},"
+      "'param6' :{'enum':['id0', 'id1']},"
+      "'param7' :{'type':'integer', 'enum':[1,2,3]},"
+      "'param8' :{'type':'number',  'enum':[1,2,3]},"
+      "'param9' :{'type':'number',  'enum':[]},"
+      "'param10':{'type':'integer', 'enum':[]}"
+      "}";
   EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
                               nullptr));
   EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString());
@@ -761,51 +845,57 @@
 
 TEST(CommandSchema, ObjectSchema_FromJson_Inheritance) {
   const char* base_schema_str = "{"
-  "'param0' :{'minimum':1, 'maximum':5},"
-  "'param1' :{'minimum':1, 'maximum':5},"
-  "'param2' :{'minimum':1, 'maximum':5},"
-  "'param3' :{'minimum':1, 'maximum':5},"
-  "'param4' :{'minimum':1, 'maximum':5},"
-  "'param5' :{'minimum':1.1, 'maximum':5.5},"
-  "'param6' :{'minimum':1.1, 'maximum':5.5},"
-  "'param7' :{'minimum':1.1, 'maximum':5.5},"
-  "'param8' :{'minimum':1.1, 'maximum':5.5},"
-  "'param9' :{'minLength':1, 'maxLength':5},"
-  "'param10':{'minLength':1, 'maxLength':5},"
-  "'param11':{'minLength':1, 'maxLength':5},"
-  "'param12':{'minLength':1, 'maxLength':5},"
-  "'param13':[1,2,3],"
-  "'param14':[1,2,3],"
-  "'param15':[1.1,2.2,3.3],"
-  "'param16':[1.1,2.2,3.3],"
-  "'param17':['id1', 'id2'],"
-  "'param18':['id1', 'id2'],"
-  "'param19':{'minimum':1, 'maximum':5}"
-  "}";
+      "'param0' :{'minimum':1, 'maximum':5},"
+      "'param1' :{'minimum':1, 'maximum':5},"
+      "'param2' :{'minimum':1, 'maximum':5},"
+      "'param3' :{'minimum':1, 'maximum':5},"
+      "'param4' :{'minimum':1, 'maximum':5},"
+      "'param5' :{'minimum':1.1, 'maximum':5.5},"
+      "'param6' :{'minimum':1.1, 'maximum':5.5},"
+      "'param7' :{'minimum':1.1, 'maximum':5.5},"
+      "'param8' :{'minimum':1.1, 'maximum':5.5},"
+      "'param9' :{'minLength':1, 'maxLength':5},"
+      "'param10':{'minLength':1, 'maxLength':5},"
+      "'param11':{'minLength':1, 'maxLength':5},"
+      "'param12':{'minLength':1, 'maxLength':5},"
+      "'param13':[1,2,3],"
+      "'param14':[1,2,3],"
+      "'param15':[1.1,2.2,3.3],"
+      "'param16':[1.1,2.2,3.3],"
+      "'param17':['id1', 'id2'],"
+      "'param18':['id1', 'id2'],"
+      "'param19':{'minimum':1, 'maximum':5},"
+      "'param20':{'default':49},"
+      "'param21':{'default':49},"
+      "'param22':'integer'"
+      "}";
   buffet::ObjectSchema base_schema;
   EXPECT_TRUE(base_schema.FromJson(CreateDictionaryValue(base_schema_str).get(),
                                    nullptr, nullptr));
   const char* schema_str = "{"
-  "'param1' :{},"
-  "'param2' :{'minimum':2},"
-  "'param3' :{'maximum':9},"
-  "'param4' :{'minimum':2, 'maximum':9},"
-  "'param5' :{},"
-  "'param6' :{'minimum':2.2},"
-  "'param7' :{'maximum':9.9},"
-  "'param8' :{'minimum':2.2, 'maximum':9.9},"
-  "'param9' :{},"
-  "'param10':{'minLength':3},"
-  "'param11':{'maxLength':8},"
-  "'param12':{'minLength':3, 'maxLength':8},"
-  "'param13':{},"
-  "'param14':[1,2,3,4],"
-  "'param15':{},"
-  "'param16':[1.1,2.2,3.3,4.4],"
-  "'param17':{},"
-  "'param18':['id1', 'id3'],"
-  "'param19':{}"
-  "}";
+      "'param1' :{},"
+      "'param2' :{'minimum':2},"
+      "'param3' :{'maximum':9},"
+      "'param4' :{'minimum':2, 'maximum':9},"
+      "'param5' :{},"
+      "'param6' :{'minimum':2.2},"
+      "'param7' :{'maximum':9.9},"
+      "'param8' :{'minimum':2.2, 'maximum':9.9},"
+      "'param9' :{},"
+      "'param10':{'minLength':3},"
+      "'param11':{'maxLength':8},"
+      "'param12':{'minLength':3, 'maxLength':8},"
+      "'param13':{},"
+      "'param14':[1,2,3,4],"
+      "'param15':{},"
+      "'param16':[1.1,2.2,3.3,4.4],"
+      "'param17':{},"
+      "'param18':['id1', 'id3'],"
+      "'param19':{},"
+      "'param20':{},"
+      "'param21':{'default':8},"
+      "'param22':{'default':123}"
+      "}";
   buffet::ObjectSchema schema;
   EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(),
                               &base_schema, nullptr));
@@ -868,64 +958,140 @@
   EXPECT_EQ("integer", schema.GetProp("param19")->GetTypeAsString());
   EXPECT_EQ(1, schema.GetProp("param19")->GetInt()->GetMinValue());
   EXPECT_EQ(5, schema.GetProp("param19")->GetInt()->GetMaxValue());
+  EXPECT_EQ(49,
+            schema.GetProp("param20")->GetDefaultValue()->GetInt()->GetValue());
+  EXPECT_EQ(8,
+            schema.GetProp("param21")->GetDefaultValue()->GetInt()->GetValue());
+  EXPECT_EQ(123,
+            schema.GetProp("param22")->GetDefaultValue()->GetInt()->GetValue());
+}
+
+TEST(CommandSchema, ObjectSchema_UseDefaults) {
+  buffet::ObjectPropType prop;
+  const char* schema_str = "{'properties':{"
+      "'param1':{'default':true},"
+      "'param2':{'default':2},"
+      "'param3':{'default':3.3},"
+      "'param4':{'default':'four'},"
+      "'param5':{'default':{'x':5,'y':6},"
+                "'properties':{'x':'integer','y':'integer'}}"
+      "}}";
+  ASSERT_TRUE(prop.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                            nullptr));
+
+  // Omit all.
+  auto value = prop.CreateValue();
+  ASSERT_TRUE(value->FromJson(CreateDictionaryValue("{}").get(), nullptr));
+  buffet::native_types::Object obj = value->GetObject()->GetValue();
+  EXPECT_TRUE(obj["param1"]->GetBoolean()->GetValue());
+  EXPECT_EQ(2, obj["param2"]->GetInt()->GetValue());
+  EXPECT_DOUBLE_EQ(3.3, obj["param3"]->GetDouble()->GetValue());
+  EXPECT_EQ("four", obj["param4"]->GetString()->GetValue());
+  buffet::native_types::Object param5 = obj["param5"]->GetObject()->GetValue();
+  EXPECT_EQ(5, param5["x"]->GetInt()->GetValue());
+  EXPECT_EQ(6, param5["y"]->GetInt()->GetValue());
+
+  // Specify some.
+  value = prop.CreateValue();
+  const char* val_json = "{"
+      "'param1':false,"
+      "'param3':33.3,"
+      "'param5':{'x':-5,'y':-6}"
+      "}";
+  ASSERT_TRUE(value->FromJson(CreateDictionaryValue(val_json).get(), nullptr));
+  obj = value->GetObject()->GetValue();
+  EXPECT_FALSE(obj["param1"]->GetBoolean()->GetValue());
+  EXPECT_EQ(2, obj["param2"]->GetInt()->GetValue());
+  EXPECT_DOUBLE_EQ(33.3, obj["param3"]->GetDouble()->GetValue());
+  EXPECT_EQ("four", obj["param4"]->GetString()->GetValue());
+  param5 = obj["param5"]->GetObject()->GetValue();
+  EXPECT_EQ(-5, param5["x"]->GetInt()->GetValue());
+  EXPECT_EQ(-6, param5["y"]->GetInt()->GetValue());
+
+  // Specify all.
+  value = prop.CreateValue();
+  val_json = "{"
+      "'param1':false,"
+      "'param2':22,"
+      "'param3':333.3,"
+      "'param4':'FOUR',"
+      "'param5':{'x':-55,'y':66}"
+      "}";
+  ASSERT_TRUE(value->FromJson(CreateDictionaryValue(val_json).get(), nullptr));
+  obj = value->GetObject()->GetValue();
+  EXPECT_FALSE(obj["param1"]->GetBoolean()->GetValue());
+  EXPECT_EQ(22, obj["param2"]->GetInt()->GetValue());
+  EXPECT_DOUBLE_EQ(333.3, obj["param3"]->GetDouble()->GetValue());
+  EXPECT_EQ("FOUR", obj["param4"]->GetString()->GetValue());
+  param5 = obj["param5"]->GetObject()->GetValue();
+  EXPECT_EQ(-55, param5["x"]->GetInt()->GetValue());
+  EXPECT_EQ(66, param5["y"]->GetInt()->GetValue());
 }
 
 TEST(CommandSchema, ObjectSchema_FromJson_BaseSchema_Failures) {
   buffet::ObjectSchema schema;
   chromeos::ErrorPtr error;
   const char* schema_str = "{"
-  "'param1':{}"
-  "}";
+      "'param1':{}"
+      "}";
   EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
                                &error));
   EXPECT_EQ("no_type_info", error->GetFirstError()->GetCode());
   error.reset();
 
   schema_str = "{"
-  "'param1':{'type':'foo'}"
-  "}";
+      "'param1':{'type':'foo'}"
+      "}";
   EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
                                &error));
   EXPECT_EQ("unknown_type", error->GetFirstError()->GetCode());
   error.reset();
 
   schema_str = "{"
-  "'param1':[]"
-  "}";
+      "'param1':[]"
+      "}";
   EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
                                &error));
   EXPECT_EQ("no_type_info", error->GetFirstError()->GetCode());
   error.reset();
 
   schema_str = "{"
-  "'param1':{'minimum':'foo'}"
-  "}";
+      "'param1':{'minimum':'foo'}"
+      "}";
   EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
                                &error));
   EXPECT_EQ("type_mismatch", error->GetFirstError()->GetCode());
   error.reset();
 
   schema_str = "{"
-  "'param1':[1,2.2]"
-  "}";
+      "'param1':[1,2.2]"
+      "}";
   EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
                                &error));
   EXPECT_EQ("type_mismatch", error->GetFirstError()->GetCode());
   error.reset();
 
   schema_str = "{"
-  "'param1':{'minimum':1, 'enum':[1,2,3]}"  // can't have min/max & enum.
-  "}";
+      "'param1':{'minimum':1, 'enum':[1,2,3]}"  // can't have min/max & enum.
+      "}";
   EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
                                &error));
   EXPECT_EQ("unexpected_parameter", error->GetFirstError()->GetCode());
   error.reset();
 
   schema_str = "{"
-  "'param1':{'maximum':1, 'blah':2}"  // 'blah' is unexpected.
-  "}";
+      "'param1':{'maximum':1, 'blah':2}"  // 'blah' is unexpected.
+      "}";
   EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
                                &error));
   EXPECT_EQ("unexpected_parameter", error->GetFirstError()->GetCode());
   error.reset();
+
+  schema_str = "{"
+      "'param1':{'enum':[1,2,3],'default':5}"  // 'default' must be 1, 2, or 3.
+      "}";
+  EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                               &error));
+  EXPECT_EQ("out_of_range", error->GetFirstError()->GetCode());
+  error.reset();
 }
diff --git a/buffet/commands/prop_types.cc b/buffet/commands/prop_types.cc
index 5a8b10f..37a6dc8 100644
--- a/buffet/commands/prop_types.cc
+++ b/buffet/commands/prop_types.cc
@@ -32,6 +32,9 @@
 }
 
 bool PropType::HasOverriddenAttributes() const {
+  if (default_.value && !default_.is_inherited)
+    return true;
+
   for (const auto& pair : constraints_) {
     if (pair.second->HasOverriddenAttributes())
       return true;
@@ -79,6 +82,14 @@
     if (!pair.second->AddToJsonDict(dict.get(), !full_schema, error))
       return std::unique_ptr<base::Value>();
   }
+
+  if (default_.value && (full_schema || !default_.is_inherited)) {
+    auto defval = default_.value->ToJson(error);
+    if (!defval)
+      return std::unique_ptr<base::Value>();
+    dict->Set(commands::attributes::kDefault, defval.release());
+  }
+
   return std::unique_ptr<base::Value>(dict.release());
 }
 
@@ -95,7 +106,14 @@
   }
   based_on_schema_ = (base_schema != nullptr);
   constraints_.clear();
-  std::set<std::string> processed_keys{commands::attributes::kType};
+  // Add the well-known object properties first (like "type", "displayName",
+  // "default") to the list of "processed" keys so we do not complain about them
+  // when we check for unknown/unexpected keys below.
+  std::set<std::string> processed_keys{
+      commands::attributes::kType,
+      commands::attributes::kDisplayName,
+      commands::attributes::kDefault,
+  };
   if (!ObjectSchemaFromJson(value, base_schema, &processed_keys, error))
     return false;
   if (base_schema) {
@@ -121,6 +139,31 @@
     iter.Advance();
   }
 
+  // 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.
+  const base::Value* defval = nullptr;  // Owned by value
+  if (value->GetWithoutPathExpansion(commands::attributes::kDefault, &defval)) {
+    std::shared_ptr<PropValue> prop_value = CreateValue();
+    if (!prop_value->FromJson(defval, error) ||
+        !ValidateValue(prop_value->GetValueAsAny(), error)) {
+      chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                                   errors::commands::kInvalidPropValue,
+                                   "Invalid value for property '%s'",
+                                   commands::attributes::kDefault);
+      return false;
+    }
+    default_.value = prop_value;
+    default_.is_inherited = false;
+  } else if (base_schema) {
+    // If we have the base schema, inherit the type's default value from it.
+    // It doesn't matter if the base schema actually has a default value
+    // specified or not. If it doesn't, then the current type definition will
+    // have no default value set either (|default_.value| is a shared_ptr to
+    // PropValue, which can be set to nullptr).
+    default_.value = base_schema->default_.value;
+    default_.is_inherited = true;
+  }
   return true;
 }
 
diff --git a/buffet/commands/schema_constants.cc b/buffet/commands/schema_constants.cc
index a26c355..22a4766 100644
--- a/buffet/commands/schema_constants.cc
+++ b/buffet/commands/schema_constants.cc
@@ -30,6 +30,7 @@
 namespace attributes {
 const char kType[] = "type";
 const char kDisplayName[] = "displayName";
+const char kDefault[] = "default";
 
 const char kNumeric_Min[] = "minimum";
 const char kNumeric_Max[] = "maximum";
diff --git a/buffet/commands/schema_constants.h b/buffet/commands/schema_constants.h
index 6b06e2f..1cfbc75 100644
--- a/buffet/commands/schema_constants.h
+++ b/buffet/commands/schema_constants.h
@@ -34,6 +34,7 @@
 // Command description JSON schema attributes.
 extern const char kType[];
 extern const char kDisplayName[];
+extern const char kDefault[];
 
 extern const char kNumeric_Min[];
 extern const char kNumeric_Max[];
diff --git a/buffet/commands/unittest_utils.cc b/buffet/commands/unittest_utils.cc
index 8d4c369..db8aa4a 100644
--- a/buffet/commands/unittest_utils.cc
+++ b/buffet/commands/unittest_utils.cc
@@ -21,7 +21,8 @@
   std::string json2(json);
   std::replace(json2.begin(), json2.end(), '\'', '"');
   base::Value* value = base::JSONReader::Read(json2);
-  base::DictionaryValue* dict;
+  CHECK(value) << "Failed to load JSON: " << json2;
+  base::DictionaryValue* dict = nullptr;
   value->GetAsDictionary(&dict);
   return std::unique_ptr<base::DictionaryValue>(dict);
 }