Resetting weave-release-1.1 to 03cd192

BUG=25936428
TEST=None

Change-Id: Id60979589bc8c2c15fc5e0126dc836c3ed8b5f8b
Reviewed-on: https://weave-review.googlesource.com/1683
Reviewed-by: Ben Henry <benhenry@google.com>
diff --git a/.gitignore b/.gitignore
index 3ee16a1..4011af4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,4 @@
-*.target.mk
 *~
 /out/
 /third_party/include
 /third_party/lib
-gomacc.lock
-Makefile
diff --git a/README b/README
index 9b357a0..bf9b70b 100644
--- a/README
+++ b/README
@@ -71,6 +71,7 @@
   libtool
   gyp
   libexpat1-dev
+  ninja-build
 
 For tests:
 
@@ -89,18 +90,18 @@
 ---------
 Everywhere below Debug can be replaced with Release.
 
-Generate build files:
+Generate ninja build files:
 
   gyp -I libweave_common.gypi --toplevel-dir=. --depth=. \
-      -f make libweave_standalone.gyp
+      -f ninja libweave_standalone.gyp
 
 Build library with tests:
 
-  make
+  ninja -C out/Debug
 
 Build library only:
 
-  make libweave
+  ninja -C out/Debug libweave
 
 Testing
 -------
@@ -129,3 +130,7 @@
   repo upload .
 
 Go to the url from the output of "repo upload" and add reviewers.
+
+Known Issues
+------------
+* No big-endian support. Pairing fails on big-endian hardware.
diff --git a/examples/build.sh b/examples/build.sh
index 262c56b..e4c412d 100755
--- a/examples/build.sh
+++ b/examples/build.sh
@@ -8,7 +8,7 @@
 
 cd $ROOT_DIR
 
-gyp -Ilibweave_common.gypi --toplevel-dir=. --depth=. -f make $DIR/daemon/examples.gyp
+gyp -Ilibweave_common.gypi --toplevel-dir=. --depth=. -f ninja $DIR/daemon/examples.gyp
 
 if [ -z "$BUILD_CONFIG" ]; then
    export BUILD_CONFIG=Debug
@@ -20,7 +20,7 @@
 fi
 
 export CORES=`cat /proc/cpuinfo | grep processor | wc -l`
-BUILDTYPE=$BUILD_CONFIG make -j $CORES $BUILD_TARGET || exit 1
+ninja -j $CORES -C out/${BUILD_CONFIG} $BUILD_TARGET || exit 1
 
 if [[ $BUILD_TARGET == *"libweave_testrunner"* ]]; then
   out/${BUILD_CONFIG}/libweave_testrunner --gtest_break_on_failure || exit 1
diff --git a/examples/daemon/ledflasher/ledflasher.cc b/examples/daemon/ledflasher/ledflasher.cc
index 9e4a9e1..38314f5 100644
--- a/examples/daemon/ledflasher/ledflasher.cc
+++ b/examples/daemon/ledflasher/ledflasher.cc
@@ -25,7 +25,7 @@
     device_ = device;
 
     device->AddStateDefinitionsFromJson(R"({
-      "_ledflasher": {"_leds": {"type": "array", "items": {"type": "boolean"}}}
+      "_ledflasher": {"_leds": {"items": "boolean"}}
     })");
 
     device->SetStatePropertiesFromJson(R"({
@@ -35,18 +35,16 @@
 
     device->AddCommandDefinitionsFromJson(R"({
       "_ledflasher": {
-        "_set":{
-          "minimalRole": "user",
-          "parameters": {
-            "_led": {"type": "integer", "minimum": 1, "maximum": 3},
-            "_on": {"type": "boolean"}
-          }
-        },
-        "_toggle":{
-          "minimalRole": "user",
-          "parameters": {
-            "_led": {"type": "integer", "minimum": 1, "maximum": 3}
-          }
+         "_set":{
+           "parameters": {
+             "_led": {"minimum": 1, "maximum": 3},
+             "_on": "boolean"
+           }
+         },
+         "_toggle":{
+           "parameters": {
+             "_led": {"minimum": 1, "maximum": 3}
+           }
         }
       }
     })");
@@ -66,10 +64,9 @@
       return;
     LOG(INFO) << "received command: " << cmd->GetName();
     int32_t led_index = 0;
-    const auto& params = cmd->GetParameters();
     bool cmd_value = false;
-    if (params.GetInteger("_led", &led_index) &&
-        params.GetBoolean("_on", &cmd_value)) {
+    if (cmd->GetParameters()->GetInteger("_led", &led_index) &&
+        cmd->GetParameters()->GetBoolean("_on", &cmd_value)) {
       // Display this command in terminal
       LOG(INFO) << cmd->GetName() << " _led: " << led_index
                 << ", _on: " << (cmd_value ? "true" : "false");
@@ -96,9 +93,8 @@
     if (!cmd)
       return;
     LOG(INFO) << "received command: " << cmd->GetName();
-    const auto& params = cmd->GetParameters();
     int32_t led_index = 0;
-    if (params.GetInteger("_led", &led_index)) {
+    if (cmd->GetParameters()->GetInteger("_led", &led_index)) {
       LOG(INFO) << cmd->GetName() << " _led: " << led_index;
       led_index--;
       led_status_[led_index] = ~led_status_[led_index];
diff --git a/examples/daemon/light/light.cc b/examples/daemon/light/light.cc
index 90680d2..d54de93 100644
--- a/examples/daemon/light/light.cc
+++ b/examples/daemon/light/light.cc
@@ -18,31 +18,31 @@
     device_ = device;
 
     device->AddStateDefinitionsFromJson(R"({
-      "onOff": {"state": {"type": "string", "enum": ["on", "standby"]}},
-      "brightness": {"brightness": {"type": "integer"}},
+      "onOff": {"state": ["on", "standby"]},
+      "brightness": {"brightness": "integer"},
       "colorXY": {
         "colorSetting": {
           "properties": {
-            "colorX": {"type": "number", "minimum": 0.0, "maximum": 1.0},
-            "colorY": {"type": "number", "minimum": 0.0, "maximum": 1.0}
+            "colorX": {"minimum": 0.0, "maximum": 1.0},
+            "colorY": {"minimum": 0.0, "maximum": 1.0}
           }
         },
         "colorCapRed": {
           "properties": {
-            "colorX": {"type": "number", "minimum": 0.0, "maximum": 1.0},
-            "colorY": {"type": "number", "minimum": 0.0, "maximum": 1.0}
+            "colorX": {"minimum": 0.0, "maximum": 1.0},
+            "colorY": {"minimum": 0.0, "maximum": 1.0}
           }
         },
         "colorCapGreen": {
           "properties": {
-            "colorX": {"type": "number", "minimum": 0.0, "maximum": 1.0},
-            "colorY": {"type": "number", "minimum": 0.0, "maximum": 1.0}
+            "colorX": {"minimum": 0.0, "maximum": 1.0},
+            "colorY": {"minimum": 0.0, "maximum": 1.0}
           }
         },
         "colorCapBlue": {
           "properties": {
-            "colorX": {"type": "number", "minimum": 0.0, "maximum": 1.0},
-            "colorY": {"type": "number", "minimum": 0.0, "maximum": 1.0}
+            "colorX": {"minimum": 0.0, "maximum": 1.0},
+            "colorY": {"minimum": 0.0, "maximum": 1.0}
           }
         }
       }
@@ -62,23 +62,21 @@
 
     device->AddCommandDefinitionsFromJson(R"({
       "onOff": {
-        "setConfig":{
-          "minimalRole": "user",
-          "parameters": {
-            "state": {"type": "string", "enum": ["on", "standby"]}
-          }
-        }
-      },
-      "brightness": {
-        "setConfig":{
-          "minimalRole": "user",
-          "parameters": {
-            "brightness": {
-              "type": "integer",
-              "minimum": 0,
-              "maximum": 100
-            }
-          }
+         "setConfig":{
+           "parameters": {
+             "state": ["on", "standby"]
+           }
+         }
+       },
+       "brightness": {
+         "setConfig":{
+           "parameters": {
+             "brightness": {
+               "type": "integer",
+               "minimum": 0,
+               "maximum": 100
+             }
+           }
         }
       },
       "_colorXY": {
@@ -90,16 +88,15 @@
               "properties": {
                 "_colorX": {
                   "type": "number",
-                  "minimum": 0.0,
-                  "maximum": 1.0
+                  "minimum": 0,
+                  "maximum": 1
                 },
                 "_colorY": {
                   "type": "number",
-                  "minimum": 0.0,
-                  "maximum": 1.0
+                  "minimum": 0,
+                  "maximum": 1
                 }
-              },
-              "additionalProperties": false
+              }
             }
           }
         }
@@ -122,9 +119,8 @@
     if (!cmd)
       return;
     LOG(INFO) << "received command: " << cmd->GetName();
-    const auto& params = cmd->GetParameters();
     int32_t brightness_value = 0;
-    if (params.GetInteger("brightness", &brightness_value)) {
+    if (cmd->GetParameters()->GetInteger("brightness", &brightness_value)) {
       // Display this command in terminal.
       LOG(INFO) << cmd->GetName() << " brightness: " << brightness_value;
 
@@ -146,9 +142,8 @@
     if (!cmd)
       return;
     LOG(INFO) << "received command: " << cmd->GetName();
-    const auto& params = cmd->GetParameters();
     std::string requested_state;
-    if (params.GetString("state", &requested_state)) {
+    if (cmd->GetParameters()->GetString("state", &requested_state)) {
       LOG(INFO) << cmd->GetName() << " state: " << requested_state;
 
       bool new_light_status = requested_state == "on";
@@ -172,9 +167,9 @@
     if (!cmd)
       return;
     LOG(INFO) << "received command: " << cmd->GetName();
-    const auto& params = cmd->GetParameters();
-    const base::DictionaryValue* colorXY = nullptr;
-    if (params.GetDictionary("_colorSetting", &colorXY)) {
+    auto params = cmd->GetParameters();
+    base::DictionaryValue* colorXY = nullptr;
+    if (params->GetDictionary("_colorSetting", &colorXY)) {
       bool updateState = false;
       double X = 0.0;
       double Y = 0.0;
diff --git a/examples/daemon/lock/lock.cc b/examples/daemon/lock/lock.cc
index 10bea00..3014fb1 100644
--- a/examples/daemon/lock/lock.cc
+++ b/examples/daemon/lock/lock.cc
@@ -35,12 +35,8 @@
 
     device->AddStateDefinitionsFromJson(R"({
       "lock": {
-        "lockedState": {
-          "type": "string",
-          "enum": ["locked", "unlocked", "partiallyLocked"]
-        },
-        "isLockingSupported": {"type": "boolean"}
-      }
+        "lockedState": ["locked", "unlocked", "partiallyLocked"],
+        "isLockingSupported": "boolean"}
     })");
 
     device->SetStatePropertiesFromJson(R"({
@@ -52,14 +48,13 @@
                                        nullptr);
 
     device->AddCommandDefinitionsFromJson(R"({
-      "lock": {
-        "setConfig":{
-          "minimalRole": "user",
-          "parameters": {
-            "lockedState": {"type": "string", "enum":["locked", "unlocked"]}
+        "lock": {
+          "setConfig":{
+            "parameters": {
+              "lockedState": ["locked", "unlocked"]
+            }
           }
         }
-      }
     })");
     device->AddCommandHandler("lock.setConfig",
                               base::Bind(&LockHandler::OnLockSetConfig,
@@ -72,9 +67,8 @@
     if (!cmd)
       return;
     LOG(INFO) << "received command: " << cmd->GetName();
-    const auto& params = cmd->GetParameters();
     std::string requested_state;
-    if (params.GetString("lockedState", &requested_state)) {
+    if (cmd->GetParameters()->GetString("lockedState", &requested_state)) {
       LOG(INFO) << cmd->GetName() << " state: " << requested_state;
 
       weave::lockstate::LockState new_lock_status;
diff --git a/examples/daemon/sample/sample.cc b/examples/daemon/sample/sample.cc
index 811e9fb..905a977 100644
--- a/examples/daemon/sample/sample.cc
+++ b/examples/daemon/sample/sample.cc
@@ -27,23 +27,27 @@
         "_hello": {
           "minimalRole": "user",
           "parameters": {
-            "_name": {"type": "string"}
-          }
+            "_name": "string"
+          },
+          "results": { "_reply": "string" }
         },
         "_ping": {
-          "minimalRole": "user"
+          "minimalRole": "user",
+          "results": {}
         },
         "_countdown": {
           "minimalRole": "user",
           "parameters": {
-            "_seconds": {"type": "integer", "minimum": 1, "maximum": 25}
-          }
+            "_seconds": {"minimum": 1, "maximum": 25}
+          },
+          "progress": { "_seconds_left": "integer"},
+          "results": {}
         }
       }
     })");
 
     device->AddStateDefinitionsFromJson(R"({
-      "_sample": {"_ping_count": {"type": "integer"}}
+      "_sample": {"_ping_count":"integer"}
     })");
 
     device->SetStatePropertiesFromJson(R"({
@@ -69,9 +73,8 @@
       return;
     LOG(INFO) << "received command: " << cmd->GetName();
 
-    const auto& params = cmd->GetParameters();
     std::string name;
-    if (!params.GetString("_name", &name)) {
+    if (!cmd->GetParameters()->GetString("_name", &name)) {
       weave::ErrorPtr error;
       weave::Error::AddTo(&error, FROM_HERE, "example",
                           "invalid_parameter_value", "Name is missing");
@@ -94,7 +97,7 @@
     base::DictionaryValue state;
     state.SetInteger("_sample._ping_count", ++ping_count_);
     device_->SetStateProperties(state, nullptr);
-    LOG(INFO) << "New state: " << device_->GetState();
+    LOG(INFO) << "New state: " << *device_->GetState();
 
     base::DictionaryValue result;
     cmd->Complete(result, nullptr);
@@ -108,9 +111,8 @@
       return;
     LOG(INFO) << "received command: " << cmd->GetName();
 
-    const auto& params = cmd->GetParameters();
     int seconds;
-    if (!params.GetInteger("_seconds", &seconds))
+    if (!cmd->GetParameters()->GetInteger("_seconds", &seconds))
       seconds = 10;
 
     LOG(INFO) << "starting countdown";
@@ -123,9 +125,8 @@
       return;
 
     if (seconds > 0) {
-      const auto& params = cmd->GetParameters();
       std::string todo;
-      params.GetString("_todo", &todo);
+      cmd->GetParameters()->GetString("_todo", &todo);
       LOG(INFO) << "countdown tick: " << seconds << " seconds left";
 
       base::DictionaryValue progress;
diff --git a/examples/daemon/speaker/speaker.cc b/examples/daemon/speaker/speaker.cc
index cd7d62f..32591f9 100644
--- a/examples/daemon/speaker/speaker.cc
+++ b/examples/daemon/speaker/speaker.cc
@@ -18,10 +18,10 @@
     device_ = device;
 
     device->AddStateDefinitionsFromJson(R"({
-      "onOff": {"state": {"type": "string", "enum": ["on", "standby"]}},
+      "onOff": {"state": ["on", "standby"]},
       "volume": {
-        "volume": {"type": "integer"},
-        "isMuted": {"type": "boolean"}
+        "volume": "integer",
+        "isMuted": "boolean"
       }
     })");
 
@@ -36,24 +36,22 @@
 
     device->AddCommandDefinitionsFromJson(R"({
       "onOff": {
-        "setConfig":{
-          "minimalRole": "user",
-          "parameters": {
-            "state": {"type": "string", "enum": ["on", "standby"]}
-          }
-        }
-      },
-      "volume": {
-        "setConfig":{
-          "minimalRole": "user",
-          "parameters": {
-            "volume": {
-              "type": "integer",
-              "minimum": 0,
-              "maximum": 100
-            },
-            "isMuted": {"type": "boolean"}
-          }
+         "setConfig":{
+           "parameters": {
+             "state": ["on", "standby"]
+           }
+         }
+       },
+       "volume": {
+         "setConfig":{
+           "parameters": {
+             "volume": {
+               "type": "integer",
+               "minimum": 0,
+               "maximum": 100
+             },
+             "isMuted": "boolean"
+           }
         }
       }
     })");
@@ -72,10 +70,9 @@
       return;
     LOG(INFO) << "received command: " << cmd->GetName();
 
-    const auto& params = cmd->GetParameters();
     // Handle volume parameter
     int32_t volume_value = 0;
-    if (params.GetInteger("volume", &volume_value)) {
+    if (cmd->GetParameters()->GetInteger("volume", &volume_value)) {
       // Display this command in terminal.
       LOG(INFO) << cmd->GetName() << " volume: " << volume_value;
 
@@ -89,7 +86,7 @@
 
     // Handle isMuted parameter
     bool isMuted_status = false;
-    if (params.GetBoolean("isMuted", &isMuted_status)) {
+    if (cmd->GetParameters()->GetBoolean("isMuted", &isMuted_status)) {
       // Display this command in terminal.
       LOG(INFO) << cmd->GetName() << " is "
                 << (isMuted_status ? "muted" : "not muted");
@@ -111,9 +108,8 @@
     if (!cmd)
       return;
     LOG(INFO) << "received command: " << cmd->GetName();
-    const auto& params = cmd->GetParameters();
     std::string requested_state;
-    if (params.GetString("state", &requested_state)) {
+    if (cmd->GetParameters()->GetString("state", &requested_state)) {
       LOG(INFO) << cmd->GetName() << " state: " << requested_state;
 
       bool new_speaker_status = requested_state == "on";
diff --git a/examples/prerequisites.sh b/examples/prerequisites.sh
index 1b27806..7358b71 100755
--- a/examples/prerequisites.sh
+++ b/examples/prerequisites.sh
@@ -20,6 +20,7 @@
   libnl-route-3-dev \
   libssl-dev \
   libtool \
+  ninja-build \
   || exit 1
 
 mkdir -p $ROOT_DIR/third_party/lib $ROOT_DIR/third_party/include 2> /dev/null
diff --git a/examples/provider/avahi_client.h b/examples/provider/avahi_client.h
index 7d9b932..0ca28db 100644
--- a/examples/provider/avahi_client.h
+++ b/examples/provider/avahi_client.h
@@ -34,7 +34,7 @@
   std::unique_ptr<AvahiThreadedPoll, decltype(&avahi_threaded_poll_free)>
       thread_pool_{nullptr, &avahi_threaded_poll_free};
 
-  std::unique_ptr< ::AvahiClient, decltype(&avahi_client_free)> client_{
+  std::unique_ptr<::AvahiClient, decltype(&avahi_client_free)> client_{
       nullptr, &avahi_client_free};
 
   std::unique_ptr<AvahiEntryGroup, decltype(&avahi_entry_group_free)> group_{
diff --git a/examples/provider/event_http_client.cc b/examples/provider/event_http_client.cc
index 1931547..03da97f 100644
--- a/examples/provider/event_http_client.cc
+++ b/examples/provider/event_http_client.cc
@@ -91,7 +91,7 @@
                                   const Headers& headers,
                                   const std::string& data,
                                   const SendRequestCallback& callback) {
-  evhttp_cmd_type method_id = EVHTTP_REQ_GET;
+  evhttp_cmd_type method_id;
   CHECK(weave::StringToEnum(weave::EnumToString(method), &method_id));
   EventPtr<evhttp_uri> http_uri{evhttp_uri_parse(url.c_str())};
   CHECK(http_uri);
diff --git a/examples/provider/event_http_server.cc b/examples/provider/event_http_server.cc
index ae8bbec..e0ecea6 100644
--- a/examples/provider/event_http_server.cc
+++ b/examples/provider/event_http_server.cc
@@ -164,14 +164,14 @@
 void HttpServerImpl::AddHttpRequestHandler(
     const std::string& path,
     const RequestHandlerCallback& callback) {
-  handlers_.insert(std::make_pair(path, callback));
+  handlers_.emplace(path, callback);
   evhttp_set_cb(httpd_.get(), path.c_str(), &ProcessRequestCallback, this);
 }
 
 void HttpServerImpl::AddHttpsRequestHandler(
     const std::string& path,
     const RequestHandlerCallback& callback) {
-  handlers_.insert(std::make_pair(path, callback));
+  handlers_.emplace(path, callback);
   evhttp_set_cb(httpsd_.get(), path.c_str(), &ProcessRequestCallback, this);
 }
 
diff --git a/examples/provider/event_task_runner.cc b/examples/provider/event_task_runner.cc
index 1d94612..c07e912 100644
--- a/examples/provider/event_task_runner.cc
+++ b/examples/provider/event_task_runner.cc
@@ -34,8 +34,7 @@
   flags |= (what & kClosed) ? EV_CLOSED : 0;
   event* ioevent = event_new(base_.get(), fd, flags, FdEventHandler, this);
   EventPtr<event> ioeventPtr{ioevent};
-  fd_task_map_.insert(
-      std::make_pair(fd, std::make_pair(std::move(ioeventPtr), task)));
+  fd_task_map_.emplace(fd, std::make_pair(std::move(ioeventPtr), task));
   event_add(ioevent, nullptr);
 }
 
diff --git a/include/weave/command.h b/include/weave/command.h
index 0a7d545..59a9305 100644
--- a/include/weave/command.h
+++ b/include/weave/command.h
@@ -40,13 +40,13 @@
   virtual Command::Origin GetOrigin() const = 0;
 
   // Returns the command parameters.
-  virtual const base::DictionaryValue& GetParameters() const = 0;
+  virtual std::unique_ptr<base::DictionaryValue> GetParameters() const = 0;
 
   // Returns the command progress.
-  virtual const base::DictionaryValue& GetProgress() const = 0;
+  virtual std::unique_ptr<base::DictionaryValue> GetProgress() const = 0;
 
   // Returns the command results.
-  virtual const base::DictionaryValue& GetResults() const = 0;
+  virtual std::unique_ptr<base::DictionaryValue> GetResults() const = 0;
 
   // Returns the command error.
   virtual const Error* GetError() const = 0;
@@ -79,7 +79,7 @@
   virtual bool Cancel(ErrorPtr* error) = 0;
 
  protected:
-  virtual ~Command() {}
+  virtual ~Command() = default;
 };
 
 }  // namespace weave
diff --git a/include/weave/device.h b/include/weave/device.h
index 5e86dfd..19012b5 100644
--- a/include/weave/device.h
+++ b/include/weave/device.h
@@ -32,7 +32,7 @@
 
 class Device {
  public:
-  virtual ~Device() {}
+  virtual ~Device() = default;
 
   // Returns reference the current settings.
   virtual const Settings& GetSettings() const = 0;
@@ -96,7 +96,7 @@
 
   // Returns value of the single property.
   // |name| is full property name, including package name. e.g. "base.network".
-  virtual const base::Value* GetStateProperty(
+  virtual std::unique_ptr<base::Value> GetStateProperty(
       const std::string& name) const = 0;
 
   // Sets value of the single property.
@@ -106,7 +106,7 @@
                                 ErrorPtr* error) = 0;
 
   // Returns aggregated state properties across all registered packages.
-  virtual const base::DictionaryValue& GetState() const = 0;
+  virtual std::unique_ptr<base::DictionaryValue> GetState() const = 0;
 
   // Returns current state of GCD connection.
   virtual GcdState GetGcdState() const = 0;
diff --git a/include/weave/provider/bluetooth.h b/include/weave/provider/bluetooth.h
index 6a47e92..e8f3b3c 100644
--- a/include/weave/provider/bluetooth.h
+++ b/include/weave/provider/bluetooth.h
@@ -14,7 +14,7 @@
   // TODO(rginda): Add bluetooth interface methods here.
 
  protected:
-  virtual ~Bluetooth() {}
+  virtual ~Bluetooth() = default;
 };
 
 }  // namespace provider
diff --git a/include/weave/provider/config_store.h b/include/weave/provider/config_store.h
index 1b7988f..53c1128 100644
--- a/include/weave/provider/config_store.h
+++ b/include/weave/provider/config_store.h
@@ -75,7 +75,7 @@
   virtual void SaveSettings(const std::string& settings) = 0;
 
  protected:
-  virtual ~ConfigStore() {}
+  virtual ~ConfigStore() = default;
 };
 
 }  // namespace provider
diff --git a/include/weave/provider/dns_service_discovery.h b/include/weave/provider/dns_service_discovery.h
index 37bf84b..fa9d50e 100644
--- a/include/weave/provider/dns_service_discovery.h
+++ b/include/weave/provider/dns_service_discovery.h
@@ -91,7 +91,7 @@
   virtual void StopPublishing(const std::string& service_type) = 0;
 
  protected:
-  virtual ~DnsServiceDiscovery() {}
+  virtual ~DnsServiceDiscovery() = default;
 };
 
 }  // namespace provider
diff --git a/include/weave/provider/http_client.h b/include/weave/provider/http_client.h
index bf01022..deb127a 100644
--- a/include/weave/provider/http_client.h
+++ b/include/weave/provider/http_client.h
@@ -78,7 +78,7 @@
     virtual std::string GetContentType() const = 0;
     virtual std::string GetData() const = 0;
 
-    virtual ~Response() {}
+    virtual ~Response() = default;
   };
 
   using Headers = std::vector<std::pair<std::string, std::string>>;
@@ -92,7 +92,7 @@
                            const SendRequestCallback& callback) = 0;
 
  protected:
-  virtual ~HttpClient() {}
+  virtual ~HttpClient() = default;
 };
 
 }  // namespace provider
diff --git a/include/weave/provider/http_server.h b/include/weave/provider/http_server.h
index 1c28d63..622785b 100644
--- a/include/weave/provider/http_server.h
+++ b/include/weave/provider/http_server.h
@@ -109,7 +109,7 @@
  public:
   class Request {
    public:
-    virtual ~Request() {}
+    virtual ~Request() = default;
 
     virtual std::string GetPath() const = 0;
     virtual std::string GetFirstHeader(const std::string& name) const = 0;
@@ -141,7 +141,7 @@
   virtual base::TimeDelta GetRequestTimeout() const = 0;
 
  protected:
-  virtual ~HttpServer() {}
+  virtual ~HttpServer() = default;
 };
 
 }  // namespace provider
diff --git a/include/weave/provider/network.h b/include/weave/provider/network.h
index 0fb147d..651155a 100644
--- a/include/weave/provider/network.h
+++ b/include/weave/provider/network.h
@@ -47,7 +47,7 @@
                              const OpenSslSocketCallback& callback) = 0;
 
  protected:
-  virtual ~Network() {}
+  virtual ~Network() = default;
 };
 
 }  // namespace provider
diff --git a/include/weave/provider/task_runner.h b/include/weave/provider/task_runner.h
index 095910b..0804a10 100644
--- a/include/weave/provider/task_runner.h
+++ b/include/weave/provider/task_runner.h
@@ -28,7 +28,7 @@
                                base::TimeDelta delay) = 0;
 
  protected:
-  virtual ~TaskRunner() {}
+  virtual ~TaskRunner() = default;
 };
 
 }  // namespace provider
diff --git a/include/weave/provider/wifi.h b/include/weave/provider/wifi.h
index 48ac651..111bf3c 100644
--- a/include/weave/provider/wifi.h
+++ b/include/weave/provider/wifi.h
@@ -29,7 +29,7 @@
   virtual void StopAccessPoint() = 0;
 
  protected:
-  virtual ~Wifi() {}
+  virtual ~Wifi() = default;
 };
 
 }  // namespace provider
diff --git a/include/weave/stream.h b/include/weave/stream.h
index 14cc7f0..19d38a0 100644
--- a/include/weave/stream.h
+++ b/include/weave/stream.h
@@ -15,7 +15,7 @@
 // Interface for async input streaming.
 class InputStream {
  public:
-  virtual ~InputStream() {}
+  virtual ~InputStream() = default;
 
   // Callback type for Read.
   using ReadCallback = base::Callback<void(size_t size, ErrorPtr error)>;
@@ -31,7 +31,7 @@
 // Interface for async input streaming.
 class OutputStream {
  public:
-  virtual ~OutputStream() {}
+  virtual ~OutputStream() = default;
 
   using WriteCallback = base::Callback<void(ErrorPtr error)>;
 
@@ -47,7 +47,7 @@
 // Interface for async bi-directional streaming.
 class Stream : public InputStream, public OutputStream {
  public:
-  ~Stream() override {}
+  ~Stream() override = default;
 
   // Cancels all pending read or write requests. Canceled operations must not
   // call any callbacks.
diff --git a/include/weave/test/mock_command.h b/include/weave/test/mock_command.h
index fe1a02a..2b1080e 100644
--- a/include/weave/test/mock_command.h
+++ b/include/weave/test/mock_command.h
@@ -25,9 +25,9 @@
   MOCK_CONST_METHOD0(GetCategory, const std::string&());
   MOCK_CONST_METHOD0(GetState, Command::State());
   MOCK_CONST_METHOD0(GetOrigin, Command::Origin());
-  MOCK_CONST_METHOD0(GetParameters, const base::DictionaryValue&());
-  MOCK_CONST_METHOD0(GetProgress, const base::DictionaryValue&());
-  MOCK_CONST_METHOD0(GetResults, const base::DictionaryValue&());
+  MOCK_CONST_METHOD0(MockGetParameters, const std::string&());
+  MOCK_CONST_METHOD0(MockGetProgress, const std::string&());
+  MOCK_CONST_METHOD0(MockGetResults, const std::string&());
   MOCK_CONST_METHOD0(GetError, const Error*());
   MOCK_METHOD2(SetProgress, bool(const base::DictionaryValue&, ErrorPtr*));
   MOCK_METHOD2(Complete, bool(const base::DictionaryValue&, ErrorPtr*));
@@ -35,6 +35,10 @@
   MOCK_METHOD2(SetError, bool(const Error*, ErrorPtr*));
   MOCK_METHOD2(Abort, bool(const Error*, ErrorPtr*));
   MOCK_METHOD1(Cancel, bool(ErrorPtr*));
+
+  std::unique_ptr<base::DictionaryValue> GetParameters() const override;
+  std::unique_ptr<base::DictionaryValue> GetProgress() const override;
+  std::unique_ptr<base::DictionaryValue> GetResults() const override;
 };
 
 }  // namespace test
diff --git a/include/weave/test/mock_device.h b/include/weave/test/mock_device.h
index e5063e7..f751f97 100644
--- a/include/weave/test/mock_device.h
+++ b/include/weave/test/mock_device.h
@@ -34,13 +34,13 @@
   MOCK_METHOD2(SetStatePropertiesFromJson, bool(const std::string&, ErrorPtr*));
   MOCK_METHOD2(SetStateProperties,
                bool(const base::DictionaryValue&, ErrorPtr*));
-  MOCK_CONST_METHOD1(GetStateProperty,
-                     const base::Value*(const std::string& name));
+  MOCK_CONST_METHOD1(MockGetStateProperty,
+                     base::Value*(const std::string& name));
   MOCK_METHOD3(SetStateProperty,
                bool(const std::string& name,
                     const base::Value& value,
                     ErrorPtr* error));
-  MOCK_CONST_METHOD0(GetState, const base::DictionaryValue&());
+  MOCK_CONST_METHOD0(MockGetState, base::DictionaryValue*());
   MOCK_CONST_METHOD0(GetGcdState, GcdState());
   MOCK_METHOD1(AddGcdStateChangedCallback,
                void(const GcdStateChangedCallback& callback));
@@ -50,6 +50,15 @@
   MOCK_METHOD2(AddPairingChangedCallbacks,
                void(const PairingBeginCallback& begin_callback,
                     const PairingEndCallback& end_callback));
+
+  // Gmock 1.7.0 does not work with unuque_ptr as return value.
+  std::unique_ptr<base::Value> GetStateProperty(
+      const std::string& name) const override {
+    return std::unique_ptr<base::Value>(MockGetStateProperty(name));
+  }
+  std::unique_ptr<base::DictionaryValue> GetState() const override {
+    return std::unique_ptr<base::DictionaryValue>(MockGetState());
+  }
 };
 
 }  // namespace test
diff --git a/libweave.gypi b/libweave.gypi
index a02f082..c32e99b 100644
--- a/libweave.gypi
+++ b/libweave.gypi
@@ -12,7 +12,12 @@
       'src/commands/command_instance.cc',
       'src/commands/command_manager.cc',
       'src/commands/command_queue.cc',
+      'src/commands/object_schema.cc',
+      'src/commands/prop_constraints.cc',
+      'src/commands/prop_types.cc',
+      'src/commands/prop_values.cc',
       'src/commands/schema_constants.cc',
+      'src/commands/schema_utils.cc',
       'src/config.cc',
       'src/data_encoding.cc',
       'src/device_manager.cc',
@@ -54,6 +59,7 @@
     'weave_test_sources': [
       'src/test/fake_stream.cc',
       'src/test/fake_task_runner.cc',
+      'src/test/mock_command.cc',
       'src/test/unittest_utils.cc',
     ],
     'weave_unittest_sources': [
@@ -65,6 +71,8 @@
       'src/commands/command_instance_unittest.cc',
       'src/commands/command_manager_unittest.cc',
       'src/commands/command_queue_unittest.cc',
+      'src/commands/object_schema_unittest.cc',
+      'src/commands/schema_utils_unittest.cc',
       'src/config_unittest.cc',
       'src/data_encoding_unittest.cc',
       'src/device_registration_info_unittest.cc',
diff --git a/libweave_common.gypi b/libweave_common.gypi
index 2e1fa10..49f7ff9 100644
--- a/libweave_common.gypi
+++ b/libweave_common.gypi
@@ -17,7 +17,7 @@
           '_DEBUG',
         ],
         'cflags': [
-          '-O0  ',
+          '-Og',
           '-g3',
         ],
       },
@@ -31,7 +31,6 @@
     ],
     'cflags!': ['-fPIE'],
     'cflags': [
-      '-fno-exceptions',
       '-fPIC',
       '-fvisibility=hidden',
       '-std=c++11',
@@ -48,10 +47,6 @@
       '-Wpointer-arith',
       '-Wwrite-strings',
     ],
-    'libraries': [
-      # 'library_dirs' does not work as expected with make files
-      '-Lthird_party/lib',
-    ],
     'library_dirs': ['third_party/lib']
   },
 }
diff --git a/libweave_standalone.gyp b/libweave_standalone.gyp
index d36d208..fd87f16 100644
--- a/libweave_standalone.gyp
+++ b/libweave_standalone.gyp
@@ -8,11 +8,10 @@
   'target_defaults': {
     'libraries': [
       '-lcrypto',
-      '-lexpat',
-      '-lgmock',
       '-lgtest',
+      '-lgmock',
+      '-lexpat',
       '-lpthread',
-      '-lrt',
     ],
   },
   'targets': [
diff --git a/src/backoff_entry.h b/src/backoff_entry.h
index 002fb8d..2df0d8a 100644
--- a/src/backoff_entry.h
+++ b/src/backoff_entry.h
@@ -57,7 +57,7 @@
   // Lifetime of policy must enclose lifetime of BackoffEntry. The
   // pointer must be valid but is not dereferenced during construction.
   explicit BackoffEntry(const Policy* const policy);
-  virtual ~BackoffEntry() {}
+  virtual ~BackoffEntry() = default;
 
   // Inform this item that a request for the network resource it is
   // tracking was made, and whether it failed or succeeded.
diff --git a/src/base_api_handler.cc b/src/base_api_handler.cc
index 1423dd1..c3aa616 100644
--- a/src/base_api_handler.cc
+++ b/src/base_api_handler.cc
@@ -42,31 +42,20 @@
       "updateBaseConfiguration": {
         "minimalRole": "manager",
         "parameters": {
-          "localAnonymousAccessMaxRole": {
-            "enum": [ "none", "viewer", "user" ],
-            "type": "string"
-          },
-          "localDiscoveryEnabled": {
-            "type": "boolean"
-          },
-          "localPairingEnabled": {
-            "type": "boolean"
-          }
-        }
+          "localDiscoveryEnabled": "boolean",
+          "localAnonymousAccessMaxRole": [ "none", "viewer", "user" ],
+          "localPairingEnabled": "boolean"
+        },
+        "results": {}
       },
       "updateDeviceInfo": {
         "minimalRole": "manager",
         "parameters": {
-          "description": {
-            "type": "string"
-          },
-          "location": {
-            "type": "string"
-          },
-          "name": {
-            "type": "string"
-          }
-        }
+          "description": "string",
+          "name": "string",
+          "location": "string"
+        },
+        "results": {}
       }
     }
   })");
@@ -99,10 +88,10 @@
   bool discovery_enabled{settings.local_discovery_enabled};
   bool pairing_enabled{settings.local_pairing_enabled};
 
-  const auto& parameters = command->GetParameters();
-  parameters.GetString("localAnonymousAccessMaxRole", &anonymous_access_role);
-  parameters.GetBoolean("localDiscoveryEnabled", &discovery_enabled);
-  parameters.GetBoolean("localPairingEnabled", &pairing_enabled);
+  auto parameters = command->GetParameters();
+  parameters->GetString("localAnonymousAccessMaxRole", &anonymous_access_role);
+  parameters->GetBoolean("localDiscoveryEnabled", &discovery_enabled);
+  parameters->GetBoolean("localPairingEnabled", &pairing_enabled);
 
   AuthScope auth_scope{AuthScope::kNone};
   if (!StringToEnum(anonymous_access_role, &auth_scope)) {
@@ -144,10 +133,10 @@
   std::string description{settings.description};
   std::string location{settings.location};
 
-  const auto& parameters = command->GetParameters();
-  parameters.GetString("name", &name);
-  parameters.GetString("description", &description);
-  parameters.GetString("location", &location);
+  auto parameters = command->GetParameters();
+  parameters->GetString("name", &name);
+  parameters->GetString("description", &description);
+  parameters->GetString("location", &location);
 
   device_info_->UpdateDeviceInfo(name, description, location);
   command->Complete({}, nullptr);
diff --git a/src/base_api_handler_unittest.cc b/src/base_api_handler_unittest.cc
index 15a575a..a025e44 100644
--- a/src/base_api_handler_unittest.cc
+++ b/src/base_api_handler_unittest.cc
@@ -10,9 +10,9 @@
 #include <weave/provider/test/mock_config_store.h>
 #include <weave/provider/test/mock_http_client.h>
 #include <weave/test/mock_device.h>
-#include <weave/test/unittest_utils.h>
 
 #include "src/commands/command_manager.h"
+#include "src/commands/unittest_utils.h"
 #include "src/config.h"
 #include "src/device_registration_info.h"
 #include "src/states/mock_state_change_queue_interface.h"
@@ -31,7 +31,7 @@
 class BaseApiHandlerTest : public ::testing::Test {
  protected:
   void SetUp() override {
-    EXPECT_CALL(mock_state_change_queue_, MockNotifyPropertiesUpdated(_, _))
+    EXPECT_CALL(mock_state_change_queue_, NotifyPropertiesUpdated(_, _))
         .WillRepeatedly(Return(true));
 
     command_manager_ = std::make_shared<CommandManager>();
@@ -84,8 +84,7 @@
   }
 
   std::unique_ptr<base::DictionaryValue> GetBaseState() {
-    std::unique_ptr<base::DictionaryValue> state{
-        state_manager_->GetState().DeepCopy()};
+    auto state = state_manager_->GetState();
     std::set<std::string> result;
     for (base::DictionaryValue::Iterator it{*state}; !it.IsAtEnd();
          it.Advance()) {
@@ -108,38 +107,39 @@
 
 TEST_F(BaseApiHandlerTest, Initialization) {
   auto command_defs =
-      command_manager_->GetCommandDictionary().GetCommandsAsJson(nullptr);
+      command_manager_->GetCommandDictionary().GetCommandsAsJson(
+          [](const CommandDefinition* def) { return true; }, true, nullptr);
 
   auto expected = R"({
     "base": {
       "updateBaseConfiguration": {
-        "minimalRole": "manager",
-        "parameters": {
-          "localAnonymousAccessMaxRole": {
-            "enum": [ "none", "viewer", "user" ],
-            "type": "string"
-          },
-          "localDiscoveryEnabled": {
-            "type": "boolean"
-          },
-          "localPairingEnabled": {
-            "type": "boolean"
-          }
-        }
+         "minimalRole": "manager",
+         "parameters": {
+            "localAnonymousAccessMaxRole": {
+               "enum": [ "none", "viewer", "user" ],
+               "type": "string"
+            },
+            "localDiscoveryEnabled": {
+               "type": "boolean"
+            },
+            "localPairingEnabled": {
+               "type": "boolean"
+            }
+         }
       },
       "updateDeviceInfo": {
-        "minimalRole": "manager",
-        "parameters": {
-          "description": {
-            "type": "string"
-          },
-          "location": {
-            "type": "string"
-          },
-          "name": {
-            "type": "string"
-          }
-        }
+         "minimalRole": "manager",
+         "parameters": {
+            "description": {
+               "type": "string"
+            },
+            "location": {
+               "type": "string"
+            },
+            "name": {
+               "type": "string"
+            }
+         }
       }
     }
   })";
diff --git a/src/commands/cloud_command_proxy.cc b/src/commands/cloud_command_proxy.cc
index 3d472c7..9ec3d3e 100644
--- a/src/commands/cloud_command_proxy.cc
+++ b/src/commands/cloud_command_proxy.cc
@@ -9,6 +9,8 @@
 #include <weave/provider/task_runner.h>
 
 #include "src/commands/command_instance.h"
+#include "src/commands/prop_constraints.h"
+#include "src/commands/prop_types.h"
 #include "src/commands/schema_constants.h"
 #include "src/utils.h"
 
@@ -43,7 +45,7 @@
 void CloudCommandProxy::OnResultsChanged() {
   std::unique_ptr<base::DictionaryValue> patch{new base::DictionaryValue};
   patch->Set(commands::attributes::kCommand_Results,
-             command_instance_->GetResults().CreateDeepCopy());
+             command_instance_->GetResults().release());
   QueueCommandUpdate(std::move(patch));
 }
 
@@ -57,7 +59,7 @@
 void CloudCommandProxy::OnProgressChanged() {
   std::unique_ptr<base::DictionaryValue> patch{new base::DictionaryValue};
   patch->Set(commands::attributes::kCommand_Progress,
-             command_instance_->GetProgress().CreateDeepCopy());
+             command_instance_->GetProgress().release());
   QueueCommandUpdate(std::move(patch));
 }
 
diff --git a/src/commands/cloud_command_proxy_unittest.cc b/src/commands/cloud_command_proxy_unittest.cc
index a65a967..0c04592 100644
--- a/src/commands/cloud_command_proxy_unittest.cc
+++ b/src/commands/cloud_command_proxy_unittest.cc
@@ -10,10 +10,10 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <weave/provider/test/fake_task_runner.h>
-#include <weave/test/unittest_utils.h>
 
 #include "src/commands/command_dictionary.h"
 #include "src/commands/command_instance.h"
+#include "src/commands/unittest_utils.h"
 #include "src/states/mock_state_change_queue_interface.h"
 
 using testing::_;
@@ -94,7 +94,7 @@
       }
     })");
     CHECK(json.get());
-    CHECK(command_dictionary_.LoadCommands(*json, nullptr))
+    CHECK(command_dictionary_.LoadCommands(*json, nullptr, nullptr))
         << "Failed to parse test command dictionary";
 
     CreateCommandInstance();
diff --git a/src/commands/cloud_command_update_interface.h b/src/commands/cloud_command_update_interface.h
index ed3aa7a..9538960 100644
--- a/src/commands/cloud_command_update_interface.h
+++ b/src/commands/cloud_command_update_interface.h
@@ -21,7 +21,7 @@
                              const DoneCallback& callback) = 0;
 
  protected:
-  virtual ~CloudCommandUpdateInterface() {}
+  virtual ~CloudCommandUpdateInterface() = default;
 };
 
 }  // namespace weave
diff --git a/src/commands/command_definition.cc b/src/commands/command_definition.cc
index dbae630..d7ebc83 100644
--- a/src/commands/command_definition.cc
+++ b/src/commands/command_definition.cc
@@ -28,31 +28,61 @@
 LIBWEAVE_EXPORT EnumToStringMap<UserRole>::EnumToStringMap()
     : EnumToStringMap(kMap) {}
 
-CommandDefinition::CommandDefinition(const base::DictionaryValue& definition,
-                                     UserRole minimal_role)
-    : minimal_role_{minimal_role} {
-  definition_.MergeDictionary(&definition);
+bool CommandDefinition::Visibility::FromString(const std::string& str,
+                                               ErrorPtr* error) {
+  // This special case is useful for places where we want to make a command
+  // to ALL clients, even if new clients are added in the future.
+  if (str == commands::attributes::kCommand_Visibility_All) {
+    local = true;
+    cloud = true;
+    return true;
+  }
+
+  // Clear any bits first.
+  local = false;
+  cloud = false;
+  if (str == commands::attributes::kCommand_Visibility_None)
+    return true;
+
+  for (const std::string& value : Split(str, ",", true, true)) {
+    if (value == commands::attributes::kCommand_Visibility_Local) {
+      local = true;
+    } else if (value == commands::attributes::kCommand_Visibility_Cloud) {
+      cloud = true;
+    } else {
+      Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                         errors::commands::kInvalidPropValue,
+                         "Invalid command visibility value '%s'",
+                         value.c_str());
+      return false;
+    }
+  }
+  return true;
 }
 
-std::unique_ptr<CommandDefinition> CommandDefinition::FromJson(
-    const base::DictionaryValue& dict, ErrorPtr* error) {
-  std::unique_ptr<CommandDefinition> definition;
-  // Validate the 'minimalRole' value if present. That's the only thing we
-  // care about so far.
-  std::string value;
-  UserRole minimal_role;
-  if (dict.GetString(commands::attributes::kCommand_Role, &value)) {
-    if (!StringToEnum(value, &minimal_role)) {
-      Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
-                          errors::commands::kInvalidPropValue,
-                          "Invalid role: '%s'", value.c_str());
-      return definition;
-    }
-  } else {
-    minimal_role = UserRole::kUser;
-  }
-  definition.reset(new CommandDefinition{dict, minimal_role});
-  return definition;
+std::string CommandDefinition::Visibility::ToString() const {
+  if (local && cloud)
+    return commands::attributes::kCommand_Visibility_All;
+  if (!local && !cloud)
+    return commands::attributes::kCommand_Visibility_None;
+  if (local)
+    return commands::attributes::kCommand_Visibility_Local;
+  return commands::attributes::kCommand_Visibility_Cloud;
+}
+
+CommandDefinition::CommandDefinition(
+    std::unique_ptr<const ObjectSchema> parameters,
+    std::unique_ptr<const ObjectSchema> progress,
+    std::unique_ptr<const ObjectSchema> results)
+    : parameters_{std::move(parameters)},
+      progress_{std::move(progress)},
+      results_{std::move(results)} {
+  // Set to be available to all clients by default.
+  visibility_ = Visibility::GetAll();
+}
+
+void CommandDefinition::SetVisibility(const Visibility& visibility) {
+  visibility_ = visibility;
 }
 
 }  // namespace weave
diff --git a/src/commands/command_definition.h b/src/commands/command_definition.h
index da02bf5..3bcc07f 100644
--- a/src/commands/command_definition.h
+++ b/src/commands/command_definition.h
@@ -9,8 +9,8 @@
 #include <string>
 
 #include <base/macros.h>
-#include <base/values.h>
-#include <weave/error.h>
+
+#include "src/commands/object_schema.h"
 
 namespace weave {
 
@@ -25,19 +25,55 @@
 // describing the command parameter types and constraints.
 class CommandDefinition final {
  public:
-  // Factory method to construct a command definition from a JSON dictionary.
-  static std::unique_ptr<CommandDefinition> FromJson(
-      const base::DictionaryValue& dict, ErrorPtr* error);
-  const base::DictionaryValue& ToJson() const { return definition_; }
+  struct Visibility {
+    Visibility() = default;
+    Visibility(bool is_local, bool is_cloud)
+        : local{is_local}, cloud{is_cloud} {}
+
+    // Converts a comma-separated string of visibility identifiers into the
+    // Visibility bitset (|str| is a string like "local,cloud").
+    // Special string value "all" is treated as a list of every possible
+    // visibility values and "none" to have all the bits cleared.
+    bool FromString(const std::string& str, ErrorPtr* error);
+
+    // Converts the visibility bitset to a string.
+    std::string ToString() const;
+
+    static Visibility GetAll() { return Visibility{true, true}; }
+    static Visibility GetLocal() { return Visibility{true, false}; }
+    static Visibility GetCloud() { return Visibility{false, true}; }
+    static Visibility GetNone() { return Visibility{false, false}; }
+
+    bool local{false};  // Command is available to local clients.
+    bool cloud{false};  // Command is available to cloud clients.
+  };
+
+  CommandDefinition(std::unique_ptr<const ObjectSchema> parameters,
+                    std::unique_ptr<const ObjectSchema> progress,
+                    std::unique_ptr<const ObjectSchema> results);
+
+  // Gets the object schema for command parameters.
+  const ObjectSchema* GetParameters() const { return parameters_.get(); }
+  // Gets the object schema for command progress.
+  const ObjectSchema* GetProgress() const { return progress_.get(); }
+  // Gets the object schema for command results.
+  const ObjectSchema* GetResults() const { return results_.get(); }
+  // Returns the command visibility.
+  const Visibility& GetVisibility() const { return visibility_; }
+  // Changes the command visibility.
+  void SetVisibility(const Visibility& visibility);
   // Returns the role required to execute command.
   UserRole GetMinimalRole() const { return minimal_role_; }
+  // Changes the role required to execute command.
+  void SetMinimalRole(UserRole minimal_role) { minimal_role_ = minimal_role; }
 
  private:
-  CommandDefinition(const base::DictionaryValue& definition,
-                    UserRole minimal_role);
-
-  base::DictionaryValue definition_;
-  UserRole minimal_role_;
+  std::unique_ptr<const ObjectSchema> parameters_;  // Command parameters def.
+  std::unique_ptr<const ObjectSchema> progress_;    // Command progress def.
+  std::unique_ptr<const ObjectSchema> results_;     // Command results def.
+  Visibility visibility_;  // Available to all by default.
+  // Minimal role required to execute command.
+  UserRole minimal_role_{UserRole::kUser};
 
   DISALLOW_COPY_AND_ASSIGN(CommandDefinition);
 };
diff --git a/src/commands/command_definition_unittest.cc b/src/commands/command_definition_unittest.cc
index 867d48f..77b2754 100644
--- a/src/commands/command_definition_unittest.cc
+++ b/src/commands/command_definition_unittest.cc
@@ -5,47 +5,88 @@
 #include "src/commands/command_definition.h"
 
 #include <gtest/gtest.h>
-#include <weave/test/unittest_utils.h>
 
 namespace weave {
 
-using test::CreateDictionaryValue;
-
-TEST(CommandDefinition, DefaultRole) {
-  auto params = CreateDictionaryValue(R"({
-    'parameters': {
-      'height': 'integer',
-      'jumpType': ['_withAirFlip', '_withSpin', '_withKick']
-    },
-    'progress': {'progress': 'integer'},
-    'results': {'testResult': 'integer'}
-  })");
-  auto def = CommandDefinition::FromJson(*params, nullptr);
-  EXPECT_EQ(UserRole::kUser, def->GetMinimalRole());
+TEST(CommandVisibility, DefaultConstructor) {
+  CommandDefinition::Visibility visibility;
+  EXPECT_FALSE(visibility.local);
+  EXPECT_FALSE(visibility.cloud);
 }
 
-TEST(CommandDefinition, SpecifiedRole) {
-  auto params = CreateDictionaryValue(R"({
-    'parameters': {},
-    'progress': {},
-    'results': {},
-    'minimalRole': 'owner'
-  })");
-  auto def = CommandDefinition::FromJson(*params, nullptr);
-  EXPECT_EQ(UserRole::kOwner, def->GetMinimalRole());
+TEST(CommandVisibility, InitialState) {
+  auto visibility = CommandDefinition::Visibility::GetAll();
+  EXPECT_TRUE(visibility.local);
+  EXPECT_TRUE(visibility.cloud);
+
+  visibility = CommandDefinition::Visibility::GetLocal();
+  EXPECT_TRUE(visibility.local);
+  EXPECT_FALSE(visibility.cloud);
+
+  visibility = CommandDefinition::Visibility::GetCloud();
+  EXPECT_FALSE(visibility.local);
+  EXPECT_TRUE(visibility.cloud);
+
+  visibility = CommandDefinition::Visibility::GetNone();
+  EXPECT_FALSE(visibility.local);
+  EXPECT_FALSE(visibility.cloud);
 }
 
-TEST(CommandDefinition, IncorrectRole) {
-  auto params = CreateDictionaryValue(R"({
-    'parameters': {},
-    'progress': {},
-    'results': {},
-    'minimalRole': 'foo'
-  })");
+TEST(CommandVisibility, FromString) {
+  CommandDefinition::Visibility visibility;
+
+  ASSERT_TRUE(visibility.FromString("local", nullptr));
+  EXPECT_TRUE(visibility.local);
+  EXPECT_FALSE(visibility.cloud);
+
+  ASSERT_TRUE(visibility.FromString("cloud", nullptr));
+  EXPECT_FALSE(visibility.local);
+  EXPECT_TRUE(visibility.cloud);
+
+  ASSERT_TRUE(visibility.FromString("cloud,local", nullptr));
+  EXPECT_TRUE(visibility.local);
+  EXPECT_TRUE(visibility.cloud);
+
+  ASSERT_TRUE(visibility.FromString("none", nullptr));
+  EXPECT_FALSE(visibility.local);
+  EXPECT_FALSE(visibility.cloud);
+
+  ASSERT_TRUE(visibility.FromString("all", nullptr));
+  EXPECT_TRUE(visibility.local);
+  EXPECT_TRUE(visibility.cloud);
+
+  ASSERT_TRUE(visibility.FromString("", nullptr));
+  EXPECT_FALSE(visibility.local);
+  EXPECT_FALSE(visibility.cloud);
+
   ErrorPtr error;
-  auto def = CommandDefinition::FromJson(*params, &error);
-  EXPECT_EQ(nullptr, def.get());
+  ASSERT_FALSE(visibility.FromString("cloud,all", &error));
   EXPECT_EQ("invalid_parameter_value", error->GetCode());
 }
 
+TEST(CommandVisibility, ToString) {
+  EXPECT_EQ("none", CommandDefinition::Visibility::GetNone().ToString());
+  EXPECT_EQ("local", CommandDefinition::Visibility::GetLocal().ToString());
+  EXPECT_EQ("cloud", CommandDefinition::Visibility::GetCloud().ToString());
+  EXPECT_EQ("all", CommandDefinition::Visibility::GetAll().ToString());
+}
+
+TEST(CommandDefinition, Test) {
+  std::unique_ptr<const ObjectSchema> params{ObjectSchema::Create()};
+  std::unique_ptr<const ObjectSchema> progress{ObjectSchema::Create()};
+  std::unique_ptr<const ObjectSchema> results{ObjectSchema::Create()};
+  const ObjectSchema* param_ptr = params.get();
+  const ObjectSchema* progress_ptr = progress.get();
+  const ObjectSchema* results_ptr = results.get();
+  CommandDefinition def{std::move(params), std::move(progress),
+                        std::move(results)};
+  EXPECT_EQ(param_ptr, def.GetParameters());
+  EXPECT_EQ(progress_ptr, def.GetProgress());
+  EXPECT_EQ(results_ptr, def.GetResults());
+  EXPECT_EQ("all", def.GetVisibility().ToString());
+
+  def.SetVisibility(CommandDefinition::Visibility::GetLocal());
+  EXPECT_EQ("local", def.GetVisibility().ToString());
+}
+
 }  // namespace weave
diff --git a/src/commands/command_dictionary.cc b/src/commands/command_dictionary.cc
index 516961f..053e7aa 100644
--- a/src/commands/command_dictionary.cc
+++ b/src/commands/command_dictionary.cc
@@ -14,6 +14,7 @@
 namespace weave {
 
 bool CommandDictionary::LoadCommands(const base::DictionaryValue& json,
+                                     const CommandDictionary* base_commands,
                                      ErrorPtr* error) {
   CommandMap new_defs;
 
@@ -53,17 +54,89 @@
       // Construct the compound command name as "pkg_name.cmd_name".
       std::string full_command_name = Join(".", package_name, command_name);
 
-      auto command_def = CommandDefinition::FromJson(*command_def_json, error);
-      if (!command_def) {
-        Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
-                           errors::commands::kInvalidMinimalRole,
-                           "Error parsing command '%s'",
-                           full_command_name.c_str());
-        return false;
+      const ObjectSchema* base_parameters_def = nullptr;
+      const ObjectSchema* base_progress_def = nullptr;
+      const ObjectSchema* base_results_def = nullptr;
+      // By default make it available to all clients.
+      auto visibility = CommandDefinition::Visibility::GetAll();
+      UserRole minimal_role{UserRole::kUser};
+      if (base_commands) {
+        auto cmd = base_commands->FindCommand(full_command_name);
+        if (cmd) {
+          base_parameters_def = cmd->GetParameters();
+          base_progress_def = cmd->GetProgress();
+          base_results_def = cmd->GetResults();
+          visibility = cmd->GetVisibility();
+          minimal_role = cmd->GetMinimalRole();
+        }
+
+        // If the base command dictionary was provided but the command was not
+        // found in it, this must be a custom (vendor) command. GCD spec states
+        // that all custom command names must begin with "_". Let's enforce
+        // this rule here.
+        if (!cmd) {
+          if (command_name.front() != '_') {
+            Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                               errors::commands::kInvalidCommandName,
+                               "The name of custom command '%s' in package '%s'"
+                               " must start with '_'",
+                               command_name.c_str(), package_name.c_str());
+            return false;
+          }
+        }
       }
 
-      new_defs.insert(
-          std::make_pair(full_command_name, std::move(command_def)));
+      auto parameters_schema = BuildObjectSchema(
+          command_def_json, commands::attributes::kCommand_Parameters,
+          base_parameters_def, full_command_name, error);
+      if (!parameters_schema)
+        return false;
+
+      auto progress_schema = BuildObjectSchema(
+          command_def_json, commands::attributes::kCommand_Progress,
+          base_progress_def, full_command_name, error);
+      if (!progress_schema)
+        return false;
+
+      auto results_schema = BuildObjectSchema(
+          command_def_json, commands::attributes::kCommand_Results,
+          base_results_def, full_command_name, error);
+      if (!results_schema)
+        return false;
+
+      std::string value;
+      if (command_def_json->GetString(commands::attributes::kCommand_Visibility,
+                                      &value)) {
+        if (!visibility.FromString(value, error)) {
+          Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                             errors::commands::kInvalidCommandVisibility,
+                             "Error parsing command '%s'",
+                             full_command_name.c_str());
+          return false;
+        }
+      }
+
+      if (command_def_json->GetString(commands::attributes::kCommand_Role,
+                                      &value)) {
+        if (!StringToEnum(value, &minimal_role)) {
+          Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                             errors::commands::kInvalidPropValue,
+                             "Invalid role: '%s'", value.c_str());
+          Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                             errors::commands::kInvalidMinimalRole,
+                             "Error parsing command '%s'",
+                             full_command_name.c_str());
+          return false;
+        }
+      }
+
+      std::unique_ptr<CommandDefinition> command_def{new CommandDefinition{
+          std::move(parameters_schema), std::move(progress_schema),
+          std::move(results_schema)}};
+      command_def->SetVisibility(visibility);
+      command_def->SetMinimalRole(minimal_role);
+      new_defs.emplace(full_command_name, std::move(command_def));
+
       command_iter.Advance();
     }
     package_iter.Advance();
@@ -82,14 +155,53 @@
 
   // Insert new definitions into the global map.
   for (auto& pair : new_defs)
-    definitions_.insert(std::make_pair(pair.first, std::move(pair.second)));
+    definitions_.emplace(pair.first, std::move(pair.second));
   return true;
 }
 
+std::unique_ptr<ObjectSchema> CommandDictionary::BuildObjectSchema(
+    const base::DictionaryValue* command_def_json,
+    const char* property_name,
+    const ObjectSchema* base_def,
+    const std::string& command_name,
+    ErrorPtr* error) {
+  auto object_schema = ObjectSchema::Create();
+
+  const base::DictionaryValue* schema_def = nullptr;
+  if (!command_def_json->GetDictionaryWithoutPathExpansion(property_name,
+                                                           &schema_def)) {
+    if (base_def)
+      return base_def->Clone();
+    return object_schema;
+  }
+
+  if (!object_schema->FromJson(schema_def, base_def, error)) {
+    Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                       errors::commands::kInvalidObjectSchema,
+                       "Invalid definition for command '%s'",
+                       command_name.c_str());
+    return {};
+  }
+
+  return object_schema;
+}
+
 std::unique_ptr<base::DictionaryValue> CommandDictionary::GetCommandsAsJson(
+    const std::function<bool(const CommandDefinition*)>& filter,
+    bool full_schema,
     ErrorPtr* error) const {
   std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
   for (const auto& pair : definitions_) {
+    // Check if the command definition has the desired visibility.
+    // If not, then skip it.
+    if (!filter(pair.second.get()))
+      continue;
+
+    std::unique_ptr<base::DictionaryValue> parameters =
+        pair.second->GetParameters()->ToJson(full_schema, true);
+    CHECK(parameters);
+    // Progress and results are not part of public commandDefs.
+
     auto parts = SplitAtFirst(pair.first, ".", true);
     const std::string& package_name = parts.first;
     const std::string& command_name = parts.second;
@@ -101,8 +213,12 @@
       package = new base::DictionaryValue;
       dict->SetWithoutPathExpansion(package_name, package);
     }
-    package->SetWithoutPathExpansion(command_name,
-                                     pair.second->ToJson().DeepCopy());
+    base::DictionaryValue* command_def = new base::DictionaryValue;
+    command_def->Set(commands::attributes::kCommand_Parameters,
+                     parameters.release());
+    command_def->SetString(commands::attributes::kCommand_Role,
+                           EnumToString(pair.second->GetMinimalRole()));
+    package->SetWithoutPathExpansion(command_name, command_def);
   }
   return dict;
 }
diff --git a/src/commands/command_dictionary.h b/src/commands/command_dictionary.h
index 03e080a..8d3d45c 100644
--- a/src/commands/command_dictionary.h
+++ b/src/commands/command_dictionary.h
@@ -23,6 +23,8 @@
 
 namespace weave {
 
+class ObjectSchema;
+
 // CommandDictionary is a wrapper around a map of command name and the
 // corresponding command definition schema. The command name (the key in
 // the map) is a compound name in a form of "package_name.command_name",
@@ -35,15 +37,25 @@
   // Loads command definitions from a JSON object. This is done at the daemon
   // startup and whenever a device daemon decides to update its command list.
   // |json| is a JSON dictionary that describes the complete commands. Optional
-  // Returns false on failure and |error| provides additional error information
-  // when provided.
+  // |base_commands| parameter specifies the definition of standard GCD commands
+  // for parameter schema validation. Can be set to nullptr if no validation is
+  // needed. Returns false on failure and |error| provides additional error
+  // information when provided.
   bool LoadCommands(const base::DictionaryValue& json,
+                    const CommandDictionary* base_commands,
                     ErrorPtr* error);
   // Converts all the command definitions to a JSON object for CDD/Device
   // draft.
+  // |filter| is a predicate used to filter out the command definitions to
+  // be returned by this method. Only command definitions for which the
+  // predicate returns true will be included in the resulting JSON.
+  // |full_schema| specifies whether full command definitions must be generated
+  // (true) for CDD or only overrides from the base schema (false).
   // Returns empty unique_ptr in case of an error and fills in the additional
   // error details in |error|.
   std::unique_ptr<base::DictionaryValue> GetCommandsAsJson(
+      const std::function<bool(const CommandDefinition*)>& filter,
+      bool full_schema,
       ErrorPtr* error) const;
   // Returns the number of command definitions in the dictionary.
   size_t GetSize() const { return definitions_.size(); }
@@ -58,6 +70,13 @@
  private:
   using CommandMap = std::map<std::string, std::unique_ptr<CommandDefinition>>;
 
+  std::unique_ptr<ObjectSchema> BuildObjectSchema(
+      const base::DictionaryValue* command_def_json,
+      const char* property_name,
+      const ObjectSchema* base_def,
+      const std::string& command_name,
+      ErrorPtr* error);
+
   CommandMap definitions_;  // List of all available command definitions.
   DISALLOW_COPY_AND_ASSIGN(CommandDictionary);
 };
diff --git a/src/commands/command_dictionary_unittest.cc b/src/commands/command_dictionary_unittest.cc
index adae4ec..819718f 100644
--- a/src/commands/command_dictionary_unittest.cc
+++ b/src/commands/command_dictionary_unittest.cc
@@ -5,7 +5,8 @@
 #include "src/commands/command_dictionary.h"
 
 #include <gtest/gtest.h>
-#include <weave/test/unittest_utils.h>
+
+#include "src/commands/unittest_utils.h"
 
 namespace weave {
 
@@ -34,8 +35,8 @@
     }
   })");
   CommandDictionary dict;
-  EXPECT_TRUE(dict.LoadCommands(*json, nullptr));
-  EXPECT_EQ(1u, dict.GetSize());
+  EXPECT_TRUE(dict.LoadCommands(*json, nullptr, nullptr));
+  EXPECT_EQ(1, dict.GetSize());
   EXPECT_NE(nullptr, dict.FindCommand("robot.jump"));
   json = CreateDictionaryValue(R"({
     'base': {
@@ -46,49 +47,185 @@
       }
     }
   })");
-  EXPECT_TRUE(dict.LoadCommands(*json, nullptr));
-  EXPECT_EQ(3u, dict.GetSize());
+  EXPECT_TRUE(dict.LoadCommands(*json, nullptr, nullptr));
+  EXPECT_EQ(3, dict.GetSize());
   EXPECT_NE(nullptr, dict.FindCommand("robot.jump"));
   EXPECT_NE(nullptr, dict.FindCommand("base.reboot"));
   EXPECT_NE(nullptr, dict.FindCommand("base.shutdown"));
   EXPECT_EQ(nullptr, dict.FindCommand("foo.bar"));
 }
 
+TEST(CommandDictionary, LoadWithInheritance) {
+  auto json = CreateDictionaryValue(R"({
+    'robot': {
+      'jump': {
+        'minimalRole': 'viewer',
+        'visibility':'local',
+        'parameters': {
+          'height': 'integer'
+        },
+        'progress': {
+          'progress': 'integer'
+        },
+        'results': {
+          'success': 'boolean'
+        }
+      }
+    }
+  })");
+  CommandDictionary base_dict;
+  EXPECT_TRUE(base_dict.LoadCommands(*json, nullptr, nullptr));
+  EXPECT_EQ(1, base_dict.GetSize());
+  json = CreateDictionaryValue(R"({'robot': {'jump': {}}})");
+
+  CommandDictionary dict;
+  EXPECT_TRUE(dict.LoadCommands(*json, &base_dict, nullptr));
+  EXPECT_EQ(1, dict.GetSize());
+
+  auto cmd = dict.FindCommand("robot.jump");
+  EXPECT_NE(nullptr, cmd);
+
+  EXPECT_EQ("local", cmd->GetVisibility().ToString());
+  EXPECT_EQ(UserRole::kViewer, cmd->GetMinimalRole());
+
+  EXPECT_JSON_EQ("{'height': {'type': 'integer'}}",
+                 *cmd->GetParameters()->ToJson(true, true));
+  EXPECT_JSON_EQ("{'progress': {'type': 'integer'}}",
+                 *cmd->GetProgress()->ToJson(true, false));
+  EXPECT_JSON_EQ("{'success': {'type': 'boolean'}}",
+                 *cmd->GetResults()->ToJson(true, false));
+}
+
 TEST(CommandDictionary, LoadCommands_Failures) {
   CommandDictionary dict;
   ErrorPtr error;
 
   // Command definition is not an object.
   auto json = CreateDictionaryValue("{'robot':{'jump':0}}");
-  EXPECT_FALSE(dict.LoadCommands(*json, &error));
+  EXPECT_FALSE(dict.LoadCommands(*json, nullptr, &error));
   EXPECT_EQ("type_mismatch", error->GetCode());
   error.reset();
 
   // Package definition is not an object.
   json = CreateDictionaryValue("{'robot':'blah'}");
-  EXPECT_FALSE(dict.LoadCommands(*json, &error));
+  EXPECT_FALSE(dict.LoadCommands(*json, nullptr, &error));
   EXPECT_EQ("type_mismatch", error->GetCode());
   error.reset();
 
+  // Invalid command definition is not an object.
+  json = CreateDictionaryValue(
+      "{'robot':{'jump':{'parameters':{'flip':0},'results':{}}}}");
+  EXPECT_FALSE(dict.LoadCommands(*json, nullptr, &error));
+  EXPECT_EQ("invalid_object_schema", error->GetCode());
+  EXPECT_NE(nullptr, error->GetInnerError());  // Must have additional info.
+  error.reset();
+
   // Empty command name.
   json = CreateDictionaryValue("{'robot':{'':{'parameters':{},'results':{}}}}");
-  EXPECT_FALSE(dict.LoadCommands(*json, &error));
+  EXPECT_FALSE(dict.LoadCommands(*json, nullptr, &error));
   EXPECT_EQ("invalid_command_name", error->GetCode());
   error.reset();
 }
 
-TEST(CommandDictionaryDeathTest, LoadCommands_Redefine) {
-  // Redefine commands.
+TEST(CommandDictionaryDeathTest, LoadCommands_RedefineInDifferentCategory) {
+  // Redefine commands in different category.
   CommandDictionary dict;
   ErrorPtr error;
   auto json = CreateDictionaryValue("{'robot':{'jump':{}}}");
-  dict.LoadCommands(*json, nullptr);
-  ASSERT_DEATH(dict.LoadCommands(*json, &error),
+  dict.LoadCommands(*json, nullptr, &error);
+  ASSERT_DEATH(dict.LoadCommands(*json, nullptr, &error),
                ".*Definition for command 'robot.jump' overrides an "
                "earlier definition");
 }
 
+TEST(CommandDictionary, LoadCommands_CustomCommandNaming) {
+  // Custom command must start with '_'.
+  CommandDictionary base_dict;
+  CommandDictionary dict;
+  ErrorPtr error;
+  auto json = CreateDictionaryValue(R"({
+    'base': {
+      'reboot': {
+        'parameters': {'delay': 'integer'},
+        'results': {}
+      }
+    }
+  })");
+  base_dict.LoadCommands(*json, nullptr, &error);
+  EXPECT_TRUE(dict.LoadCommands(*json, &base_dict, &error));
+  auto json2 =
+      CreateDictionaryValue("{'base':{'jump':{'parameters':{},'results':{}}}}");
+  EXPECT_FALSE(dict.LoadCommands(*json2, &base_dict, &error));
+  EXPECT_EQ("invalid_command_name", error->GetCode());
+  error.reset();
+
+  // If the command starts with "_", then it's Ok.
+  json2 = CreateDictionaryValue(
+      "{'base':{'_jump':{'parameters':{},'results':{}}}}");
+  EXPECT_TRUE(dict.LoadCommands(*json2, &base_dict, nullptr));
+}
+
+TEST(CommandDictionary, LoadCommands_RedefineStdCommand) {
+  // Redefine commands parameter type.
+  CommandDictionary base_dict;
+  CommandDictionary dict;
+  ErrorPtr error;
+  auto json = CreateDictionaryValue(R"({
+    'base': {
+      'reboot': {
+        'parameters': {'delay': 'integer'},
+        'results': {'version': 'integer'}
+      }
+    }
+  })");
+  base_dict.LoadCommands(*json, nullptr, &error);
+
+  auto json2 = CreateDictionaryValue(R"({
+    'base': {
+      'reboot': {
+        'parameters': {'delay': 'string'},
+        'results': {'version': 'integer'}
+      }
+    }
+  })");
+  EXPECT_FALSE(dict.LoadCommands(*json2, &base_dict, &error));
+  EXPECT_EQ("invalid_object_schema", error->GetCode());
+  EXPECT_EQ("invalid_parameter_definition", error->GetInnerError()->GetCode());
+  EXPECT_EQ("param_type_changed", error->GetFirstError()->GetCode());
+  error.reset();
+
+  auto json3 = CreateDictionaryValue(R"({
+    'base': {
+      'reboot': {
+        'parameters': {'delay': 'integer'},
+        'results': {'version': 'string'}
+      }
+    }
+  })");
+  EXPECT_FALSE(dict.LoadCommands(*json3, &base_dict, &error));
+  EXPECT_EQ("invalid_object_schema", error->GetCode());
+  // TODO(antonm): remove parameter from error below and use some generic.
+  EXPECT_EQ("invalid_parameter_definition", error->GetInnerError()->GetCode());
+  EXPECT_EQ("param_type_changed", error->GetFirstError()->GetCode());
+  error.reset();
+}
+
 TEST(CommandDictionary, GetCommandsAsJson) {
+  auto json_base = CreateDictionaryValue(R"({
+    'base': {
+      'reboot': {
+        'parameters': {'delay': {'maximum': 100}},
+        'results': {}
+      },
+      'shutdown': {
+        'parameters': {},
+        'results': {}
+      }
+    }
+  })");
+  CommandDictionary base_dict;
+  base_dict.LoadCommands(*json_base, nullptr, nullptr);
+
   auto json = CreateDictionaryValue(R"({
     'base': {
       'reboot': {
@@ -99,20 +236,21 @@
     'robot': {
       '_jump': {
         'parameters': {'_height': 'integer'},
-        'minimalRole': 'user'
+        'results': {}
       }
     }
   })");
   CommandDictionary dict;
-  dict.LoadCommands(*json, nullptr);
+  dict.LoadCommands(*json, &base_dict, nullptr);
 
-  json = dict.GetCommandsAsJson(nullptr);
+  json = dict.GetCommandsAsJson(
+      [](const CommandDefinition* def) { return true; }, false, nullptr);
   ASSERT_NE(nullptr, json.get());
   auto expected = R"({
     'base': {
       'reboot': {
         'parameters': {'delay': {'minimum': 10}},
-        'results': {}
+        'minimalRole': 'user'
       }
     },
     'robot': {
@@ -123,6 +261,143 @@
     }
   })";
   EXPECT_JSON_EQ(expected, *json);
+
+  json = dict.GetCommandsAsJson(
+      [](const CommandDefinition* def) { return true; }, true, nullptr);
+  ASSERT_NE(nullptr, json.get());
+  expected = R"({
+    'base': {
+      'reboot': {
+        'parameters': {
+          'delay': {
+            'maximum': 100,
+            'minimum': 10,
+            'type': 'integer'
+          }
+        },
+        'minimalRole': 'user'
+      }
+    },
+    'robot': {
+      '_jump': {
+        'parameters': {
+          '_height': {
+           'type': 'integer'
+          }
+        },
+        'minimalRole': 'user'
+      }
+    }
+  })";
+  EXPECT_JSON_EQ(expected, *json);
+}
+
+TEST(CommandDictionary, GetCommandsAsJsonWithVisibility) {
+  auto json = CreateDictionaryValue(R"({
+    'test': {
+      'command1': {
+        'parameters': {},
+        'results': {},
+        'visibility': 'none'
+      },
+      'command2': {
+        'parameters': {},
+        'results': {},
+        'visibility': 'local'
+      },
+      'command3': {
+        'parameters': {},
+        'results': {},
+        'visibility': 'cloud'
+      },
+      'command4': {
+        'parameters': {},
+        'results': {},
+        'visibility': 'all'
+      },
+      'command5': {
+        'parameters': {},
+        'results': {},
+        'visibility': 'none'
+      },
+      'command6': {
+        'parameters': {},
+        'results': {},
+        'visibility': 'local'
+      },
+      'command7': {
+        'parameters': {},
+        'results': {},
+        'visibility': 'cloud'
+      },
+      'command8': {
+        'parameters': {},
+        'results': {},
+        'visibility': 'all'
+      }
+    }
+  })");
+  CommandDictionary dict;
+  ASSERT_TRUE(dict.LoadCommands(*json, nullptr, nullptr));
+
+  json = dict.GetCommandsAsJson(
+      [](const CommandDefinition* def) { return true; }, false, nullptr);
+  ASSERT_NE(nullptr, json.get());
+  auto expected = R"({
+    'test': {
+      'command1': {'parameters': {}, 'minimalRole': 'user'},
+      'command2': {'parameters': {}, 'minimalRole': 'user'},
+      'command3': {'parameters': {}, 'minimalRole': 'user'},
+      'command4': {'parameters': {}, 'minimalRole': 'user'},
+      'command5': {'parameters': {}, 'minimalRole': 'user'},
+      'command6': {'parameters': {}, 'minimalRole': 'user'},
+      'command7': {'parameters': {}, 'minimalRole': 'user'},
+      'command8': {'parameters': {}, 'minimalRole': 'user'}
+    }
+  })";
+  EXPECT_JSON_EQ(expected, *json);
+
+  json = dict.GetCommandsAsJson(
+      [](const CommandDefinition* def) { return def->GetVisibility().local; },
+      false, nullptr);
+  ASSERT_NE(nullptr, json.get());
+  expected = R"({
+    'test': {
+      'command2': {'parameters': {}, 'minimalRole': 'user'},
+      'command4': {'parameters': {}, 'minimalRole': 'user'},
+      'command6': {'parameters': {}, 'minimalRole': 'user'},
+      'command8': {'parameters': {}, 'minimalRole': 'user'}
+    }
+  })";
+  EXPECT_JSON_EQ(expected, *json);
+
+  json = dict.GetCommandsAsJson(
+      [](const CommandDefinition* def) { return def->GetVisibility().cloud; },
+      false, nullptr);
+  ASSERT_NE(nullptr, json.get());
+  expected = R"({
+    'test': {
+      'command3': {'parameters': {}, 'minimalRole': 'user'},
+      'command4': {'parameters': {}, 'minimalRole': 'user'},
+      'command7': {'parameters': {}, 'minimalRole': 'user'},
+      'command8': {'parameters': {}, 'minimalRole': 'user'}
+    }
+  })";
+  EXPECT_JSON_EQ(expected, *json);
+
+  json = dict.GetCommandsAsJson(
+      [](const CommandDefinition* def) {
+        return def->GetVisibility().local && def->GetVisibility().cloud;
+      },
+      false, nullptr);
+  ASSERT_NE(nullptr, json.get());
+  expected = R"({
+    'test': {
+      'command4': {'parameters': {}, 'minimalRole': 'user'},
+      'command8': {'parameters': {}, 'minimalRole': 'user'}
+    }
+  })";
+  EXPECT_JSON_EQ(expected, *json);
 }
 
 TEST(CommandDictionary, LoadWithPermissions) {
@@ -131,51 +406,161 @@
     'base': {
       'command1': {
         'parameters': {},
-        'results': {}
+        'results': {},
+        'visibility':'none'
       },
       'command2': {
         'minimalRole': 'viewer',
         'parameters': {},
-        'results': {}
+        'results': {},
+        'visibility':'local'
       },
       'command3': {
         'minimalRole': 'user',
         'parameters': {},
-        'results': {}
+        'results': {},
+        'visibility':'cloud'
       },
       'command4': {
         'minimalRole': 'manager',
         'parameters': {},
-        'results': {}
+        'results': {},
+        'visibility':'all'
       },
       'command5': {
         'minimalRole': 'owner',
         'parameters': {},
+        'results': {},
+        'visibility':'local,cloud'
+      }
+    }
+  })");
+  EXPECT_TRUE(base_dict.LoadCommands(*json, nullptr, nullptr));
+
+  auto cmd = base_dict.FindCommand("base.command1");
+  ASSERT_NE(nullptr, cmd);
+  EXPECT_EQ("none", cmd->GetVisibility().ToString());
+  EXPECT_EQ(UserRole::kUser, cmd->GetMinimalRole());
+
+  cmd = base_dict.FindCommand("base.command2");
+  ASSERT_NE(nullptr, cmd);
+  EXPECT_EQ("local", cmd->GetVisibility().ToString());
+  EXPECT_EQ(UserRole::kViewer, cmd->GetMinimalRole());
+
+  cmd = base_dict.FindCommand("base.command3");
+  ASSERT_NE(nullptr, cmd);
+  EXPECT_EQ("cloud", cmd->GetVisibility().ToString());
+  EXPECT_EQ(UserRole::kUser, cmd->GetMinimalRole());
+
+  cmd = base_dict.FindCommand("base.command4");
+  ASSERT_NE(nullptr, cmd);
+  EXPECT_EQ("all", cmd->GetVisibility().ToString());
+  EXPECT_EQ(UserRole::kManager, cmd->GetMinimalRole());
+
+  cmd = base_dict.FindCommand("base.command5");
+  ASSERT_NE(nullptr, cmd);
+  EXPECT_EQ("all", cmd->GetVisibility().ToString());
+  EXPECT_EQ(UserRole::kOwner, cmd->GetMinimalRole());
+
+  CommandDictionary dict;
+  json = CreateDictionaryValue(R"({
+    'base': {
+      'command1': {
+        'parameters': {},
+        'results': {}
+      },
+      'command2': {
+        'parameters': {},
+        'results': {}
+      },
+      'command3': {
+        'parameters': {},
+        'results': {}
+      },
+      'command4': {
+        'parameters': {},
+        'results': {}
+      },
+      'command5': {
+        'parameters': {},
+        'results': {}
+      },
+      '_command6': {
+        'parameters': {},
         'results': {}
       }
     }
   })");
-  EXPECT_TRUE(base_dict.LoadCommands(*json, nullptr));
+  EXPECT_TRUE(dict.LoadCommands(*json, &base_dict, nullptr));
 
-  auto cmd = base_dict.FindCommand("base.command1");
+  cmd = dict.FindCommand("base.command1");
   ASSERT_NE(nullptr, cmd);
+  EXPECT_EQ("none", cmd->GetVisibility().ToString());
   EXPECT_EQ(UserRole::kUser, cmd->GetMinimalRole());
 
-  cmd = base_dict.FindCommand("base.command2");
+  cmd = dict.FindCommand("base.command2");
   ASSERT_NE(nullptr, cmd);
+  EXPECT_EQ("local", cmd->GetVisibility().ToString());
   EXPECT_EQ(UserRole::kViewer, cmd->GetMinimalRole());
 
-  cmd = base_dict.FindCommand("base.command3");
+  cmd = dict.FindCommand("base.command3");
   ASSERT_NE(nullptr, cmd);
+  EXPECT_EQ("cloud", cmd->GetVisibility().ToString());
   EXPECT_EQ(UserRole::kUser, cmd->GetMinimalRole());
 
-  cmd = base_dict.FindCommand("base.command4");
+  cmd = dict.FindCommand("base.command4");
   ASSERT_NE(nullptr, cmd);
+  EXPECT_EQ("all", cmd->GetVisibility().ToString());
   EXPECT_EQ(UserRole::kManager, cmd->GetMinimalRole());
 
-  cmd = base_dict.FindCommand("base.command5");
+  cmd = dict.FindCommand("base.command5");
   ASSERT_NE(nullptr, cmd);
+  EXPECT_EQ("all", cmd->GetVisibility().ToString());
   EXPECT_EQ(UserRole::kOwner, cmd->GetMinimalRole());
+
+  cmd = dict.FindCommand("base._command6");
+  ASSERT_NE(nullptr, cmd);
+  EXPECT_EQ("all", cmd->GetVisibility().ToString());
+  EXPECT_EQ(UserRole::kUser, cmd->GetMinimalRole());
+}
+
+TEST(CommandDictionary, LoadWithPermissions_InvalidVisibility) {
+  CommandDictionary dict;
+  ErrorPtr error;
+
+  auto json = CreateDictionaryValue(R"({
+    'base': {
+      'jump': {
+        'parameters': {},
+        'results': {},
+        'visibility':'foo'
+      }
+    }
+  })");
+  EXPECT_FALSE(dict.LoadCommands(*json, nullptr, &error));
+  EXPECT_EQ("invalid_command_visibility", error->GetCode());
+  EXPECT_EQ("invalid_parameter_value", error->GetInnerError()->GetCode());
+  error.reset();
+}
+
+TEST(CommandDictionary, LoadWithPermissions_InvalidRole) {
+  CommandDictionary dict;
+  ErrorPtr error;
+
+  auto json = CreateDictionaryValue(R"({
+    'base': {
+      'jump': {
+        'parameters': {},
+        'results': {},
+        'visibility':'local,cloud',
+        'minimalRole':'foo'
+      }
+    }
+  })");
+  EXPECT_FALSE(dict.LoadCommands(*json, nullptr, &error));
+  EXPECT_EQ("invalid_minimal_role", error->GetCode());
+  EXPECT_EQ("invalid_parameter_value", error->GetInnerError()->GetCode());
+  error.reset();
 }
 
 }  // namespace weave
diff --git a/src/commands/command_instance.cc b/src/commands/command_instance.cc
index 8328299..f152d82 100644
--- a/src/commands/command_instance.cc
+++ b/src/commands/command_instance.cc
@@ -12,7 +12,9 @@
 #include "src/commands/command_definition.h"
 #include "src/commands/command_dictionary.h"
 #include "src/commands/command_queue.h"
+#include "src/commands/prop_types.h"
 #include "src/commands/schema_constants.h"
+#include "src/commands/schema_utils.h"
 #include "src/json_error_codes.h"
 #include "src/utils.h"
 
@@ -66,12 +68,12 @@
 CommandInstance::CommandInstance(const std::string& name,
                                  Command::Origin origin,
                                  const CommandDefinition* command_definition,
-                                 const base::DictionaryValue& parameters)
+                                 const ValueMap& parameters)
     : name_{name},
       origin_{origin},
-      command_definition_{command_definition} {
+      command_definition_{command_definition},
+      parameters_{parameters} {
   CHECK(command_definition_);
-  parameters_.MergeDictionary(&parameters);
 }
 
 CommandInstance::~CommandInstance() {
@@ -94,16 +96,16 @@
   return origin_;
 }
 
-const base::DictionaryValue& CommandInstance::GetParameters() const {
-  return parameters_;
+std::unique_ptr<base::DictionaryValue> CommandInstance::GetParameters() const {
+  return TypedValueToJson(parameters_);
 }
 
-const base::DictionaryValue& CommandInstance::GetProgress() const {
-  return progress_;
+std::unique_ptr<base::DictionaryValue> CommandInstance::GetProgress() const {
+  return TypedValueToJson(progress_);
 }
 
-const base::DictionaryValue& CommandInstance::GetResults() const {
-  return results_;
+std::unique_ptr<base::DictionaryValue> CommandInstance::GetResults() const {
+  return TypedValueToJson(results_);
 }
 
 const Error* CommandInstance::GetError() const {
@@ -112,13 +114,21 @@
 
 bool CommandInstance::SetProgress(const base::DictionaryValue& progress,
                                   ErrorPtr* error) {
+  if (!command_definition_)
+    return ReportDestroyedError(error);
+  ObjectPropType obj_prop_type;
+  obj_prop_type.SetObjectSchema(command_definition_->GetProgress()->Clone());
+
+  ValueMap obj;
+  if (!TypedValueFromJson(&progress, &obj_prop_type, &obj, error))
+    return false;
+
   // Change status even if progress unchanged, e.g. 0% -> 0%.
   if (!SetStatus(State::kInProgress, error))
     return false;
 
-  if (!progress_.Equals(&progress)) {
-    progress_.Clear();
-    progress_.MergeDictionary(&progress);
+  if (obj != progress_) {
+    progress_ = obj;
     FOR_EACH_OBSERVER(Observer, observers_, OnProgressChanged());
   }
 
@@ -127,9 +137,17 @@
 
 bool CommandInstance::Complete(const base::DictionaryValue& results,
                                ErrorPtr* error) {
-  if (!results_.Equals(&results)) {
-    results_.Clear();
-    results_.MergeDictionary(&results);
+  if (!command_definition_)
+    return ReportDestroyedError(error);
+  ObjectPropType obj_prop_type;
+  obj_prop_type.SetObjectSchema(command_definition_->GetResults()->Clone());
+
+  ValueMap obj;
+  if (!TypedValueFromJson(&results, &obj_prop_type, &obj, error))
+    return false;
+
+  if (obj != results_) {
+    results_ = obj;
     FOR_EACH_OBSERVER(Observer, observers_, OnResultsChanged());
   }
   // Change status even if result is unchanged.
@@ -153,29 +171,36 @@
 // On success, returns |true| and the validated parameters and values through
 // |parameters|. Otherwise returns |false| and additional error information in
 // |error|.
-std::unique_ptr<base::DictionaryValue> GetCommandParameters(
-    const base::DictionaryValue* json,
-    const CommandDefinition* command_def,
-    ErrorPtr* error) {
+bool GetCommandParameters(const base::DictionaryValue* json,
+                          const CommandDefinition* command_def,
+                          ValueMap* parameters,
+                          ErrorPtr* error) {
   // Get the command parameters from 'parameters' property.
-  std::unique_ptr<base::DictionaryValue> params;
+  base::DictionaryValue no_params;  // Placeholder when no params are specified.
+  const base::DictionaryValue* params = nullptr;
   const base::Value* params_value = nullptr;
   if (json->Get(commands::attributes::kCommand_Parameters, &params_value)) {
     // Make sure the "parameters" property is actually an object.
-    const base::DictionaryValue* params_dict = nullptr;
-    if (!params_value->GetAsDictionary(&params_dict)) {
+    if (!params_value->GetAsDictionary(&params)) {
       Error::AddToPrintf(error, FROM_HERE, errors::json::kDomain,
                          errors::json::kObjectExpected,
                          "Property '%s' must be a JSON object",
                          commands::attributes::kCommand_Parameters);
-      return params;
+      return false;
     }
-    params.reset(params_dict->DeepCopy());
   } else {
     // "parameters" are not specified. Assume empty param list.
-    params.reset(new base::DictionaryValue);
+    params = &no_params;
   }
-  return params;
+
+  // Now read in the parameters and validate their values against the command
+  // definition schema.
+  ObjectPropType obj_prop_type;
+  obj_prop_type.SetObjectSchema(command_def->GetParameters()->Clone());
+  if (!TypedValueFromJson(params, &obj_prop_type, parameters, error)) {
+    return false;
+  }
+  return true;
 }
 
 }  // anonymous namespace
@@ -221,8 +246,8 @@
     return instance;
   }
 
-  auto parameters = GetCommandParameters(json, command_def, error);
-  if (!parameters) {
+  ValueMap parameters;
+  if (!GetCommandParameters(json, command_def, &parameters, error)) {
     Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
                        errors::commands::kCommandFailed,
                        "Failed to validate command '%s'", command_name.c_str());
@@ -230,7 +255,7 @@
   }
 
   instance.reset(
-      new CommandInstance{command_name, origin, command_def, *parameters});
+      new CommandInstance{command_name, origin, command_def, parameters});
 
   if (!command_id->empty())
     instance->SetID(*command_id);
@@ -243,9 +268,12 @@
 
   json->SetString(commands::attributes::kCommand_Id, id_);
   json->SetString(commands::attributes::kCommand_Name, name_);
-  json->Set(commands::attributes::kCommand_Parameters, parameters_.DeepCopy());
-  json->Set(commands::attributes::kCommand_Progress, progress_.DeepCopy());
-  json->Set(commands::attributes::kCommand_Results, results_.DeepCopy());
+  json->Set(commands::attributes::kCommand_Parameters,
+            TypedValueToJson(parameters_).release());
+  json->Set(commands::attributes::kCommand_Progress,
+            TypedValueToJson(progress_).release());
+  json->Set(commands::attributes::kCommand_Results,
+            TypedValueToJson(results_).release());
   json->SetString(commands::attributes::kCommand_State, EnumToString(state_));
   if (error_) {
     json->Set(commands::attributes::kCommand_Error,
diff --git a/src/commands/command_instance.h b/src/commands/command_instance.h
index 32a93a9..5a2ebf7 100644
--- a/src/commands/command_instance.h
+++ b/src/commands/command_instance.h
@@ -15,6 +15,9 @@
 #include <weave/error.h>
 #include <weave/command.h>
 
+#include "src/commands/prop_values.h"
+#include "src/commands/schema_utils.h"
+
 namespace base {
 class Value;
 }  // namespace base
@@ -37,16 +40,16 @@
     virtual void OnStateChanged() = 0;
 
    protected:
-    virtual ~Observer() {}
+    virtual ~Observer() = default;
   };
 
   // Construct a command instance given the full command |name| which must
-  // be in format "<package_name>.<command_name>" and a list of parameters and
-  // their values specified in |parameters|.
+  // be in format "<package_name>.<command_name>", a command |category| and
+  // a list of parameters and their values specified in |parameters|.
   CommandInstance(const std::string& name,
                   Command::Origin origin,
                   const CommandDefinition* command_definition,
-                  const base::DictionaryValue& parameters);
+                  const ValueMap& parameters);
   ~CommandInstance() override;
 
   // Command overrides.
@@ -54,9 +57,9 @@
   const std::string& GetName() const override;
   Command::State GetState() const override;
   Command::Origin GetOrigin() const override;
-  const base::DictionaryValue& GetParameters() const override;
-  const base::DictionaryValue& GetProgress() const override;
-  const base::DictionaryValue& GetResults() const override;
+  std::unique_ptr<base::DictionaryValue> GetParameters() const override;
+  std::unique_ptr<base::DictionaryValue> GetProgress() const override;
+  std::unique_ptr<base::DictionaryValue> GetResults() const override;
   const Error* GetError() const override;
   bool SetProgress(const base::DictionaryValue& progress,
                    ErrorPtr* error) override;
@@ -121,11 +124,11 @@
   // Command definition.
   const CommandDefinition* command_definition_{nullptr};
   // Command parameters and their values.
-  base::DictionaryValue parameters_;
+  ValueMap parameters_;
   // Current command execution progress.
-  base::DictionaryValue progress_;
+  ValueMap progress_;
   // Command results.
-  base::DictionaryValue results_;
+  ValueMap results_;
   // Current command state.
   Command::State state_ = Command::State::kQueued;
   // Error encountered during execution of the command.
diff --git a/src/commands/command_instance_unittest.cc b/src/commands/command_instance_unittest.cc
index fb8fe84..4e208ed 100644
--- a/src/commands/command_instance_unittest.cc
+++ b/src/commands/command_instance_unittest.cc
@@ -5,9 +5,11 @@
 #include "src/commands/command_instance.h"
 
 #include <gtest/gtest.h>
-#include <weave/test/unittest_utils.h>
 
 #include "src/commands/command_dictionary.h"
+#include "src/commands/prop_types.h"
+#include "src/commands/schema_utils.h"
+#include "src/commands/unittest_utils.h"
 
 namespace weave {
 
@@ -59,7 +61,7 @@
         }
       }
     })");
-    CHECK(dict_.LoadCommands(*json, nullptr))
+    CHECK(dict_.LoadCommands(*json, nullptr, nullptr))
         << "Failed to parse test command dictionary";
   }
   CommandDictionary dict_;
@@ -68,12 +70,14 @@
 }  // anonymous namespace
 
 TEST_F(CommandInstanceTest, Test) {
-  auto params = CreateDictionaryValue(R"({
-    'phrase': 'iPityDaFool',
-    'volume': 5
-  })");
+  StringPropType str_prop;
+  IntPropType int_prop;
+  ValueMap params;
+  params["phrase"] =
+      str_prop.CreateValue(base::StringValue{"iPityDaFool"}, nullptr);
+  params["volume"] = int_prop.CreateValue(base::FundamentalValue{5}, nullptr);
   CommandInstance instance{"robot.speak", Command::Origin::kCloud,
-                           dict_.FindCommand("robot.speak"), *params};
+                           dict_.FindCommand("robot.speak"), params};
 
   EXPECT_TRUE(
       instance.Complete(*CreateDictionaryValue("{'foo': 239}"), nullptr));
@@ -82,8 +86,8 @@
   EXPECT_EQ("robot.speak", instance.GetName());
   EXPECT_EQ(Command::Origin::kCloud, instance.GetOrigin());
   EXPECT_JSON_EQ("{'phrase': 'iPityDaFool', 'volume': 5}",
-                 instance.GetParameters());
-  EXPECT_JSON_EQ("{'foo': 239}", instance.GetResults());
+                 *instance.GetParameters());
+  EXPECT_JSON_EQ("{'foo': 239}", *instance.GetResults());
 
   CommandInstance instance2{"base.reboot",
                             Command::Origin::kLocal,
@@ -118,7 +122,7 @@
   EXPECT_EQ("abcd", instance->GetID());
   EXPECT_EQ("robot.jump", instance->GetName());
   EXPECT_JSON_EQ("{'height': 53, '_jumpType': '_withKick'}",
-                 instance->GetParameters());
+                 *instance->GetParameters());
 }
 
 TEST_F(CommandInstanceTest, FromJson_ParamsOmitted) {
@@ -126,7 +130,7 @@
   auto instance = CommandInstance::FromJson(json.get(), Command::Origin::kCloud,
                                             dict_, nullptr, nullptr);
   EXPECT_EQ("base.reboot", instance->GetName());
-  EXPECT_JSON_EQ("{}", instance->GetParameters());
+  EXPECT_JSON_EQ("{}", *instance->GetParameters());
 }
 
 TEST_F(CommandInstanceTest, FromJson_NotObject) {
@@ -170,6 +174,25 @@
   EXPECT_EQ("command_failed", error->GetCode());
 }
 
+TEST_F(CommandInstanceTest, FromJson_ParamError) {
+  auto json = CreateDictionaryValue(R"({
+    'name': 'robot.speak',
+    'parameters': {
+      'phrase': 'iPityDaFool',
+      'volume': 20
+    }
+  })");
+  ErrorPtr error;
+  auto instance = CommandInstance::FromJson(json.get(), Command::Origin::kCloud,
+                                            dict_, nullptr, &error);
+  EXPECT_EQ(nullptr, instance.get());
+  auto first = error->GetFirstError();
+  EXPECT_EQ("out_of_range", first->GetCode());
+  auto inner = error->GetInnerError();
+  EXPECT_EQ("invalid_parameter_value", inner->GetCode());
+  EXPECT_EQ("command_failed", error->GetCode());
+}
+
 TEST_F(CommandInstanceTest, ToJson) {
   auto json = CreateDictionaryValue(R"({
     'name': 'robot.jump',
diff --git a/src/commands/command_manager.cc b/src/commands/command_manager.cc
index a64c8e5..75d8295 100644
--- a/src/commands/command_manager.cc
+++ b/src/commands/command_manager.cc
@@ -28,7 +28,7 @@
 
 bool CommandManager::LoadCommands(const base::DictionaryValue& dict,
                                   ErrorPtr* error) {
-  bool result = dictionary_.LoadCommands(dict, error);
+  bool result = dictionary_.LoadCommands(dict, nullptr, error);
   for (const auto& cb : on_command_changed_)
     cb.Run();
   return result;
@@ -83,6 +83,37 @@
   return command_queue_.Find(id);
 }
 
+bool CommandManager::SetCommandVisibility(
+    const std::vector<std::string>& command_names,
+    CommandDefinition::Visibility visibility,
+    ErrorPtr* error) {
+  if (command_names.empty())
+    return true;
+
+  std::vector<CommandDefinition*> definitions;
+  definitions.reserve(command_names.size());
+
+  // Find/validate command definitions first.
+  for (const std::string& name : command_names) {
+    CommandDefinition* def = dictionary_.FindCommand(name);
+    if (!def) {
+      Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                         errors::commands::kInvalidCommandName,
+                         "Command '%s' is unknown", name.c_str());
+      return false;
+    }
+    definitions.push_back(def);
+  }
+
+  // Now that we know that all the command names were valid,
+  // update the respective commands' visibility.
+  for (CommandDefinition* def : definitions)
+    def->SetVisibility(visibility);
+  for (const auto& cb : on_command_changed_)
+    cb.Run();
+  return true;
+}
+
 void CommandManager::AddCommandAddedCallback(
     const CommandQueue::CommandCallback& callback) {
   command_queue_.AddCommandAddedCallback(callback);
diff --git a/src/commands/command_manager.h b/src/commands/command_manager.h
index 04f49ee..7cc8907 100644
--- a/src/commands/command_manager.h
+++ b/src/commands/command_manager.h
@@ -60,6 +60,11 @@
   // Adds a new command to the command queue.
   void AddCommand(std::unique_ptr<CommandInstance> command_instance);
 
+  // Changes the visibility of commands.
+  bool SetCommandVisibility(const std::vector<std::string>& command_names,
+                            CommandDefinition::Visibility visibility,
+                            ErrorPtr* error);
+
   bool AddCommand(const base::DictionaryValue& command,
                   UserRole role,
                   std::string* id,
diff --git a/src/commands/command_manager_unittest.cc b/src/commands/command_manager_unittest.cc
index f0dc95d..0c890a9 100644
--- a/src/commands/command_manager_unittest.cc
+++ b/src/commands/command_manager_unittest.cc
@@ -9,9 +9,9 @@
 #include <base/json/json_writer.h>
 #include <gtest/gtest.h>
 #include <weave/provider/test/mock_config_store.h>
-#include <weave/test/unittest_utils.h>
 
 #include "src/bind_lambda.h"
+#include "src/commands/unittest_utils.h"
 
 using testing::Return;
 
@@ -75,7 +75,7 @@
     }
   })";
   EXPECT_TRUE(manager.LoadCommands(json_str, nullptr));
-  EXPECT_EQ(2u, manager.GetCommandDictionary().GetSize());
+  EXPECT_EQ(2, manager.GetCommandDictionary().GetSize());
   EXPECT_NE(nullptr, manager.GetCommandDictionary().FindCommand("base.reboot"));
   EXPECT_NE(nullptr, manager.GetCommandDictionary().FindCommand("robot._jump"));
 }
@@ -84,11 +84,66 @@
   CommandManager manager;
   ASSERT_TRUE(manager.LoadCommands(kTestVendorCommands, nullptr));
   ASSERT_TRUE(manager.LoadCommands(kTestTestCommands, nullptr));
-  EXPECT_EQ(3u, manager.GetCommandDictionary().GetSize());
+  EXPECT_EQ(3, manager.GetCommandDictionary().GetSize());
   EXPECT_NE(nullptr, manager.GetCommandDictionary().FindCommand("robot._jump"));
   EXPECT_NE(nullptr,
             manager.GetCommandDictionary().FindCommand("robot._speak"));
   EXPECT_NE(nullptr, manager.GetCommandDictionary().FindCommand("test._yo"));
 }
 
+TEST(CommandManager, UpdateCommandVisibility) {
+  CommandManager manager;
+  int update_count = 0;
+  auto on_command_change = [&update_count]() { update_count++; };
+  manager.AddCommandDefChanged(base::Bind(on_command_change));
+
+  auto json = CreateDictionaryValue(R"({
+    'foo': {
+      '_baz': {
+        'parameters': {},
+        'results': {}
+      },
+      '_bar': {
+        'parameters': {},
+        'results': {}
+      }
+    },
+    'bar': {
+      '_quux': {
+        'parameters': {},
+        'results': {},
+        'visibility': 'none'
+      }
+    }
+  })");
+  ASSERT_TRUE(manager.LoadCommands(*json, nullptr));
+  EXPECT_EQ(2, update_count);
+  const CommandDictionary& dict = manager.GetCommandDictionary();
+  EXPECT_TRUE(manager.SetCommandVisibility(
+      {"foo._baz"}, CommandDefinition::Visibility::GetLocal(), nullptr));
+  EXPECT_EQ(3, update_count);
+  EXPECT_EQ("local", dict.FindCommand("foo._baz")->GetVisibility().ToString());
+  EXPECT_EQ("all", dict.FindCommand("foo._bar")->GetVisibility().ToString());
+  EXPECT_EQ("none", dict.FindCommand("bar._quux")->GetVisibility().ToString());
+
+  ErrorPtr error;
+  ASSERT_FALSE(manager.SetCommandVisibility(
+      {"foo._baz", "foo._bar", "test.cmd"},
+      CommandDefinition::Visibility::GetLocal(), &error));
+  EXPECT_EQ(errors::commands::kInvalidCommandName, error->GetCode());
+  // The visibility state of commands shouldn't have changed.
+  EXPECT_EQ(3, update_count);
+  EXPECT_EQ("local", dict.FindCommand("foo._baz")->GetVisibility().ToString());
+  EXPECT_EQ("all", dict.FindCommand("foo._bar")->GetVisibility().ToString());
+  EXPECT_EQ("none", dict.FindCommand("bar._quux")->GetVisibility().ToString());
+
+  EXPECT_TRUE(manager.SetCommandVisibility(
+      {"foo._baz", "bar._quux"}, CommandDefinition::Visibility::GetCloud(),
+      nullptr));
+  EXPECT_EQ(4, update_count);
+  EXPECT_EQ("cloud", dict.FindCommand("foo._baz")->GetVisibility().ToString());
+  EXPECT_EQ("all", dict.FindCommand("foo._bar")->GetVisibility().ToString());
+  EXPECT_EQ("cloud", dict.FindCommand("bar._quux")->GetVisibility().ToString());
+}
+
 }  // namespace weave
diff --git a/src/commands/command_queue.cc b/src/commands/command_queue.cc
index 3d2167f..e08527b 100644
--- a/src/commands/command_queue.cc
+++ b/src/commands/command_queue.cc
@@ -38,8 +38,7 @@
       }
     }
 
-    CHECK(command_callbacks_.insert(std::make_pair(command_name, callback))
-              .second)
+    CHECK(command_callbacks_.emplace(command_name, callback).second)
         << command_name << " already has handler";
 
   } else {
diff --git a/src/commands/command_queue_unittest.cc b/src/commands/command_queue_unittest.cc
index dc7290a..5060071 100644
--- a/src/commands/command_queue_unittest.cc
+++ b/src/commands/command_queue_unittest.cc
@@ -13,21 +13,18 @@
 #include <gtest/gtest.h>
 
 #include "src/commands/command_definition.h"
+#include "src/commands/object_schema.h"
 #include "src/string_utils.h"
 
 namespace weave {
 
 class CommandQueueTest : public testing::Test {
  public:
-  CommandQueueTest() {
-    command_definition_ = CommandDefinition::FromJson({}, nullptr);
-  }
-
   std::unique_ptr<CommandInstance> CreateDummyCommandInstance(
       const std::string& name,
       const std::string& id) {
     std::unique_ptr<CommandInstance> cmd{new CommandInstance{
-        name, Command::Origin::kLocal, command_definition_.get(), {}}};
+        name, Command::Origin::kLocal, &command_definition_, {}}};
     cmd->SetID(id);
     return cmd;
   }
@@ -42,7 +39,8 @@
   CommandQueue queue_;
 
  private:
-  std::unique_ptr<CommandDefinition> command_definition_;
+  CommandDefinition command_definition_{
+      ObjectSchema::Create(), ObjectSchema::Create(), ObjectSchema::Create()};
 };
 
 // Keeps track of commands being added to and removed from the queue_.
@@ -83,14 +81,14 @@
 
 TEST_F(CommandQueueTest, Empty) {
   EXPECT_TRUE(queue_.IsEmpty());
-  EXPECT_EQ(0u, queue_.GetCount());
+  EXPECT_EQ(0, queue_.GetCount());
 }
 
 TEST_F(CommandQueueTest, Add) {
   queue_.Add(CreateDummyCommandInstance("base.reboot", "id1"));
   queue_.Add(CreateDummyCommandInstance("base.reboot", "id2"));
   queue_.Add(CreateDummyCommandInstance("base.reboot", "id3"));
-  EXPECT_EQ(3u, queue_.GetCount());
+  EXPECT_EQ(3, queue_.GetCount());
   EXPECT_FALSE(queue_.IsEmpty());
 }
 
@@ -101,31 +99,31 @@
   queue_.Add(CreateDummyCommandInstance("base.reboot", id2));
   EXPECT_FALSE(queue_.IsEmpty());
   EXPECT_FALSE(Remove("dummy"));
-  EXPECT_EQ(2u, queue_.GetCount());
+  EXPECT_EQ(2, queue_.GetCount());
   EXPECT_TRUE(Remove(id1));
-  EXPECT_EQ(1u, queue_.GetCount());
+  EXPECT_EQ(1, queue_.GetCount());
   EXPECT_FALSE(Remove(id1));
-  EXPECT_EQ(1u, queue_.GetCount());
+  EXPECT_EQ(1, queue_.GetCount());
   EXPECT_TRUE(Remove(id2));
-  EXPECT_EQ(0u, queue_.GetCount());
+  EXPECT_EQ(0, queue_.GetCount());
   EXPECT_FALSE(Remove(id2));
-  EXPECT_EQ(0u, queue_.GetCount());
+  EXPECT_EQ(0, queue_.GetCount());
   EXPECT_TRUE(queue_.IsEmpty());
 }
 
 TEST_F(CommandQueueTest, DelayedRemove) {
   const std::string id1 = "id1";
   queue_.Add(CreateDummyCommandInstance("base.reboot", id1));
-  EXPECT_EQ(1u, queue_.GetCount());
+  EXPECT_EQ(1, queue_.GetCount());
 
   queue_.DelayedRemove(id1);
-  EXPECT_EQ(1u, queue_.GetCount());
+  EXPECT_EQ(1, queue_.GetCount());
 
   Cleanup(base::TimeDelta::FromMinutes(1));
-  EXPECT_EQ(1u, queue_.GetCount());
+  EXPECT_EQ(1, queue_.GetCount());
 
   Cleanup(base::TimeDelta::FromMinutes(15));
-  EXPECT_EQ(0u, queue_.GetCount());
+  EXPECT_EQ(0, queue_.GetCount());
 }
 
 TEST_F(CommandQueueTest, Dispatch) {
diff --git a/src/commands/object_schema.cc b/src/commands/object_schema.cc
new file mode 100644
index 0000000..b70beff
--- /dev/null
+++ b/src/commands/object_schema.cc
@@ -0,0 +1,378 @@
+// Copyright 2015 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/commands/object_schema.h"
+
+#include <algorithm>
+#include <limits>
+
+#include <base/logging.h>
+#include <base/values.h>
+#include <weave/enum_to_string.h>
+
+#include "src/commands/prop_types.h"
+#include "src/commands/prop_values.h"
+#include "src/commands/schema_constants.h"
+#include "src/string_utils.h"
+
+namespace weave {
+
+namespace {
+
+// Helper function for to create a PropType based on type string.
+// Generates an error if the string identifies an unknown type.
+std::unique_ptr<PropType> CreatePropType(const std::string& type_name,
+                                         ErrorPtr* error) {
+  auto parts = SplitAtFirst(type_name, ".", false);
+  const std::string& primary_type = parts.first;
+  const std::string& array_type = parts.second;
+
+  std::unique_ptr<PropType> prop;
+  ValueType type;
+  if (PropType::GetTypeFromTypeString(primary_type, &type)) {
+    prop = PropType::Create(type);
+    if (prop && type == ValueType::Array && !array_type.empty()) {
+      auto items_type = CreatePropType(array_type, error);
+      if (items_type) {
+        prop->GetArray()->SetItemType(std::move(items_type));
+      } else {
+        prop.reset();
+        return prop;
+      }
+    }
+  }
+  if (!prop) {
+    Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                       errors::commands::kUnknownType, "Unknown type %s",
+                       type_name.c_str());
+  }
+  return prop;
+}
+
+// Generates "no_type_info" error.
+void ErrorInvalidTypeInfo(ErrorPtr* error) {
+  Error::AddTo(error, FROM_HERE, errors::commands::kDomain,
+               errors::commands::kNoTypeInfo,
+               "Unable to determine parameter type");
+}
+
+// Helper function for PropFromJson to handle the case of parameter being
+// defined as a JSON string like this:
+//   "prop":"..."
+std::unique_ptr<PropType> PropFromJsonString(const base::Value& value,
+                                             const PropType* base_schema,
+                                             ErrorPtr* error) {
+  std::string type_name;
+  CHECK(value.GetAsString(&type_name)) << "Unable to get string value";
+  std::unique_ptr<PropType> prop = CreatePropType(type_name, error);
+  base::DictionaryValue empty;
+  if (prop && !prop->FromJson(&empty, base_schema, error))
+    prop.reset();
+
+  return prop;
+}
+
+// Detects a type based on JSON array. Inspects the first element of the array
+// to deduce the PropType from. Returns the string name of the type detected
+// or empty string is type detection failed.
+std::string DetectArrayType(const base::ListValue* list,
+                            const PropType* base_schema,
+                            bool allow_arrays) {
+  std::string type_name;
+  if (base_schema) {
+    type_name = base_schema->GetTypeAsString();
+  } else if (list->GetSize() > 0) {
+    const base::Value* first_element = nullptr;
+    if (list->Get(0, &first_element)) {
+      switch (first_element->GetType()) {
+        case base::Value::TYPE_BOOLEAN:
+          type_name = PropType::GetTypeStringFromType(ValueType::Boolean);
+          break;
+        case base::Value::TYPE_INTEGER:
+          type_name = PropType::GetTypeStringFromType(ValueType::Int);
+          break;
+        case base::Value::TYPE_DOUBLE:
+          type_name = PropType::GetTypeStringFromType(ValueType::Double);
+          break;
+        case base::Value::TYPE_STRING:
+          type_name = PropType::GetTypeStringFromType(ValueType::String);
+          break;
+        case base::Value::TYPE_DICTIONARY:
+          type_name = PropType::GetTypeStringFromType(ValueType::Object);
+          break;
+        case base::Value::TYPE_LIST: {
+          if (allow_arrays) {
+            type_name = PropType::GetTypeStringFromType(ValueType::Array);
+            const base::ListValue* first_element_list = nullptr;
+            if (first_element->GetAsList(&first_element_list)) {
+              // We do not allow arrays of arrays.
+              auto child_type =
+                  DetectArrayType(first_element_list, nullptr, false);
+              if (child_type.empty()) {
+                type_name.clear();
+              } else {
+                type_name += '.' + child_type;
+              }
+            }
+          }
+          break;
+        }
+        default:
+          // The rest are unsupported.
+          break;
+      }
+    }
+  }
+  return type_name;
+}
+
+// Helper function for PropFromJson to handle the case of parameter being
+// defined as a JSON array like this:
+//   "prop":[...]
+std::unique_ptr<PropType> PropFromJsonArray(const base::Value& value,
+                                            const PropType* base_schema,
+                                            ErrorPtr* error) {
+  std::unique_ptr<PropType> prop;
+  const base::ListValue* list = nullptr;
+  CHECK(value.GetAsList(&list)) << "Unable to get array value";
+  std::string type_name = DetectArrayType(list, base_schema, true);
+  if (type_name.empty()) {
+    ErrorInvalidTypeInfo(error);
+    return prop;
+  }
+  base::DictionaryValue array_object;
+  array_object.SetWithoutPathExpansion(commands::attributes::kOneOf_Enum,
+                                       list->DeepCopy());
+  prop = CreatePropType(type_name, error);
+  if (prop && !prop->FromJson(&array_object, base_schema, error))
+    prop.reset();
+
+  return prop;
+}
+
+// Detects a type based on JSON object definition of type. Looks at various
+// members such as minimum/maximum constraints, default and enum values to
+// try to deduce the underlying type of the element. Returns the string name of
+// the type detected or empty string is type detection failed.
+std::string DetectObjectType(const base::DictionaryValue* dict,
+                             const PropType* base_schema) {
+  bool has_min_max = dict->HasKey(commands::attributes::kNumeric_Min) ||
+                     dict->HasKey(commands::attributes::kNumeric_Max);
+
+  // Here we are trying to "detect the type and read in the object based on
+  // the deduced type". Later, we'll verify that this detected type matches
+  // the expectation of the base schema, if applicable, to make sure we are not
+  // changing the expected type. This makes the vendor-side (re)definition of
+  // standard and custom commands behave exactly the same.
+  // The only problem with this approach was the double-vs-int types.
+  // If the type is meant to be a double we want to allow its definition as
+  // "min:0, max:0" instead of just forcing it to be only "min:0.0, max:0.0".
+  // If we have "minimum" or "maximum", and we have a Double schema object,
+  // treat this object as a Double (even if both min and max are integers).
+  if (has_min_max && base_schema && base_schema->GetType() == ValueType::Double)
+    return PropType::GetTypeStringFromType(ValueType::Double);
+
+  // If we have at least one "minimum" or "maximum" that is Double,
+  // it's a Double.
+  const base::Value* value = nullptr;
+  if (dict->Get(commands::attributes::kNumeric_Min, &value) &&
+      value->IsType(base::Value::TYPE_DOUBLE))
+    return PropType::GetTypeStringFromType(ValueType::Double);
+  if (dict->Get(commands::attributes::kNumeric_Max, &value) &&
+      value->IsType(base::Value::TYPE_DOUBLE))
+    return PropType::GetTypeStringFromType(ValueType::Double);
+
+  // If we have "minimum" or "maximum", it's an Integer.
+  if (has_min_max)
+    return PropType::GetTypeStringFromType(ValueType::Int);
+
+  // If we have "minLength" or "maxLength", it's a String.
+  if (dict->HasKey(commands::attributes::kString_MinLength) ||
+      dict->HasKey(commands::attributes::kString_MaxLength))
+    return PropType::GetTypeStringFromType(ValueType::String);
+
+  // If we have "properties", it's an object.
+  if (dict->HasKey(commands::attributes::kObject_Properties))
+    return PropType::GetTypeStringFromType(ValueType::Object);
+
+  // If we have "items", it's an array.
+  if (dict->HasKey(commands::attributes::kItems))
+    return PropType::GetTypeStringFromType(ValueType::Array);
+
+  // If we have "enum", it's an array. Detect type from array elements.
+  const base::ListValue* list = nullptr;
+  if (dict->GetListWithoutPathExpansion(commands::attributes::kOneOf_Enum,
+                                        &list))
+    return DetectArrayType(list, base_schema, true);
+
+  // 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);
+    if (value->IsType(base::Value::TYPE_LIST)) {
+      CHECK(value->GetAsList(&list)) << "List value expected";
+      std::string child_type = DetectArrayType(list, base_schema, false);
+      if (!child_type.empty()) {
+        return PropType::GetTypeStringFromType(ValueType::Array) + '.' +
+               child_type;
+      }
+    }
+  }
+
+  return std::string{};
+}
+
+// Helper function for PropFromJson to handle the case of parameter being
+// defined as a JSON object like this:
+//   "prop":{...}
+std::unique_ptr<PropType> PropFromJsonObject(const base::Value& value,
+                                             const PropType* base_schema,
+                                             ErrorPtr* error) {
+  std::unique_ptr<PropType> prop;
+  const base::DictionaryValue* dict = nullptr;
+  CHECK(value.GetAsDictionary(&dict)) << "Unable to get dictionary value";
+  std::string type_name;
+  if (dict->HasKey(commands::attributes::kType)) {
+    if (!dict->GetString(commands::attributes::kType, &type_name)) {
+      ErrorInvalidTypeInfo(error);
+      return prop;
+    }
+  } else {
+    type_name = DetectObjectType(dict, base_schema);
+  }
+  if (type_name.empty()) {
+    if (!base_schema) {
+      ErrorInvalidTypeInfo(error);
+      return prop;
+    }
+    type_name = base_schema->GetTypeAsString();
+  }
+  prop = CreatePropType(type_name, error);
+  if (prop && !prop->FromJson(dict, base_schema, error))
+    prop.reset();
+
+  return prop;
+}
+
+const EnumToStringMap<base::Value::Type>::Map kMap[] = {
+    {base::Value::TYPE_NULL, "Null"},
+    {base::Value::TYPE_BOOLEAN, "Boolean"},
+    {base::Value::TYPE_INTEGER, "Integer"},
+    {base::Value::TYPE_DOUBLE, "Double"},
+    {base::Value::TYPE_STRING, "String"},
+    {base::Value::TYPE_BINARY, "Binary"},
+    {base::Value::TYPE_DICTIONARY, "Object"},
+    {base::Value::TYPE_LIST, "Array"},
+};
+
+}  // anonymous namespace
+
+template <>
+EnumToStringMap<base::Value::Type>::EnumToStringMap() : EnumToStringMap(kMap) {}
+
+ObjectSchema::ObjectSchema() {}
+ObjectSchema::~ObjectSchema() {}
+
+std::unique_ptr<ObjectSchema> ObjectSchema::Clone() const {
+  std::unique_ptr<ObjectSchema> cloned{new ObjectSchema};
+  for (const auto& pair : properties_) {
+    cloned->properties_.emplace(pair.first, pair.second->Clone());
+  }
+  cloned->extra_properties_allowed_ = extra_properties_allowed_;
+  return cloned;
+}
+
+void ObjectSchema::AddProp(const std::string& name,
+                           std::unique_ptr<PropType> prop) {
+  // Not using emplace() here to make sure we override existing properties.
+  properties_[name] = std::move(prop);
+}
+
+const PropType* ObjectSchema::GetProp(const std::string& name) const {
+  auto p = properties_.find(name);
+  return p != properties_.end() ? p->second.get() : nullptr;
+}
+
+bool ObjectSchema::MarkPropRequired(const std::string& name, ErrorPtr* error) {
+  auto p = properties_.find(name);
+  if (p == properties_.end()) {
+    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) const {
+  std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue);
+  for (const auto& pair : properties_) {
+    auto prop_def = pair.second->ToJson(full_schema, in_command_def);
+    CHECK(prop_def);
+    value->SetWithoutPathExpansion(pair.first, prop_def.release());
+  }
+  return value;
+}
+
+bool ObjectSchema::FromJson(const base::DictionaryValue* value,
+                            const ObjectSchema* object_schema,
+                            ErrorPtr* error) {
+  Properties properties;
+  base::DictionaryValue::Iterator iter(*value);
+  while (!iter.IsAtEnd()) {
+    std::string name = iter.key();
+    const PropType* base_schema =
+        object_schema ? object_schema->GetProp(iter.key()) : nullptr;
+    auto prop_type = PropFromJson(iter.value(), base_schema, error);
+    if (prop_type) {
+      properties.emplace(iter.key(), std::move(prop_type));
+    } else {
+      Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                         errors::commands::kInvalidPropDef,
+                         "Error in definition of property '%s'",
+                         iter.key().c_str());
+      return false;
+    }
+    iter.Advance();
+  }
+  properties_ = std::move(properties);
+  return true;
+}
+
+std::unique_ptr<PropType> ObjectSchema::PropFromJson(
+    const base::Value& value,
+    const PropType* base_schema,
+    ErrorPtr* error) {
+  if (value.IsType(base::Value::TYPE_STRING)) {
+    // A string value is a short-hand object specification and provides
+    // the parameter type.
+    return PropFromJsonString(value, base_schema, error);
+  } else if (value.IsType(base::Value::TYPE_LIST)) {
+    // One of the enumerated types.
+    return PropFromJsonArray(value, base_schema, error);
+  } else if (value.IsType(base::Value::TYPE_DICTIONARY)) {
+    // Full parameter definition.
+    return PropFromJsonObject(value, base_schema, error);
+  }
+  Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                     errors::commands::kUnknownType,
+                     "Unexpected JSON value type: %s",
+                     EnumToString(value.GetType()).c_str());
+  return nullptr;
+}
+
+std::unique_ptr<ObjectSchema> ObjectSchema::Create() {
+  return std::unique_ptr<ObjectSchema>{new ObjectSchema};
+}
+
+}  // namespace weave
diff --git a/src/commands/object_schema.h b/src/commands/object_schema.h
new file mode 100644
index 0000000..504e1dd
--- /dev/null
+++ b/src/commands/object_schema.h
@@ -0,0 +1,95 @@
+// Copyright 2015 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_SRC_COMMANDS_OBJECT_SCHEMA_H_
+#define LIBWEAVE_SRC_COMMANDS_OBJECT_SCHEMA_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include <weave/error.h>
+
+namespace base {
+class Value;
+class DictionaryValue;
+}  // namespace base
+
+namespace weave {
+
+class PropType;
+
+// ObjectSchema is a class representing an object definition in GCD command
+// schema. This could represent a GCD command definition, but also it can be
+// used when defining custom object types for command properties such as
+// output media type (paper) for print command. The schema definition for
+// these type of object description is the same.
+class ObjectSchema final {
+ public:
+  // Do not inline the constructor/destructor to allow forward-declared type
+  // PropType to be part of |properties_| member.
+  ObjectSchema();
+  ~ObjectSchema();
+
+  // Properties is a string-to-PropType map representing a list of
+  // properties defined for a command/object. The key is the parameter
+  // name and the value is the parameter type definition object.
+  using Properties = std::map<std::string, std::unique_ptr<PropType>>;
+
+  // Makes a full copy of this object.
+  virtual std::unique_ptr<ObjectSchema> Clone() const;
+
+  // Add a new parameter definition.
+  void AddProp(const std::string& name, std::unique_ptr<PropType> prop);
+
+  // Finds parameter type definition by name. Returns nullptr if not found.
+  const PropType* GetProp(const std::string& name) const;
+
+  // 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, 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
+  // the schema and no other (custom) properties are allowed.
+  // This is to support JSON Schema's "additionalProperties" specification.
+  bool GetExtraPropertiesAllowed() const { return extra_properties_allowed_; }
+  void SetExtraPropertiesAllowed(bool allowed) {
+    extra_properties_allowed_ = allowed;
+  }
+
+  // Saves the object schema to JSON. When |full_schema| is set to true,
+  // then all properties and constraints are saved, otherwise, only
+  // the overridden (not inherited) ones are saved.
+  std::unique_ptr<base::DictionaryValue> ToJson(bool full_schema,
+                                                bool in_command_def) const;
+
+  // Loads the object schema from JSON. If |object_schema| is not nullptr, it is
+  // used as a base schema to inherit omitted properties and constraints from.
+  bool FromJson(const base::DictionaryValue* value,
+                const ObjectSchema* object_schema,
+                ErrorPtr* error);
+
+  // Helper factory method to create a new instance of ObjectSchema object.
+  static std::unique_ptr<ObjectSchema> Create();
+
+  // Helper method to load property type definitions from JSON.
+  static std::unique_ptr<PropType> PropFromJson(const base::Value& value,
+                                                const PropType* base_schema,
+                                                ErrorPtr* error);
+
+ private:
+  // Internal parameter type definition map.
+  Properties properties_;
+  bool extra_properties_allowed_{false};
+};
+
+}  // namespace weave
+
+#endif  // LIBWEAVE_SRC_COMMANDS_OBJECT_SCHEMA_H_
diff --git a/src/commands/object_schema_unittest.cc b/src/commands/object_schema_unittest.cc
new file mode 100644
index 0000000..6417952
--- /dev/null
+++ b/src/commands/object_schema_unittest.cc
@@ -0,0 +1,1697 @@
+// Copyright 2015 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/commands/object_schema.h"
+
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <vector>
+
+#include <base/json/json_reader.h>
+#include <base/json/json_writer.h>
+#include <base/values.h>
+#include <gtest/gtest.h>
+
+#include "src/commands/prop_constraints.h"
+#include "src/commands/prop_types.h"
+#include "src/commands/schema_constants.h"
+#include "src/commands/unittest_utils.h"
+
+namespace weave {
+
+using test::CreateValue;
+using test::CreateDictionaryValue;
+
+namespace {
+
+template <typename T>
+std::vector<T> GetArrayValues(const ValueVector& arr) {
+  std::vector<T> values;
+  values.reserve(arr.size());
+  for (const auto& prop_value : arr) {
+    const auto& value = static_cast<const TypedValueBase<T>&>(*prop_value);
+    values.push_back(value.GetValue());
+  }
+  return values;
+}
+
+template <typename T>
+std::vector<T> GetOneOfValues(const PropType* prop_type) {
+  auto one_of = static_cast<const ConstraintOneOf*>(
+      prop_type->GetConstraint(ConstraintType::OneOf));
+  if (!one_of)
+    return {};
+
+  return GetArrayValues<T>(one_of->set_.value);
+}
+
+bool ValidateValue(const PropType& type,
+                   const base::Value& value,
+                   ErrorPtr* error) {
+  std::unique_ptr<PropValue> val = type.CreatePropValue(value, error);
+  return val != nullptr;
+}
+
+}  // anonymous namespace
+
+TEST(CommandSchema, IntPropType_Empty) {
+  IntPropType prop;
+  EXPECT_TRUE(prop.GetConstraints().empty());
+  EXPECT_FALSE(prop.HasOverriddenAttributes());
+  EXPECT_FALSE(prop.IsBasedOnSchema());
+  EXPECT_EQ(nullptr, prop.GetDefaultValue());
+}
+
+TEST(CommandSchema, IntPropType_Types) {
+  IntPropType prop;
+  EXPECT_EQ(nullptr, prop.GetBoolean());
+  EXPECT_EQ(&prop, prop.GetInt());
+  EXPECT_EQ(nullptr, prop.GetDouble());
+  EXPECT_EQ(nullptr, prop.GetString());
+  EXPECT_EQ(nullptr, prop.GetObject());
+  EXPECT_EQ(nullptr, prop.GetArray());
+}
+
+TEST(CommandSchema, IntPropType_ToJson) {
+  IntPropType prop;
+  EXPECT_JSON_EQ("'integer'", *prop.ToJson(false, false));
+  EXPECT_JSON_EQ("{'type':'integer'}", *prop.ToJson(true, false));
+  IntPropType param2;
+  param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+  EXPECT_JSON_EQ("{}", *param2.ToJson(false, false));
+  param2.FromJson(CreateDictionaryValue("{'minimum':3}").get(), &prop, nullptr);
+  EXPECT_JSON_EQ("{'minimum':3}", *param2.ToJson(false, false));
+  param2.FromJson(CreateDictionaryValue("{'maximum':-7}").get(), &prop,
+                  nullptr);
+  EXPECT_JSON_EQ("{'maximum':-7}", *param2.ToJson(false, false));
+  param2.FromJson(CreateDictionaryValue("{'minimum':0,'maximum':5}").get(),
+                  &prop, nullptr);
+  EXPECT_JSON_EQ("{'maximum':5,'minimum':0}", *param2.ToJson(false, false));
+  param2.FromJson(CreateDictionaryValue("{'enum':[1,2,3]}").get(), &prop,
+                  nullptr);
+  EXPECT_JSON_EQ("[1,2,3]", *param2.ToJson(false, false));
+  param2.FromJson(CreateDictionaryValue("{'default':123}").get(), &prop,
+                  nullptr);
+  EXPECT_JSON_EQ("{'default':123}", *param2.ToJson(false, false));
+}
+
+TEST(CommandSchema, IntPropType_FromJson) {
+  IntPropType prop;
+  prop.AddMinMaxConstraint(2, 8);
+  IntPropType param2;
+  param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+  EXPECT_FALSE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_EQ(2, prop.GetMinValue());
+  EXPECT_EQ(8, prop.GetMaxValue());
+  prop.AddMinMaxConstraint(-2, 30);
+  param2.FromJson(CreateDictionaryValue("{'minimum':7}").get(), &prop, nullptr);
+  EXPECT_TRUE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_EQ(7, param2.GetMinValue());
+  EXPECT_EQ(30, param2.GetMaxValue());
+  param2.FromJson(CreateDictionaryValue("{'maximum':17}").get(), &prop,
+                  nullptr);
+  EXPECT_TRUE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_EQ(-2, param2.GetMinValue());
+  EXPECT_EQ(17, param2.GetMaxValue());
+
+  ASSERT_TRUE(param2.FromJson(CreateDictionaryValue("{'default':3}").get(),
+                              &prop, nullptr));
+  EXPECT_TRUE(param2.HasOverriddenAttributes());
+  ASSERT_NE(nullptr, param2.GetDefaultValue());
+  EXPECT_EQ(3, param2.GetDefaultValue()->GetInt()->GetValue());
+}
+
+TEST(CommandSchema, IntPropType_Validate) {
+  IntPropType prop;
+  prop.AddMinMaxConstraint(2, 4);
+  ErrorPtr error;
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("-1"), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("0"), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("1"), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  EXPECT_TRUE(ValidateValue(prop, *CreateValue("2"), &error));
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_TRUE(ValidateValue(prop, *CreateValue("3"), &error));
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_TRUE(ValidateValue(prop, *CreateValue("4"), &error));
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("5"), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("true"), &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("3.0"), &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("'3'"), &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+}
+
+TEST(CommandSchema, IntPropType_CreateValue) {
+  IntPropType prop;
+  ErrorPtr error;
+  auto val = prop.CreateValue(base::FundamentalValue{2}, &error);
+  ASSERT_NE(nullptr, val.get());
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_EQ(2, val->GetValue());
+
+  val = prop.CreateValue(base::StringValue{"blah"}, &error);
+  EXPECT_EQ(nullptr, val.get());
+  ASSERT_NE(nullptr, error.get());
+  EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TEST(CommandSchema, BoolPropType_Empty) {
+  BooleanPropType prop;
+  EXPECT_TRUE(prop.GetConstraints().empty());
+  EXPECT_FALSE(prop.HasOverriddenAttributes());
+  EXPECT_FALSE(prop.IsBasedOnSchema());
+  EXPECT_EQ(nullptr, prop.GetDefaultValue());
+  EXPECT_FALSE(prop.IsRequired());
+}
+
+TEST(CommandSchema, BoolPropType_Types) {
+  BooleanPropType prop;
+  EXPECT_EQ(nullptr, prop.GetInt());
+  EXPECT_EQ(&prop, prop.GetBoolean());
+  EXPECT_EQ(nullptr, prop.GetDouble());
+  EXPECT_EQ(nullptr, prop.GetString());
+  EXPECT_EQ(nullptr, prop.GetObject());
+  EXPECT_EQ(nullptr, prop.GetArray());
+}
+
+TEST(CommandSchema, BoolPropType_ToJson) {
+  BooleanPropType prop;
+  EXPECT_JSON_EQ("'boolean'", *prop.ToJson(false, false));
+  EXPECT_JSON_EQ("{'type':'boolean'}", *prop.ToJson(true, false));
+  BooleanPropType param2;
+  param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+  EXPECT_JSON_EQ("{}", *param2.ToJson(false, false));
+  param2.FromJson(CreateDictionaryValue("{'enum':[true,false]}").get(), &prop,
+                  nullptr);
+  EXPECT_JSON_EQ("[true,false]", *param2.ToJson(false, false));
+  EXPECT_JSON_EQ("{'enum':[true,false],'type':'boolean'}",
+                 *param2.ToJson(true, false));
+  param2.FromJson(CreateDictionaryValue("{'default':true}").get(), &prop,
+                  nullptr);
+  EXPECT_JSON_EQ("{'default':true}", *param2.ToJson(false, false));
+}
+
+TEST(CommandSchema, BoolPropType_FromJson) {
+  BooleanPropType prop;
+  prop.FromJson(CreateDictionaryValue("{'enum':[true]}").get(), &prop, nullptr);
+  BooleanPropType param2;
+  param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+  EXPECT_FALSE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_EQ(std::vector<bool>{true}, GetOneOfValues<bool>(&prop));
+
+  BooleanPropType prop_base;
+  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) {
+  BooleanPropType prop;
+  prop.FromJson(CreateDictionaryValue("{'enum':[true]}").get(), &prop, nullptr);
+  ErrorPtr error;
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("false"), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  EXPECT_TRUE(ValidateValue(prop, *CreateValue("true"), &error));
+  error.reset();
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("1"), &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("3.0"), &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("'3'"), &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+}
+
+TEST(CommandSchema, BoolPropType_CreateValue) {
+  BooleanPropType prop;
+  ErrorPtr error;
+  auto val = prop.CreateValue(base::FundamentalValue{true}, &error);
+  ASSERT_NE(nullptr, val.get());
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_TRUE(val->GetValue());
+
+  val = prop.CreateValue(base::StringValue{"blah"}, &error);
+  EXPECT_EQ(nullptr, val.get());
+  ASSERT_NE(nullptr, error.get());
+  EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TEST(CommandSchema, DoublePropType_Empty) {
+  DoublePropType prop;
+  EXPECT_DOUBLE_EQ(std::numeric_limits<double>::lowest(), prop.GetMinValue());
+  EXPECT_DOUBLE_EQ((std::numeric_limits<double>::max)(), prop.GetMaxValue());
+  EXPECT_FALSE(prop.HasOverriddenAttributes());
+  EXPECT_FALSE(prop.IsBasedOnSchema());
+  EXPECT_EQ(nullptr, prop.GetDefaultValue());
+  EXPECT_FALSE(prop.IsRequired());
+}
+
+TEST(CommandSchema, DoublePropType_Types) {
+  DoublePropType prop;
+  EXPECT_EQ(nullptr, prop.GetInt());
+  EXPECT_EQ(nullptr, prop.GetBoolean());
+  EXPECT_EQ(&prop, prop.GetDouble());
+  EXPECT_EQ(nullptr, prop.GetString());
+  EXPECT_EQ(nullptr, prop.GetObject());
+  EXPECT_EQ(nullptr, prop.GetArray());
+}
+
+TEST(CommandSchema, DoublePropType_ToJson) {
+  DoublePropType prop;
+  EXPECT_JSON_EQ("'number'", *prop.ToJson(false, false));
+  EXPECT_JSON_EQ("{'type':'number'}", *prop.ToJson(true, false));
+  DoublePropType param2;
+  param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+  EXPECT_JSON_EQ("{}", *param2.ToJson(false, false));
+  param2.FromJson(CreateDictionaryValue("{'minimum':3}").get(), &prop, nullptr);
+  EXPECT_JSON_EQ("{'minimum':3.0}", *param2.ToJson(false, false));
+  param2.FromJson(CreateDictionaryValue("{'maximum':-7}").get(), &prop,
+                  nullptr);
+  EXPECT_JSON_EQ("{'maximum':-7.0}", *param2.ToJson(false, false));
+  param2.FromJson(CreateDictionaryValue("{'minimum':0,'maximum':5}").get(),
+                  &prop, nullptr);
+  EXPECT_JSON_EQ("{'maximum':5.0,'minimum':0.0}", *param2.ToJson(false, false));
+  param2.FromJson(CreateDictionaryValue("{'default':12.3}").get(), &prop,
+                  nullptr);
+  EXPECT_JSON_EQ("{'default':12.3}", *param2.ToJson(false, false));
+}
+
+TEST(CommandSchema, DoublePropType_FromJson) {
+  DoublePropType prop;
+  prop.AddMinMaxConstraint(2.5, 8.7);
+  DoublePropType param2;
+  param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+  EXPECT_FALSE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_DOUBLE_EQ(2.5, prop.GetMinValue());
+  EXPECT_DOUBLE_EQ(8.7, prop.GetMaxValue());
+  prop.AddMinMaxConstraint(-2.2, 30.4);
+  param2.FromJson(CreateDictionaryValue("{'minimum':7}").get(), &prop, nullptr);
+  EXPECT_TRUE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_DOUBLE_EQ(7.0, param2.GetMinValue());
+  EXPECT_DOUBLE_EQ(30.4, param2.GetMaxValue());
+  param2.FromJson(CreateDictionaryValue("{'maximum':17.2}").get(), &prop,
+                  nullptr);
+  EXPECT_TRUE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_DOUBLE_EQ(-2.2, param2.GetMinValue());
+  EXPECT_DOUBLE_EQ(17.2, param2.GetMaxValue());
+  param2.FromJson(CreateDictionaryValue("{'minimum':0,'maximum':6.1}").get(),
+                  &prop, nullptr);
+  EXPECT_TRUE(param2.HasOverriddenAttributes());
+  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) {
+  DoublePropType prop;
+  prop.AddMinMaxConstraint(-1.2, 1.3);
+  ErrorPtr error;
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("-2"), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("-1.3"), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  EXPECT_TRUE(ValidateValue(prop, *CreateValue("-1.2"), &error));
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_TRUE(ValidateValue(prop, *CreateValue("0.0"), &error));
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_TRUE(ValidateValue(prop, *CreateValue("1.3"), &error));
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("1.31"), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("true"), &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("'0.0'"), &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+}
+
+TEST(CommandSchema, DoublePropType_CreateValue) {
+  DoublePropType prop;
+  ErrorPtr error;
+  auto val = prop.CreateValue(base::FundamentalValue{2.0}, &error);
+  ASSERT_NE(nullptr, val.get());
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_DOUBLE_EQ(2.0, val->GetValue());
+
+  val = prop.CreateValue(base::StringValue{"blah"}, &error);
+  EXPECT_EQ(nullptr, val.get());
+  ASSERT_NE(nullptr, error.get());
+  EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TEST(CommandSchema, StringPropType_Empty) {
+  StringPropType prop;
+  EXPECT_EQ(0, prop.GetMinLength());
+  EXPECT_EQ((std::numeric_limits<int>::max)(), prop.GetMaxLength());
+  EXPECT_FALSE(prop.HasOverriddenAttributes());
+  EXPECT_FALSE(prop.IsBasedOnSchema());
+  EXPECT_EQ(nullptr, prop.GetDefaultValue());
+  EXPECT_FALSE(prop.IsRequired());
+}
+
+TEST(CommandSchema, StringPropType_Types) {
+  StringPropType prop;
+  EXPECT_EQ(nullptr, prop.GetInt());
+  EXPECT_EQ(nullptr, prop.GetBoolean());
+  EXPECT_EQ(nullptr, prop.GetDouble());
+  EXPECT_EQ(&prop, prop.GetString());
+  EXPECT_EQ(nullptr, prop.GetObject());
+  EXPECT_EQ(nullptr, prop.GetArray());
+}
+
+TEST(CommandSchema, StringPropType_ToJson) {
+  StringPropType prop;
+  EXPECT_JSON_EQ("'string'", *prop.ToJson(false, false));
+  EXPECT_JSON_EQ("{'type':'string'}", *prop.ToJson(true, false));
+  StringPropType param2;
+  param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+  EXPECT_JSON_EQ("{}", *param2.ToJson(false, false));
+  param2.FromJson(CreateDictionaryValue("{'minLength':3}").get(), &prop,
+                  nullptr);
+  EXPECT_JSON_EQ("{'minLength':3}", *param2.ToJson(false, false));
+  param2.FromJson(CreateDictionaryValue("{'maxLength':7}").get(), &prop,
+                  nullptr);
+  EXPECT_JSON_EQ("{'maxLength':7}", *param2.ToJson(false, false));
+  param2.FromJson(CreateDictionaryValue("{'minLength':0,'maxLength':5}").get(),
+                  &prop, nullptr);
+  EXPECT_JSON_EQ("{'maxLength':5,'minLength':0}", *param2.ToJson(false, false));
+  param2.FromJson(CreateDictionaryValue("{'default':'abcd'}").get(), &prop,
+                  nullptr);
+  EXPECT_JSON_EQ("{'default':'abcd'}", *param2.ToJson(false, false));
+}
+
+TEST(CommandSchema, StringPropType_FromJson) {
+  StringPropType prop;
+  prop.AddLengthConstraint(2, 8);
+  StringPropType param2;
+  param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+  EXPECT_FALSE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_EQ(2, prop.GetMinLength());
+  EXPECT_EQ(8, prop.GetMaxLength());
+  prop.AddLengthConstraint(3, 5);
+  param2.FromJson(CreateDictionaryValue("{'minLength':4}").get(), &prop,
+                  nullptr);
+  EXPECT_TRUE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_EQ(4, param2.GetMinLength());
+  EXPECT_EQ(5, param2.GetMaxLength());
+  param2.FromJson(CreateDictionaryValue("{'maxLength':8}").get(), &prop,
+                  nullptr);
+  EXPECT_TRUE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_EQ(3, param2.GetMinLength());
+  EXPECT_EQ(8, param2.GetMaxLength());
+  param2.FromJson(CreateDictionaryValue("{'minLength':1,'maxLength':7}").get(),
+                  &prop, nullptr);
+  EXPECT_TRUE(param2.HasOverriddenAttributes());
+  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) {
+  StringPropType prop;
+  prop.AddLengthConstraint(1, 3);
+  ErrorPtr error;
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("''"), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  prop.AddLengthConstraint(2, 3);
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("''"), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("'a'"), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  EXPECT_TRUE(ValidateValue(prop, *CreateValue("'ab'"), &error));
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_TRUE(ValidateValue(prop, *CreateValue("'abc'"), &error));
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("'abcd'"), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+
+  prop.FromJson(CreateDictionaryValue("{'enum':['abc','def','xyz!!']}").get(),
+                nullptr, &error);
+  EXPECT_TRUE(ValidateValue(prop, *CreateValue("'abc'"), &error));
+  EXPECT_TRUE(ValidateValue(prop, *CreateValue("'def'"), &error));
+  EXPECT_TRUE(ValidateValue(prop, *CreateValue("'xyz!!'"), &error));
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("'xyz'"), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+}
+
+TEST(CommandSchema, StringPropType_CreateValue) {
+  StringPropType prop;
+  ErrorPtr error;
+  auto val = prop.CreateValue(base::StringValue{"blah"}, &error);
+  ASSERT_NE(nullptr, val.get());
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_EQ("blah", val->GetValue());
+
+  val = prop.CreateValue(base::FundamentalValue{4}, &error);
+  EXPECT_EQ(nullptr, val.get());
+  ASSERT_NE(nullptr, error.get());
+  EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TEST(CommandSchema, ObjectPropType_Empty) {
+  ObjectPropType prop;
+  EXPECT_TRUE(prop.HasOverriddenAttributes());
+  EXPECT_FALSE(prop.IsBasedOnSchema());
+  EXPECT_EQ(nullptr, prop.GetDefaultValue());
+  EXPECT_FALSE(prop.IsRequired());
+}
+
+TEST(CommandSchema, ObjectPropType_Types) {
+  ObjectPropType prop;
+  EXPECT_EQ(nullptr, prop.GetInt());
+  EXPECT_EQ(nullptr, prop.GetBoolean());
+  EXPECT_EQ(nullptr, prop.GetDouble());
+  EXPECT_EQ(nullptr, prop.GetString());
+  EXPECT_EQ(&prop, prop.GetObject());
+  EXPECT_EQ(nullptr, prop.GetArray());
+}
+
+TEST(CommandSchema, ObjectPropType_ToJson) {
+  ObjectPropType prop;
+  EXPECT_JSON_EQ("{'additionalProperties':false,'properties':{}}",
+                 *prop.ToJson(false, false));
+  EXPECT_JSON_EQ(
+      "{'additionalProperties':false,'properties':{},'type':'object'}",
+      *prop.ToJson(true, false));
+  EXPECT_FALSE(prop.IsBasedOnSchema());
+  ObjectPropType prop2;
+  prop2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+  EXPECT_JSON_EQ("{}", *prop2.ToJson(false, false));
+  EXPECT_TRUE(prop2.IsBasedOnSchema());
+
+  auto schema = ObjectSchema::Create();
+  schema->AddProp("expires", PropType::Create(ValueType::Int));
+  auto pw = PropType::Create(ValueType::String);
+  pw->GetString()->AddLengthConstraint(6, 100);
+  schema->AddProp("password", std::move(pw));
+  prop2.SetObjectSchema(std::move(schema));
+  auto expected = R"({
+    'additionalProperties': false,
+    'properties': {
+      'expires': 'integer',
+      'password': {
+        'maxLength': 100,
+        'minLength': 6
+      }
+    }
+  })";
+  EXPECT_JSON_EQ(expected, *prop2.ToJson(false, false));
+
+  expected = R"({
+    'additionalProperties': false,
+    'properties': {
+      'expires': {
+        'type': 'integer'
+      },
+      'password': {
+        'maxLength': 100,
+        'minLength': 6,
+        'type': 'string'
+      }
+    },
+    'type': 'object'
+  })";
+  EXPECT_JSON_EQ(expected, *prop2.ToJson(true, false));
+
+  ObjectPropType prop3;
+  ASSERT_TRUE(
+      prop3.FromJson(CreateDictionaryValue(
+                         "{'default':{'expires':3,'password':'abracadabra'}}")
+                         .get(),
+                     &prop2, nullptr));
+  expected = R"({
+    'default': {
+      'expires': 3,
+      'password': 'abracadabra'
+    }
+  })";
+  EXPECT_JSON_EQ(expected, *prop3.ToJson(false, false));
+
+  expected = R"({
+    'additionalProperties': false,
+    'default': {
+      'expires': 3,
+      'password': 'abracadabra'
+    },
+    'properties': {
+      'expires': {
+        'type': 'integer'
+      },
+      'password': {
+        'maxLength': 100,
+        'minLength': 6,
+        'type': 'string'
+      }
+    },
+    'type': 'object'
+  })";
+  EXPECT_JSON_EQ(expected, *prop3.ToJson(true, false));
+
+  ObjectPropType prop4;
+  ASSERT_TRUE(prop4.FromJson(
+      CreateDictionaryValue("{'additionalProperties':true,"
+                            "'default':{'expires':3,'password':'abracadabra'}}")
+          .get(),
+      &prop2, nullptr));
+  expected = R"({
+    'additionalProperties': true,
+    'default': {
+      'expires': 3,
+      'password': 'abracadabra'
+    },
+    'properties': {
+      'expires': 'integer',
+      'password': {
+        'maxLength': 100,
+        'minLength': 6
+      }
+    }
+  })";
+  EXPECT_JSON_EQ(expected, *prop4.ToJson(false, false));
+
+  expected = R"({
+    'additionalProperties': true,
+    'default': {
+      'expires': 3,
+      'password': 'abracadabra'
+    },
+    'properties': {
+      'expires': {
+        'type': 'integer'
+      },
+      'password': {
+        'maxLength': 100,
+        'minLength': 6,
+        'type': 'string'
+      }
+    },
+    'type': 'object'
+  })";
+  EXPECT_JSON_EQ(expected, *prop4.ToJson(true, false));
+}
+
+TEST(CommandSchema, ObjectPropType_FromJson) {
+  ObjectPropType base_prop;
+  EXPECT_TRUE(base_prop.FromJson(
+      CreateDictionaryValue("{'properties':{'name':'string','age':'integer'}}")
+          .get(),
+      nullptr, nullptr));
+  auto schema = base_prop.GetObject()->GetObjectSchemaPtr();
+  const PropType* prop = schema->GetProp("name");
+  EXPECT_EQ(ValueType::String, prop->GetType());
+  prop = schema->GetProp("age");
+  EXPECT_EQ(ValueType::Int, prop->GetType());
+
+  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 ObjectValue* defval = prop2.GetDefaultValue()->GetObject();
+  ASSERT_NE(nullptr, defval);
+  ValueMap objval = defval->GetValue();
+  EXPECT_EQ("Bob", objval["name"]->GetString()->GetValue());
+  EXPECT_EQ(33, objval["age"]->GetInt()->GetValue());
+}
+
+TEST(CommandSchema, ObjectPropType_Validate) {
+  ObjectPropType prop;
+  prop.FromJson(
+      CreateDictionaryValue("{'properties':{'expires':'integer',"
+                            "'password':{'maxLength':100,'minLength':6}},"
+                            "'required':['expires','password']}")
+          .get(),
+      nullptr, nullptr);
+  ErrorPtr error;
+  EXPECT_TRUE(ValidateValue(
+      prop, *CreateValue("{'expires':10,'password':'abcdef'}"), &error));
+  error.reset();
+
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("{'expires':10}"), &error));
+  EXPECT_EQ("parameter_missing", error->GetCode());
+  error.reset();
+
+  EXPECT_FALSE(
+      ValidateValue(prop, *CreateValue("{'password':'abcdef'}"), &error));
+  EXPECT_EQ("parameter_missing", error->GetCode());
+  error.reset();
+
+  EXPECT_FALSE(ValidateValue(
+      prop, *CreateValue("{'expires':10,'password':'abcde'}"), &error));
+  EXPECT_EQ("out_of_range", error->GetFirstError()->GetCode());
+  error.reset();
+
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("2"), &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+  error.reset();
+
+  EXPECT_FALSE(ValidateValue(
+      prop, *CreateValue("{'expires':10,'password':'abcdef','retry':true}"),
+      &error));
+  EXPECT_EQ("unexpected_parameter", error->GetCode());
+  error.reset();
+}
+
+TEST(CommandSchema, ObjectPropType_Validate_Enum) {
+  ObjectPropType prop;
+  EXPECT_TRUE(prop.FromJson(
+      CreateDictionaryValue(
+          "{'properties':{'width':'integer','height':'integer'},"
+          "'enum':[{'width':10,'height':20},{'width':100,'height':200}]}")
+          .get(),
+      nullptr, nullptr));
+  ErrorPtr error;
+  EXPECT_TRUE(
+      ValidateValue(prop, *CreateValue("{'height':20,'width':10}"), &error));
+  error.reset();
+
+  EXPECT_TRUE(
+      ValidateValue(prop, *CreateValue("{'height':200,'width':100}"), &error));
+  error.reset();
+
+  EXPECT_FALSE(
+      ValidateValue(prop, *CreateValue("{'height':12,'width':10}"), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+}
+
+TEST(CommandSchema, ObjectPropType_CreateValue) {
+  ObjectPropType prop;
+  IntPropType int_type;
+  ASSERT_TRUE(prop.FromJson(
+      CreateDictionaryValue(
+          "{'properties':{'width':'integer','height':'integer'},"
+          "'enum':[{'width':10,'height':20},{'width':100,'height':200}]}")
+          .get(),
+      nullptr, nullptr));
+  ValueMap obj{
+      {"width", int_type.CreateValue(base::FundamentalValue{10}, nullptr)},
+      {"height", int_type.CreateValue(base::FundamentalValue{20}, nullptr)},
+  };
+
+  ErrorPtr error;
+  auto val = prop.CreateValue(
+      *CreateDictionaryValue("{'width': 10, 'height': 20}"), &error);
+  ASSERT_NE(nullptr, val.get());
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_EQ(obj, val->GetValue());
+
+  val = prop.CreateValue(base::StringValue{"blah"}, &error);
+  EXPECT_EQ(nullptr, val.get());
+  ASSERT_NE(nullptr, error.get());
+  EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TEST(CommandSchema, ArrayPropType_Empty) {
+  ArrayPropType prop;
+  EXPECT_FALSE(prop.HasOverriddenAttributes());
+  EXPECT_FALSE(prop.IsBasedOnSchema());
+  EXPECT_EQ(nullptr, prop.GetDefaultValue());
+  EXPECT_EQ(nullptr, prop.GetItemTypePtr());
+  prop.SetItemType(PropType::Create(ValueType::Int));
+  EXPECT_TRUE(prop.HasOverriddenAttributes());
+  EXPECT_FALSE(prop.IsBasedOnSchema());
+  EXPECT_NE(nullptr, prop.GetItemTypePtr());
+  EXPECT_FALSE(prop.IsRequired());
+}
+
+TEST(CommandSchema, ArrayPropType_Types) {
+  ArrayPropType prop;
+  EXPECT_EQ(nullptr, prop.GetInt());
+  EXPECT_EQ(nullptr, prop.GetBoolean());
+  EXPECT_EQ(nullptr, prop.GetDouble());
+  EXPECT_EQ(nullptr, prop.GetString());
+  EXPECT_EQ(nullptr, prop.GetObject());
+  EXPECT_EQ(&prop, prop.GetArray());
+}
+
+TEST(CommandSchema, ArrayPropType_ToJson) {
+  ArrayPropType prop;
+  prop.SetItemType(PropType::Create(ValueType::Int));
+  EXPECT_JSON_EQ("{'items':'integer'}", *prop.ToJson(false, false));
+  EXPECT_JSON_EQ("{'items':{'type':'integer'},'type':'array'}",
+                 *prop.ToJson(true, false));
+  EXPECT_FALSE(prop.IsBasedOnSchema());
+  ArrayPropType prop2;
+  prop2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+  EXPECT_JSON_EQ("{}", *prop2.ToJson(false, false));
+  EXPECT_TRUE(prop2.IsBasedOnSchema());
+  prop2.FromJson(CreateDictionaryValue("{'default':[1,2,3]}").get(), &prop,
+                 nullptr);
+  EXPECT_JSON_EQ("{'default':[1,2,3]}", *prop2.ToJson(false, false));
+  EXPECT_JSON_EQ(
+      "{'default':[1,2,3],'items':{'type':'integer'},'type':'array'}",
+      *prop2.ToJson(true, false));
+}
+
+TEST(CommandSchema, ArrayPropType_FromJson) {
+  ArrayPropType prop;
+  EXPECT_TRUE(prop.FromJson(CreateDictionaryValue("{'items':'integer'}").get(),
+                            nullptr, nullptr));
+  EXPECT_EQ(ValueType::Int, prop.GetItemTypePtr()->GetType());
+
+  ArrayPropType prop2;
+  ASSERT_TRUE(
+      prop2.FromJson(CreateDictionaryValue(
+                         "{'items':'string','default':['foo', 'bar', 'baz']}")
+                         .get(),
+                     nullptr, nullptr));
+  ASSERT_NE(nullptr, prop2.GetDefaultValue());
+  const ArrayValue* defval = prop2.GetDefaultValue()->GetArray();
+  ASSERT_NE(nullptr, defval);
+  EXPECT_EQ((std::vector<std::string>{"foo", "bar", "baz"}),
+            GetArrayValues<std::string>(defval->GetValue()));
+}
+
+TEST(CommandSchema, ArrayPropType_Validate) {
+  ArrayPropType prop;
+  prop.FromJson(
+      CreateDictionaryValue("{'items':{'minimum':2.3, 'maximum':10.5}}").get(),
+      nullptr, nullptr);
+
+  ErrorPtr error;
+  EXPECT_TRUE(ValidateValue(prop, *CreateValue("[3,4,10.5]"), &error));
+  error.reset();
+
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("[2]"), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("[4, 5, 20]"), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+}
+
+TEST(CommandSchema, ArrayPropType_Validate_Enum) {
+  ArrayPropType prop;
+  prop.FromJson(
+      CreateDictionaryValue("{'items':'integer', 'enum':[[1], [2,3], [4,5,6]]}")
+          .get(),
+      nullptr, nullptr);
+
+  ErrorPtr error;
+  EXPECT_TRUE(ValidateValue(prop, *CreateValue("[2,3]"), &error));
+  error.reset();
+
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("[2]"), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+
+  EXPECT_FALSE(ValidateValue(prop, *CreateValue("[2,3,4]"), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+}
+
+TEST(CommandSchema, ArrayPropType_CreateValue) {
+  ArrayPropType prop;
+  ASSERT_TRUE(prop.FromJson(
+      CreateDictionaryValue(
+          "{'items':{'properties':{'width':'integer','height':'integer'}}}")
+          .get(),
+      nullptr, nullptr));
+
+  ErrorPtr error;
+  ValueVector arr;
+
+  auto val = prop.CreateValue(base::ListValue{}, &error);
+  ASSERT_NE(nullptr, val.get());
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_EQ(arr, val->GetValue());
+  EXPECT_JSON_EQ("[]", *val->ToJson());
+
+  val = prop.CreateValue(
+      *CreateValue("[{'height':20,'width':10},{'width':17, 'height':18}]"),
+      &error);
+  ASSERT_NE(nullptr, val.get());
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_JSON_EQ("[{'height':20,'width':10},{'height':18,'width':17}]",
+                 *val->ToJson());
+
+  val = prop.CreateValue(base::StringValue{"blah"}, &error);
+  EXPECT_EQ(nullptr, val.get());
+  ASSERT_NE(nullptr, error.get());
+  EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+}
+
+TEST(CommandSchema, ArrayPropType_NestedArrays_NotSupported) {
+  ArrayPropType prop;
+  ErrorPtr error;
+  EXPECT_FALSE(prop.FromJson(
+      CreateDictionaryValue("{'items':{'items':'integer'}}").get(), nullptr,
+      &error));
+  EXPECT_EQ(errors::commands::kInvalidObjectSchema, error->GetCode());
+  error.reset();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TEST(CommandSchema, ObjectSchema_FromJson_Shorthand_TypeName) {
+  ObjectSchema schema;
+  const char* schema_str =
+      "{"
+      "'param1':'integer',"
+      "'param2':'number',"
+      "'param3':'string'"
+      "}";
+  EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                              nullptr));
+  EXPECT_EQ(ValueType::Int, schema.GetProp("param1")->GetType());
+  EXPECT_EQ(ValueType::Double, schema.GetProp("param2")->GetType());
+  EXPECT_EQ(ValueType::String, schema.GetProp("param3")->GetType());
+  EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param2")->GetTypeAsString());
+  EXPECT_EQ("string", schema.GetProp("param3")->GetTypeAsString());
+  EXPECT_EQ(nullptr, schema.GetProp("param4"));
+
+  int min_int = (std::numeric_limits<int>::min)();
+  int max_int = (std::numeric_limits<int>::max)();
+  double min_dbl = (std::numeric_limits<double>::lowest)();
+  double max_dbl = (std::numeric_limits<double>::max)();
+  EXPECT_EQ(min_int, schema.GetProp("param1")->GetInt()->GetMinValue());
+  EXPECT_EQ(max_int, schema.GetProp("param1")->GetInt()->GetMaxValue());
+  EXPECT_EQ(min_dbl, schema.GetProp("param2")->GetDouble()->GetMinValue());
+  EXPECT_EQ(max_dbl, schema.GetProp("param2")->GetDouble()->GetMaxValue());
+  EXPECT_EQ(0, schema.GetProp("param3")->GetString()->GetMinLength());
+  EXPECT_EQ(max_int, schema.GetProp("param3")->GetString()->GetMaxLength());
+}
+
+TEST(CommandSchema, ObjectSchema_FromJson_Full_TypeName) {
+  ObjectSchema schema;
+  const char* schema_str =
+      "{"
+      "'param1':{'type':'integer'},"
+      "'param2':{'type':'number'},"
+      "'param3':{'type':'string'},"
+      "'param4':{'type':'array', 'items':'integer'},"
+      "'param5':{'type':'object', 'properties':{'p1':'integer'}}"
+      "}";
+  EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                              nullptr));
+  EXPECT_EQ(ValueType::Int, schema.GetProp("param1")->GetType());
+  EXPECT_EQ(ValueType::Double, schema.GetProp("param2")->GetType());
+  EXPECT_EQ(ValueType::String, schema.GetProp("param3")->GetType());
+  EXPECT_EQ(ValueType::Array, schema.GetProp("param4")->GetType());
+  EXPECT_EQ(ValueType::Object, schema.GetProp("param5")->GetType());
+  EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param2")->GetTypeAsString());
+  EXPECT_EQ("string", schema.GetProp("param3")->GetTypeAsString());
+  EXPECT_EQ("array", schema.GetProp("param4")->GetTypeAsString());
+  EXPECT_EQ("object", schema.GetProp("param5")->GetTypeAsString());
+  EXPECT_EQ(nullptr, schema.GetProp("param77"));
+
+  int min_int = (std::numeric_limits<int>::min)();
+  int max_int = (std::numeric_limits<int>::max)();
+  double min_dbl = (std::numeric_limits<double>::lowest)();
+  double max_dbl = (std::numeric_limits<double>::max)();
+  EXPECT_EQ(min_int, schema.GetProp("param1")->GetInt()->GetMinValue());
+  EXPECT_EQ(max_int, schema.GetProp("param1")->GetInt()->GetMaxValue());
+  EXPECT_EQ(min_dbl, schema.GetProp("param2")->GetDouble()->GetMinValue());
+  EXPECT_EQ(max_dbl, schema.GetProp("param2")->GetDouble()->GetMaxValue());
+  EXPECT_EQ(0, schema.GetProp("param3")->GetString()->GetMinLength());
+  EXPECT_EQ(max_int, schema.GetProp("param3")->GetString()->GetMaxLength());
+}
+
+TEST(CommandSchema, ObjectSchema_FromJson_Shorthand_TypeDeduction_Scalar) {
+  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},"
+      "'param12':{'default':12},"
+      "'param13':{'default':13.5},"
+      "'param14':{'default':true},"
+      "'param15':{'default':false},"
+      "'param16':{'default':'foobar'},"
+      "'param17':{'default':[1,2,3]},"
+      "'param18':{'items':'number', 'default':[]}"
+      "}";
+  EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                              nullptr));
+  EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString());
+  EXPECT_EQ("integer", schema.GetProp("param2")->GetTypeAsString());
+  EXPECT_EQ("integer", schema.GetProp("param3")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param4")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param5")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param6")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param7")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param8")->GetTypeAsString());
+  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());
+  EXPECT_EQ("array", schema.GetProp("param17")->GetTypeAsString());
+  auto prop17 = schema.GetProp("param17");
+  EXPECT_EQ("integer", prop17->GetArray()->GetItemTypePtr()->GetTypeAsString());
+  EXPECT_EQ("array", schema.GetProp("param18")->GetTypeAsString());
+  auto prop18 = schema.GetProp("param18");
+  EXPECT_EQ("number", prop18->GetArray()->GetItemTypePtr()->GetTypeAsString());
+
+  int min_int = (std::numeric_limits<int>::min)();
+  int max_int = (std::numeric_limits<int>::max)();
+  double min_dbl = (std::numeric_limits<double>::lowest)();
+  double max_dbl = (std::numeric_limits<double>::max)();
+  EXPECT_EQ(2, schema.GetProp("param1")->GetInt()->GetMinValue());
+  EXPECT_EQ(max_int, schema.GetProp("param1")->GetInt()->GetMaxValue());
+  EXPECT_EQ(min_int, schema.GetProp("param2")->GetInt()->GetMinValue());
+  EXPECT_EQ(10, schema.GetProp("param2")->GetInt()->GetMaxValue());
+  EXPECT_EQ(2, schema.GetProp("param3")->GetInt()->GetMinValue());
+  EXPECT_EQ(8, schema.GetProp("param3")->GetInt()->GetMaxValue());
+  EXPECT_DOUBLE_EQ(2.1, schema.GetProp("param4")->GetDouble()->GetMinValue());
+  EXPECT_DOUBLE_EQ(max_dbl,
+                   schema.GetProp("param4")->GetDouble()->GetMaxValue());
+  EXPECT_DOUBLE_EQ(min_dbl,
+                   schema.GetProp("param5")->GetDouble()->GetMinValue());
+  EXPECT_DOUBLE_EQ(10.1, schema.GetProp("param5")->GetDouble()->GetMaxValue());
+  EXPECT_DOUBLE_EQ(3.1, schema.GetProp("param6")->GetDouble()->GetMinValue());
+  EXPECT_DOUBLE_EQ(8.1, schema.GetProp("param6")->GetDouble()->GetMaxValue());
+  EXPECT_DOUBLE_EQ(3.1, schema.GetProp("param7")->GetDouble()->GetMinValue());
+  EXPECT_DOUBLE_EQ(8.0, schema.GetProp("param7")->GetDouble()->GetMaxValue());
+  EXPECT_DOUBLE_EQ(3.0, schema.GetProp("param8")->GetDouble()->GetMinValue());
+  EXPECT_DOUBLE_EQ(8.1, schema.GetProp("param8")->GetDouble()->GetMaxValue());
+  EXPECT_EQ(2, schema.GetProp("param9")->GetString()->GetMinLength());
+  EXPECT_EQ(max_int, schema.GetProp("param9")->GetString()->GetMaxLength());
+  EXPECT_EQ(0, schema.GetProp("param10")->GetString()->GetMinLength());
+  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 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());
+  val = schema.GetProp("param17")->GetDefaultValue();
+  EXPECT_EQ((std::vector<int>{1, 2, 3}),
+            GetArrayValues<int>(val->GetArray()->GetValue()));
+  val = schema.GetProp("param18")->GetDefaultValue();
+  EXPECT_TRUE(val->GetArray()->GetValue().empty());
+}
+
+TEST(CommandSchema, ObjectSchema_FromJson_Shorthand_TypeDeduction_Array) {
+  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':[]},"
+      "'param11':[[0,1],[2,3]],"
+      "'param12':[['foo','bar']],"
+      "'param13':{'enum':[['id0', 'id1']]}"
+      "}";
+  EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                              nullptr));
+  EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param2")->GetTypeAsString());
+  EXPECT_EQ("string", schema.GetProp("param3")->GetTypeAsString());
+  EXPECT_EQ("integer", schema.GetProp("param4")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param5")->GetTypeAsString());
+  EXPECT_EQ("string", schema.GetProp("param6")->GetTypeAsString());
+  EXPECT_EQ("integer", schema.GetProp("param7")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param8")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param9")->GetTypeAsString());
+  EXPECT_EQ("integer", schema.GetProp("param10")->GetTypeAsString());
+
+  auto prop_type11 = schema.GetProp("param11");
+  EXPECT_EQ("array", prop_type11->GetTypeAsString());
+  EXPECT_EQ("integer",
+            prop_type11->GetArray()->GetItemTypePtr()->GetTypeAsString());
+
+  auto prop_type12 = schema.GetProp("param12");
+  EXPECT_EQ("array", prop_type12->GetTypeAsString());
+  EXPECT_EQ("string",
+            prop_type12->GetArray()->GetItemTypePtr()->GetTypeAsString());
+
+  auto prop_type13 = schema.GetProp("param13");
+  EXPECT_EQ("array", prop_type12->GetTypeAsString());
+  EXPECT_EQ("string",
+            prop_type13->GetArray()->GetItemTypePtr()->GetTypeAsString());
+
+  EXPECT_EQ((std::vector<int>{0, 1, 2, 3}),
+            GetOneOfValues<int>(schema.GetProp("param1")));
+  EXPECT_EQ((std::vector<double>{0.0, 1.1, 2.2}),
+            GetOneOfValues<double>(schema.GetProp("param2")));
+  EXPECT_EQ((std::vector<std::string>{"id1", "id2"}),
+            GetOneOfValues<std::string>(schema.GetProp("param3")));
+
+  EXPECT_EQ((std::vector<int>{1, 2, 3}),
+            GetOneOfValues<int>(schema.GetProp("param4")));
+  EXPECT_EQ((std::vector<double>{-1.1, 2.2, 3.0}),
+            GetOneOfValues<double>(schema.GetProp("param5")));
+  EXPECT_EQ((std::vector<std::string>{"id0", "id1"}),
+            GetOneOfValues<std::string>(schema.GetProp("param6")));
+  EXPECT_EQ((std::vector<int>{1, 2, 3}),
+            GetOneOfValues<int>(schema.GetProp("param7")));
+  EXPECT_EQ((std::vector<double>{1.0, 2.0, 3.0}),
+            GetOneOfValues<double>(schema.GetProp("param8")));
+  EXPECT_TRUE(GetOneOfValues<double>(schema.GetProp("param9")).empty());
+  EXPECT_TRUE(GetOneOfValues<int>(schema.GetProp("param10")).empty());
+}
+
+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},"
+      "'param20':{'default':49},"
+      "'param21':{'default':49},"
+      "'param22':'integer'"
+      "}";
+  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':{},"
+      "'param20':{},"
+      "'param21':{'default':8},"
+      "'param22':{'default':123}"
+      "}";
+  ObjectSchema schema;
+  EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(),
+                              &base_schema, nullptr));
+  EXPECT_EQ(nullptr, schema.GetProp("param0"));
+  EXPECT_NE(nullptr, schema.GetProp("param1"));
+  EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString());
+  EXPECT_EQ(1, schema.GetProp("param1")->GetInt()->GetMinValue());
+  EXPECT_EQ(5, schema.GetProp("param1")->GetInt()->GetMaxValue());
+  EXPECT_EQ("integer", schema.GetProp("param2")->GetTypeAsString());
+  EXPECT_EQ(2, schema.GetProp("param2")->GetInt()->GetMinValue());
+  EXPECT_EQ(5, schema.GetProp("param2")->GetInt()->GetMaxValue());
+  EXPECT_EQ("integer", schema.GetProp("param3")->GetTypeAsString());
+  EXPECT_EQ(1, schema.GetProp("param3")->GetInt()->GetMinValue());
+  EXPECT_EQ(9, schema.GetProp("param3")->GetInt()->GetMaxValue());
+  EXPECT_EQ("integer", schema.GetProp("param4")->GetTypeAsString());
+  EXPECT_EQ(2, schema.GetProp("param4")->GetInt()->GetMinValue());
+  EXPECT_EQ(9, schema.GetProp("param4")->GetInt()->GetMaxValue());
+  EXPECT_EQ("number", schema.GetProp("param5")->GetTypeAsString());
+  EXPECT_EQ(1.1, schema.GetProp("param5")->GetDouble()->GetMinValue());
+  EXPECT_EQ(5.5, schema.GetProp("param5")->GetDouble()->GetMaxValue());
+  EXPECT_EQ("number", schema.GetProp("param6")->GetTypeAsString());
+  EXPECT_EQ(2.2, schema.GetProp("param6")->GetDouble()->GetMinValue());
+  EXPECT_EQ(5.5, schema.GetProp("param6")->GetDouble()->GetMaxValue());
+  EXPECT_EQ("number", schema.GetProp("param7")->GetTypeAsString());
+  EXPECT_EQ(1.1, schema.GetProp("param7")->GetDouble()->GetMinValue());
+  EXPECT_EQ(9.9, schema.GetProp("param7")->GetDouble()->GetMaxValue());
+  EXPECT_EQ("number", schema.GetProp("param8")->GetTypeAsString());
+  EXPECT_EQ(2.2, schema.GetProp("param8")->GetDouble()->GetMinValue());
+  EXPECT_EQ(9.9, schema.GetProp("param8")->GetDouble()->GetMaxValue());
+  EXPECT_EQ("string", schema.GetProp("param9")->GetTypeAsString());
+  EXPECT_EQ(1, schema.GetProp("param9")->GetString()->GetMinLength());
+  EXPECT_EQ(5, schema.GetProp("param9")->GetString()->GetMaxLength());
+  EXPECT_EQ("string", schema.GetProp("param10")->GetTypeAsString());
+  EXPECT_EQ(3, schema.GetProp("param10")->GetString()->GetMinLength());
+  EXPECT_EQ(5, schema.GetProp("param10")->GetString()->GetMaxLength());
+  EXPECT_EQ("string", schema.GetProp("param11")->GetTypeAsString());
+  EXPECT_EQ(1, schema.GetProp("param11")->GetString()->GetMinLength());
+  EXPECT_EQ(8, schema.GetProp("param11")->GetString()->GetMaxLength());
+  EXPECT_EQ("string", schema.GetProp("param12")->GetTypeAsString());
+  EXPECT_EQ(3, schema.GetProp("param12")->GetString()->GetMinLength());
+  EXPECT_EQ(8, schema.GetProp("param12")->GetString()->GetMaxLength());
+  EXPECT_EQ("integer", schema.GetProp("param13")->GetTypeAsString());
+  EXPECT_EQ((std::vector<int>{1, 2, 3}),
+            GetOneOfValues<int>(schema.GetProp("param13")));
+  EXPECT_EQ("integer", schema.GetProp("param14")->GetTypeAsString());
+  EXPECT_EQ((std::vector<int>{1, 2, 3, 4}),
+            GetOneOfValues<int>(schema.GetProp("param14")));
+  EXPECT_EQ("number", schema.GetProp("param15")->GetTypeAsString());
+  EXPECT_EQ((std::vector<double>{1.1, 2.2, 3.3}),
+            GetOneOfValues<double>(schema.GetProp("param15")));
+  EXPECT_EQ("number", schema.GetProp("param16")->GetTypeAsString());
+  EXPECT_EQ((std::vector<double>{1.1, 2.2, 3.3, 4.4}),
+            GetOneOfValues<double>(schema.GetProp("param16")));
+  EXPECT_EQ("string", schema.GetProp("param17")->GetTypeAsString());
+  EXPECT_EQ((std::vector<std::string>{"id1", "id2"}),
+            GetOneOfValues<std::string>(schema.GetProp("param17")));
+  EXPECT_EQ("string", schema.GetProp("param18")->GetTypeAsString());
+  EXPECT_EQ((std::vector<std::string>{"id1", "id3"}),
+            GetOneOfValues<std::string>(schema.GetProp("param18")));
+  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) {
+  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'}},"
+      "'param6':{'default':[1,2,3]}"
+      "}}";
+  ASSERT_TRUE(
+      prop.FromJson(CreateDictionaryValue(schema_str).get(), nullptr, nullptr));
+
+  // Omit all.
+  auto value =
+      prop.CreatePropValue(*CreateDictionaryValue("{}").get(), nullptr);
+  ASSERT_NE(nullptr, value);
+  ValueMap 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());
+  ValueMap param5 = obj["param5"]->GetObject()->GetValue();
+  EXPECT_EQ(5, param5["x"]->GetInt()->GetValue());
+  EXPECT_EQ(6, param5["y"]->GetInt()->GetValue());
+  ValueVector param6 = obj["param6"]->GetArray()->GetValue();
+  EXPECT_EQ((std::vector<int>{1, 2, 3}), GetArrayValues<int>(param6));
+
+  // Specify some.
+  const char* val_json =
+      "{"
+      "'param1':false,"
+      "'param3':33.3,"
+      "'param5':{'x':-5,'y':-6}"
+      "}";
+  value = prop.CreatePropValue(*CreateDictionaryValue(val_json).get(), nullptr);
+  ASSERT_NE(nullptr, value);
+  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());
+  param6 = obj["param6"]->GetArray()->GetValue();
+  EXPECT_EQ((std::vector<int>{1, 2, 3}), GetArrayValues<int>(param6));
+
+  // Specify all.
+  val_json =
+      "{"
+      "'param1':false,"
+      "'param2':22,"
+      "'param3':333.3,"
+      "'param4':'FOUR',"
+      "'param5':{'x':-55,'y':66},"
+      "'param6':[-1, 0]"
+      "}";
+  value = prop.CreatePropValue(*CreateDictionaryValue(val_json).get(), nullptr);
+  ASSERT_NE(nullptr, value);
+  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());
+  param6 = obj["param6"]->GetArray()->GetValue();
+  EXPECT_EQ((std::vector<int>{-1, 0}), GetArrayValues<int>(param6));
+}
+
+TEST(CommandSchema, ObjectSchema_FromJson_BaseSchema_Failures) {
+  ObjectSchema schema;
+  ErrorPtr error;
+  const char* schema_str =
+      "{"
+      "'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'}"
+      "}";
+  EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                               &error));
+  EXPECT_EQ("unknown_type", error->GetFirstError()->GetCode());
+  error.reset();
+
+  schema_str =
+      "{"
+      "'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'}"
+      "}";
+  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]"
+      "}";
+  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.
+      "}";
+  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.
+      "}";
+  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();
+
+  schema_str =
+      "{"
+      "'param1':[[1,2.3]]"
+      "}";
+  EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                               &error));
+  EXPECT_EQ("type_mismatch", error->GetFirstError()->GetCode());
+  error.reset();
+
+  schema_str =
+      "{"
+      "'param1':[[1,2],[3,4],['blah']]"
+      "}";
+  EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                               &error));
+  EXPECT_EQ("type_mismatch", error->GetFirstError()->GetCode());
+  error.reset();
+
+  schema_str =
+      "{"
+      "'param1':{'default':[]}"
+      "}";
+  EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                               &error));
+  EXPECT_EQ("no_type_info", error->GetFirstError()->GetCode());
+  error.reset();
+
+  schema_str =
+      "{"
+      "'param1':[[[1]],[[2]]]"
+      "}";
+  EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                               &error));
+  EXPECT_EQ("no_type_info", error->GetFirstError()->GetCode());
+  error.reset();
+
+  schema_str =
+      "{"
+      "'param1':{'enum':[[['foo']]]}"
+      "}";
+  EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                               &error));
+  EXPECT_EQ("no_type_info", error->GetFirstError()->GetCode());
+  error.reset();
+
+  schema_str =
+      "{"
+      "'param1':{'default':[[1],[2]]}"
+      "}";
+  EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                               &error));
+  EXPECT_EQ("no_type_info", error->GetFirstError()->GetCode());
+  error.reset();
+}
+
+TEST(CommandSchema, RequiredProperties_Integral) {
+  IntPropType prop;
+
+  prop.MakeRequired(false);
+  EXPECT_JSON_EQ("{'type':'integer'}", *prop.ToJson(true, false));
+  EXPECT_JSON_EQ("{'isRequired':false,'type':'integer'}",
+                 *prop.ToJson(true, true));
+
+  prop.MakeRequired(true);
+  EXPECT_JSON_EQ("{'type':'integer'}", *prop.ToJson(true, false));
+  EXPECT_JSON_EQ("{'isRequired':true,'type':'integer'}",
+                 *prop.ToJson(true, true));
+
+  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));
+  EXPECT_JSON_EQ("{'isRequired':false,'type':'integer'}",
+                 *prop2.ToJson(true, true));
+}
+
+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));
+  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));
+
+  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));
+  EXPECT_JSON_EQ(expected3, *obj_type.ToJson(false, true));
+}
+
+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));
+}
+
+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));
+}
+
+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;
+  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());
+  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());
+  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 val_json = R"({
+    'param1':10,
+    'param2':20,
+    'param3':30,
+    'param4':40
+  })";
+  auto value =
+      prop.CreatePropValue(*CreateDictionaryValue(val_json).get(), nullptr);
+  ASSERT_NE(nullptr, value);
+  ValueMap 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());
+
+  val_json = "{'param1':100}";
+  value = prop.CreatePropValue(*CreateDictionaryValue(val_json).get(), nullptr);
+  ASSERT_NE(nullptr, value);
+  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 val_json = "{'param2':20}";
+  ErrorPtr error;
+  auto value =
+      prop.CreatePropValue(*CreateDictionaryValue(val_json).get(), &error);
+  ASSERT_EQ(nullptr, value);
+  EXPECT_EQ(errors::commands::kPropertyMissing, error->GetCode());
+}
+
+}  // namespace weave
diff --git a/src/commands/prop_constraints.cc b/src/commands/prop_constraints.cc
new file mode 100644
index 0000000..b7e9cf6
--- /dev/null
+++ b/src/commands/prop_constraints.cc
@@ -0,0 +1,202 @@
+// Copyright 2015 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/commands/prop_constraints.h"
+
+#include <base/json/json_writer.h>
+#include <base/logging.h>
+
+#include "src/commands/prop_values.h"
+#include "src/commands/schema_constants.h"
+#include "src/string_utils.h"
+
+namespace weave {
+
+namespace {
+
+// Helper function to convert a property value to string, which is used for
+// error reporting.
+std::string PropValueToString(const PropValue& value) {
+  std::string result;
+  auto json = value.ToJson();
+  CHECK(json);
+  base::JSONWriter::Write(*json, &result);
+  return result;
+}
+
+}  // anonymous namespace
+
+// Constraint ----------------------------------------------------------------
+Constraint::~Constraint() {}
+
+bool Constraint::ReportErrorLessThan(ErrorPtr* error,
+                                     const std::string& val,
+                                     const std::string& limit) {
+  Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                     errors::commands::kOutOfRange,
+                     "Value %s is out of range. It must not be less than %s",
+                     val.c_str(), limit.c_str());
+  return false;
+}
+
+bool Constraint::ReportErrorGreaterThan(ErrorPtr* error,
+                                        const std::string& val,
+                                        const std::string& limit) {
+  Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                     errors::commands::kOutOfRange,
+                     "Value %s is out of range. It must not be greater than %s",
+                     val.c_str(), limit.c_str());
+  return false;
+}
+
+bool Constraint::ReportErrorNotOneOf(ErrorPtr* error,
+                                     const std::string& val,
+                                     const std::vector<std::string>& values) {
+  Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                     errors::commands::kOutOfRange,
+                     "Value %s is invalid. Expected one of [%s]", val.c_str(),
+                     Join(",", values).c_str());
+  return false;
+}
+
+void Constraint::AddToJsonDict(base::DictionaryValue* dict,
+                               bool overridden_only) const {
+  if (!overridden_only || HasOverriddenAttributes()) {
+    auto value = ToJson();
+    CHECK(value);
+    dict->SetWithoutPathExpansion(GetDictKey(), value.release());
+  }
+}
+
+// ConstraintStringLength -----------------------------------------------------
+ConstraintStringLength::ConstraintStringLength(
+    const InheritableAttribute<int>& limit)
+    : limit_(limit) {}
+ConstraintStringLength::ConstraintStringLength(int limit) : limit_(limit) {}
+
+bool ConstraintStringLength::HasOverriddenAttributes() const {
+  return !limit_.is_inherited;
+}
+
+std::unique_ptr<base::Value> ConstraintStringLength::ToJson() const {
+  return TypedValueToJson(limit_.value);
+}
+
+// ConstraintStringLengthMin --------------------------------------------------
+ConstraintStringLengthMin::ConstraintStringLengthMin(
+    const InheritableAttribute<int>& limit)
+    : ConstraintStringLength(limit) {}
+ConstraintStringLengthMin::ConstraintStringLengthMin(int limit)
+    : ConstraintStringLength(limit) {}
+
+bool ConstraintStringLengthMin::Validate(const PropValue& value,
+                                         ErrorPtr* error) const {
+  CHECK(value.GetString()) << "Expecting a string value for this constraint";
+  const std::string& str = value.GetString()->GetValue();
+  int length = static_cast<int>(str.size());
+  if (length < limit_.value) {
+    if (limit_.value == 1) {
+      Error::AddTo(error, FROM_HERE, errors::commands::kDomain,
+                   errors::commands::kOutOfRange, "String must not be empty");
+    } else {
+      Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                         errors::commands::kOutOfRange,
+                         "String must be at least %d characters long,"
+                         " actual length of string '%s' is %d",
+                         limit_.value, str.c_str(), length);
+    }
+    return false;
+  }
+  return true;
+}
+
+std::unique_ptr<Constraint> ConstraintStringLengthMin::Clone() const {
+  return std::unique_ptr<Constraint>{new ConstraintStringLengthMin{limit_}};
+}
+
+std::unique_ptr<Constraint> ConstraintStringLengthMin::CloneAsInherited()
+    const {
+  return std::unique_ptr<Constraint>{
+      new ConstraintStringLengthMin{limit_.value}};
+}
+
+// ConstraintStringLengthMax --------------------------------------------------
+ConstraintStringLengthMax::ConstraintStringLengthMax(
+    const InheritableAttribute<int>& limit)
+    : ConstraintStringLength(limit) {}
+ConstraintStringLengthMax::ConstraintStringLengthMax(int limit)
+    : ConstraintStringLength(limit) {}
+
+bool ConstraintStringLengthMax::Validate(const PropValue& value,
+                                         ErrorPtr* error) const {
+  CHECK(value.GetString()) << "Expecting a string value for this constraint";
+  const std::string& str = value.GetString()->GetValue();
+  int length = static_cast<int>(str.size());
+  if (length > limit_.value) {
+    Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                       errors::commands::kOutOfRange,
+                       "String must be no more than %d character(s) "
+                       "long, actual length of string '%s' is %d",
+                       limit_.value, str.c_str(), length);
+    return false;
+  }
+  return true;
+}
+
+std::unique_ptr<Constraint> ConstraintStringLengthMax::Clone() const {
+  return std::unique_ptr<Constraint>{new ConstraintStringLengthMax{limit_}};
+}
+
+std::unique_ptr<Constraint> ConstraintStringLengthMax::CloneAsInherited()
+    const {
+  return std::unique_ptr<Constraint>{
+      new ConstraintStringLengthMax{limit_.value}};
+}
+
+// ConstraintOneOf --------------------------------------------------
+ConstraintOneOf::ConstraintOneOf(InheritableAttribute<ValueVector> set)
+    : set_(std::move(set)) {}
+ConstraintOneOf::ConstraintOneOf(ValueVector set) : set_(std::move(set)) {}
+
+bool ConstraintOneOf::Validate(const PropValue& value, ErrorPtr* error) const {
+  for (const auto& item : set_.value) {
+    if (value.IsEqual(item.get()))
+      return true;
+  }
+  std::vector<std::string> choice_list;
+  choice_list.reserve(set_.value.size());
+  for (const auto& item : set_.value) {
+    choice_list.push_back(PropValueToString(*item));
+  }
+  return ReportErrorNotOneOf(error, PropValueToString(value), choice_list);
+}
+
+std::unique_ptr<Constraint> ConstraintOneOf::Clone() const {
+  InheritableAttribute<ValueVector> attr;
+  attr.is_inherited = set_.is_inherited;
+  attr.value.reserve(set_.value.size());
+  for (const auto& prop_value : set_.value) {
+    attr.value.push_back(prop_value->Clone());
+  }
+  return std::unique_ptr<Constraint>{new ConstraintOneOf{std::move(attr)}};
+}
+
+std::unique_ptr<Constraint> ConstraintOneOf::CloneAsInherited() const {
+  ValueVector cloned;
+  cloned.reserve(set_.value.size());
+  for (const auto& prop_value : set_.value) {
+    cloned.push_back(prop_value->Clone());
+  }
+  return std::unique_ptr<Constraint>{new ConstraintOneOf{std::move(cloned)}};
+}
+
+std::unique_ptr<base::Value> ConstraintOneOf::ToJson() const {
+  return TypedValueToJson(set_.value);
+}
+
+const char* ConstraintOneOf::GetDictKey() const {
+  return commands::attributes::kOneOf_Enum;
+}
+
+}  // namespace weave
diff --git a/src/commands/prop_constraints.h b/src/commands/prop_constraints.h
new file mode 100644
index 0000000..53a4d93
--- /dev/null
+++ b/src/commands/prop_constraints.h
@@ -0,0 +1,315 @@
+// Copyright 2015 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_SRC_COMMANDS_PROP_CONSTRAINTS_H_
+#define LIBWEAVE_SRC_COMMANDS_PROP_CONSTRAINTS_H_
+
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <base/macros.h>
+#include <base/values.h>
+#include <weave/error.h>
+
+#include "src/commands/prop_values.h"
+#include "src/commands/schema_constants.h"
+#include "src/commands/schema_utils.h"
+#include "src/string_utils.h"
+
+namespace weave {
+
+enum class ConstraintType { Min, Max, StringLengthMin, StringLengthMax, OneOf };
+
+// Abstract base class for all parameter constraints. Many constraints are
+// type-dependent. Thus, a numeric parameter could have "minimum" and/or
+// "maximum" constraints specified. Some constraints, such as "OneOf" apply to
+// any data type.
+class Constraint {
+ public:
+  Constraint() = default;
+  virtual ~Constraint();
+
+  // Gets the constraint type.
+  virtual ConstraintType GetType() const = 0;
+
+  // Checks if any of the constraint properties/attributes are overridden
+  // from their base schema definition. If the constraint is inherited, then
+  // it will not be written to JSON when saving partial schema.
+  virtual bool HasOverriddenAttributes() const = 0;
+
+  // Validates a parameter against the constraint. Returns true if parameter
+  // value satisfies the constraint, otherwise fills the optional |error| with
+  // the details for the failure.
+  virtual bool Validate(const PropValue& value, ErrorPtr* error) const = 0;
+
+  // Makes a full copy of this Constraint instance.
+  virtual std::unique_ptr<Constraint> Clone() const = 0;
+
+  // Makes a copy of the constraint object, marking all the attributes
+  // as inherited from the original definition.
+  virtual std::unique_ptr<Constraint> CloneAsInherited() const = 0;
+
+  // Saves the constraint into the specified JSON |dict| object, representing
+  // the object schema. If |overridden_only| is set to true, then the
+  // inherited constraints will not be added to the schema object.
+  virtual void AddToJsonDict(base::DictionaryValue* dict,
+                             bool overridden_only) const;
+
+  // Saves the value of constraint to JSON value. E.g., if the numeric
+  // constraint was defined as {"minimum":20} this will create a JSON value
+  // of 20. The current design implies that each constraint has one value
+  // only. If this assumption changes, this interface needs to be updated
+  // accordingly.
+  virtual std::unique_ptr<base::Value> ToJson() const = 0;
+
+  // Overloaded by the concrete class implementation, it should return the
+  // JSON object property name to store the constraint's value as.
+  // E.g., if the numeric constraint was defined as {"minimum":20} this
+  // method should return "minimum".
+  virtual const char* GetDictKey() const = 0;
+
+ protected:
+  // Static helper methods to format common constraint validation errors.
+  // They fill the |error| object with specific error message.
+  // Since these functions could be used by constraint objects for various
+  // data types, the values used in validation are expected to be
+  // send as strings already.
+  static bool ReportErrorLessThan(ErrorPtr* error,
+                                  const std::string& val,
+                                  const std::string& limit);
+  static bool ReportErrorGreaterThan(ErrorPtr* error,
+                                     const std::string& val,
+                                     const std::string& limit);
+
+  static bool ReportErrorNotOneOf(ErrorPtr* error,
+                                  const std::string& val,
+                                  const std::vector<std::string>& values);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Constraint);
+};
+
+// ConstraintMinMaxBase is a base class for numeric Minimum and Maximum
+// constraints.
+template <typename T>
+class ConstraintMinMaxBase : public Constraint {
+ public:
+  explicit ConstraintMinMaxBase(const InheritableAttribute<T>& limit)
+      : limit_(limit) {}
+  explicit ConstraintMinMaxBase(const T& limit) : limit_(limit) {}
+
+  // Implementation of Constraint::HasOverriddenAttributes().
+  bool HasOverriddenAttributes() const override { return !limit_.is_inherited; }
+
+  // Implementation of Constraint::ToJson().
+  std::unique_ptr<base::Value> ToJson() const override {
+    return TypedValueToJson(limit_.value);
+  }
+
+  // Stores the upper/lower value limit for maximum/minimum constraint.
+  // |limit_.is_inherited| indicates whether the constraint is inherited
+  // from base schema or overridden.
+  InheritableAttribute<T> limit_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ConstraintMinMaxBase);
+};
+
+// Implementation of Minimum value constraint for Integer/Double types.
+template <typename T>
+class ConstraintMin : public ConstraintMinMaxBase<T> {
+ public:
+  explicit ConstraintMin(const InheritableAttribute<T>& limit)
+      : ConstraintMinMaxBase<T>(limit) {}
+  explicit ConstraintMin(const T& limit) : ConstraintMinMaxBase<T>(limit) {}
+
+  // Implementation of Constraint::GetType().
+  ConstraintType GetType() const override { return ConstraintType::Min; }
+
+  // Implementation of Constraint::Validate().
+  bool Validate(const PropValue& value, ErrorPtr* error) const override {
+    const T& v = static_cast<const TypedValueBase<T>&>(value).GetValue();
+    if (v < this->limit_.value) {
+      return this->ReportErrorLessThan(error, std::to_string(v),
+                                       std::to_string(this->limit_.value));
+    }
+    return true;
+  }
+
+  // Implementation of Constraint::Clone().
+  std::unique_ptr<Constraint> Clone() const override {
+    return std::unique_ptr<Constraint>{new ConstraintMin{this->limit_}};
+  }
+
+  // Implementation of Constraint::CloneAsInherited().
+  std::unique_ptr<Constraint> CloneAsInherited() const override {
+    return std::unique_ptr<Constraint>{new ConstraintMin{this->limit_.value}};
+  }
+
+  // Implementation of Constraint::GetDictKey().
+  const char* GetDictKey() const override {
+    return commands::attributes::kNumeric_Min;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ConstraintMin);
+};
+
+// Implementation of Maximum value constraint for Integer/Double types.
+template <typename T>
+class ConstraintMax : public ConstraintMinMaxBase<T> {
+ public:
+  explicit ConstraintMax(const InheritableAttribute<T>& limit)
+      : ConstraintMinMaxBase<T>(limit) {}
+  explicit ConstraintMax(const T& limit) : ConstraintMinMaxBase<T>(limit) {}
+
+  // Implementation of Constraint::GetType().
+  ConstraintType GetType() const override { return ConstraintType::Max; }
+
+  // Implementation of Constraint::Validate().
+  bool Validate(const PropValue& value, ErrorPtr* error) const override {
+    const T& v = static_cast<const TypedValueBase<T>&>(value).GetValue();
+    if (v > this->limit_.value)
+      return this->ReportErrorGreaterThan(error, std::to_string(v),
+                                          std::to_string(this->limit_.value));
+    return true;
+  }
+
+  // Implementation of Constraint::Clone().
+  std::unique_ptr<Constraint> Clone() const override {
+    return std::unique_ptr<Constraint>{new ConstraintMax{this->limit_}};
+  }
+
+  // Implementation of Constraint::CloneAsInherited().
+  std::unique_ptr<Constraint> CloneAsInherited() const override {
+    return std::unique_ptr<Constraint>{new ConstraintMax{this->limit_.value}};
+  }
+
+  // Implementation of Constraint::GetDictKey().
+  const char* GetDictKey() const override {
+    return commands::attributes::kNumeric_Max;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ConstraintMax);
+};
+
+// ConstraintStringLength is a base class for Minimum/Maximum string length
+// constraints, similar to ConstraintMinMaxBase of numeric types.
+class ConstraintStringLength : public Constraint {
+ public:
+  explicit ConstraintStringLength(const InheritableAttribute<int>& limit);
+  explicit ConstraintStringLength(int limit);
+
+  // Implementation of Constraint::HasOverriddenAttributes().
+  bool HasOverriddenAttributes() const override;
+  // Implementation of Constraint::ToJson().
+  std::unique_ptr<base::Value> ToJson() const override;
+
+  // Stores the upper/lower value limit for string length constraint.
+  // |limit_.is_inherited| indicates whether the constraint is inherited
+  // from base schema or overridden.
+  InheritableAttribute<int> limit_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ConstraintStringLength);
+};
+
+// Implementation of Minimum string length constraint.
+class ConstraintStringLengthMin : public ConstraintStringLength {
+ public:
+  explicit ConstraintStringLengthMin(const InheritableAttribute<int>& limit);
+  explicit ConstraintStringLengthMin(int limit);
+
+  // Implementation of Constraint::GetType().
+  ConstraintType GetType() const override {
+    return ConstraintType::StringLengthMin;
+  }
+
+  // Implementation of Constraint::Validate().
+  bool Validate(const PropValue& value, ErrorPtr* error) const override;
+
+  // Implementation of Constraint::Clone().
+  std::unique_ptr<Constraint> Clone() const override;
+
+  // Implementation of Constraint::CloneAsInherited().
+  std::unique_ptr<Constraint> CloneAsInherited() const override;
+  // Implementation of Constraint::GetDictKey().
+  const char* GetDictKey() const override {
+    return commands::attributes::kString_MinLength;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ConstraintStringLengthMin);
+};
+
+// Implementation of Maximum string length constraint.
+class ConstraintStringLengthMax : public ConstraintStringLength {
+ public:
+  explicit ConstraintStringLengthMax(const InheritableAttribute<int>& limit);
+  explicit ConstraintStringLengthMax(int limit);
+
+  // Implementation of Constraint::GetType().
+  ConstraintType GetType() const override {
+    return ConstraintType::StringLengthMax;
+  }
+
+  // Implementation of Constraint::Validate().
+  bool Validate(const PropValue& value, ErrorPtr* error) const override;
+
+  // Implementation of Constraint::Clone().
+  std::unique_ptr<Constraint> Clone() const override;
+
+  // Implementation of Constraint::CloneAsInherited().
+  std::unique_ptr<Constraint> CloneAsInherited() const override;
+
+  // Implementation of Constraint::GetDictKey().
+  const char* GetDictKey() const override {
+    return commands::attributes::kString_MaxLength;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ConstraintStringLengthMax);
+};
+
+// Implementation of OneOf constraint for different data types.
+class ConstraintOneOf : public Constraint {
+ public:
+  explicit ConstraintOneOf(InheritableAttribute<ValueVector> set);
+  explicit ConstraintOneOf(ValueVector set);
+
+  // Implementation of Constraint::GetType().
+  ConstraintType GetType() const override { return ConstraintType::OneOf; }
+
+  // Implementation of Constraint::HasOverriddenAttributes().
+  bool HasOverriddenAttributes() const override { return !set_.is_inherited; }
+
+  // Implementation of Constraint::Validate().
+  bool Validate(const PropValue& value, ErrorPtr* error) const override;
+
+  // Implementation of Constraint::Clone().
+  std::unique_ptr<Constraint> Clone() const override;
+
+  // Implementation of Constraint::CloneAsInherited().
+  std::unique_ptr<Constraint> CloneAsInherited() const override;
+
+  // Implementation of Constraint::ToJson().
+  std::unique_ptr<base::Value> ToJson() const override;
+
+  // Implementation of Constraint::GetDictKey().
+  const char* GetDictKey() const override;
+
+  // Stores the list of acceptable values for the parameter.
+  // |set_.is_inherited| indicates whether the constraint is inherited
+  // from base schema or overridden.
+  InheritableAttribute<ValueVector> set_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ConstraintOneOf);
+};
+
+}  // namespace weave
+
+#endif  // LIBWEAVE_SRC_COMMANDS_PROP_CONSTRAINTS_H_
diff --git a/src/commands/prop_types.cc b/src/commands/prop_types.cc
new file mode 100644
index 0000000..88a53bd
--- /dev/null
+++ b/src/commands/prop_types.cc
@@ -0,0 +1,653 @@
+// Copyright 2015 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/commands/prop_types.h"
+
+#include <algorithm>
+#include <limits>
+#include <set>
+
+#include <base/json/json_writer.h>
+#include <base/logging.h>
+#include <base/values.h>
+
+#include "src/commands/object_schema.h"
+#include "src/commands/prop_values.h"
+#include "src/commands/schema_constants.h"
+
+namespace weave {
+
+// PropType -------------------------------------------------------------------
+PropType::PropType() {}
+
+PropType::~PropType() {}
+
+std::string PropType::GetTypeAsString() const {
+  return GetTypeStringFromType(GetType());
+}
+
+bool PropType::HasOverriddenAttributes() const {
+  if (default_.value && !default_.is_inherited)
+    return true;
+
+  for (const auto& pair : constraints_) {
+    if (pair.second->HasOverriddenAttributes())
+      return true;
+  }
+  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) 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);
+    return TypedValueToJson(GetTypeAsString());
+  }
+
+  std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+  if (full_schema) {
+    // If we are asked for full_schema definition, then we need to output every
+    // parameter property, including the "type", and any constraints.
+    // So, we write the "type" only if asked for full schema.
+    // Otherwise we will be able to infer the parameter type based on
+    // the constraints and their types.
+    // That is, the following JSONs could possibly define a parameter:
+    // {'type':'integer'} -> explicit "integer" with no constraints
+    // {'minimum':10} -> no type specified, but since we have "minimum"
+    //                   and 10 is integer, than this is an integer
+    //                   parameter with min constraint.
+    // {'enum':[1,2,3]} -> integer with OneOf constraint.
+    // And so is this: [1,2,3] -> an array of ints assume it's an "int" enum.
+    dict->SetString(commands::attributes::kType, GetTypeAsString());
+  }
+
+  if (!full_schema && constraints_.size() == 1) {
+    // If we are not asked for full schema, and we have only one constraint
+    // which is OneOf, we short-circuit the whole thing and return just
+    // the array [1,2,3] instead of an object with "enum" property like:
+    // {'enum':[1,2,3]}
+    auto p = constraints_.find(ConstraintType::OneOf);
+    if (p != constraints_.end()) {
+      return p->second->ToJson();
+    }
+  }
+
+  for (const auto& pair : constraints_)
+    pair.second->AddToJsonDict(dict.get(), !full_schema);
+
+  if (default_.value && (full_schema || !default_.is_inherited)) {
+    auto def_val = default_.value->ToJson();
+    CHECK(def_val);
+    dict->Set(commands::attributes::kDefault, def_val.release());
+  }
+
+  if (include_required)
+    dict->SetBoolean(commands::attributes::kIsRequired, required_.value);
+  return std::unique_ptr<base::Value>(dict.release());
+}
+
+std::unique_ptr<PropType> PropType::Clone() const {
+  auto cloned = PropType::Create(GetType());
+  cloned->based_on_schema_ = based_on_schema_;
+  for (const auto& pair : constraints_) {
+    cloned->constraints_.emplace(pair.first, pair.second->Clone());
+  }
+  cloned->default_.is_inherited = default_.is_inherited;
+  if (default_.value)
+    cloned->default_.value = default_.value->Clone();
+  cloned->required_ = required_;
+  return cloned;
+}
+
+bool PropType::FromJson(const base::DictionaryValue* value,
+                        const PropType* base_schema,
+                        ErrorPtr* error) {
+  if (base_schema && base_schema->GetType() != GetType()) {
+    Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                       errors::commands::kPropTypeChanged,
+                       "Redefining a property of type %s as %s",
+                       base_schema->GetTypeAsString().c_str(),
+                       GetTypeAsString().c_str());
+    return false;
+  }
+  based_on_schema_ = (base_schema != nullptr);
+  constraints_.clear();
+  // 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, commands::attributes::kIsRequired,
+  };
+  if (!ObjectSchemaFromJson(value, base_schema, &processed_keys, error))
+    return false;
+  if (base_schema) {
+    for (const auto& pair : base_schema->GetConstraints()) {
+      constraints_.emplace(pair.first, pair.second->CloneAsInherited());
+    }
+  }
+  if (!ConstraintsFromJson(value, &processed_keys, error))
+    return false;
+
+  // Now make sure there are no unexpected/unknown keys in the property schema
+  // definition object.
+  base::DictionaryValue::Iterator iter(*value);
+  while (!iter.IsAtEnd()) {
+    std::string key = iter.key();
+    if (processed_keys.find(key) == processed_keys.end()) {
+      Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                         errors::commands::kUnknownProperty,
+                         "Unexpected property '%s'", key.c_str());
+      return false;
+    }
+    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.
+  const base::Value* defval = nullptr;  // Owned by value
+  if (value->GetWithoutPathExpansion(commands::attributes::kDefault, &defval)) {
+    std::unique_ptr<PropValue> prop_value = CreatePropValue(*defval, error);
+    if (!prop_value) {
+      Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                         errors::commands::kInvalidPropValue,
+                         "Invalid value for property '%s'",
+                         commands::attributes::kDefault);
+      return false;
+    }
+    default_.value = std::move(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 unique_ptr to
+    // PropValue, which can be set to nullptr).
+    if (base_schema->default_.value)
+      default_.value = base_schema->default_.value->Clone();
+    default_.is_inherited = true;
+  }
+  return true;
+}
+
+void PropType::AddConstraint(std::unique_ptr<Constraint> constraint) {
+  constraints_[constraint->GetType()] = std::move(constraint);
+}
+
+void PropType::RemoveConstraint(ConstraintType constraint_type) {
+  constraints_.erase(constraint_type);
+}
+
+void PropType::RemoveAllConstraints() {
+  constraints_.clear();
+}
+
+const Constraint* PropType::GetConstraint(
+    ConstraintType constraint_type) const {
+  auto p = constraints_.find(constraint_type);
+  return p != constraints_.end() ? p->second.get() : nullptr;
+}
+
+Constraint* PropType::GetConstraint(ConstraintType constraint_type) {
+  auto p = constraints_.find(constraint_type);
+  return p != constraints_.end() ? p->second.get() : nullptr;
+}
+
+bool PropType::ValidateConstraints(const PropValue& value,
+                                   ErrorPtr* error) const {
+  for (const auto& pair : constraints_) {
+    if (!pair.second->Validate(value, error))
+      return false;
+  }
+  return true;
+}
+
+const PropType::TypeMap& PropType::GetTypeMap() {
+  static TypeMap map = {
+      {ValueType::Int, "integer"},   {ValueType::Double, "number"},
+      {ValueType::String, "string"}, {ValueType::Boolean, "boolean"},
+      {ValueType::Object, "object"}, {ValueType::Array, "array"},
+  };
+  return map;
+}
+
+std::string PropType::GetTypeStringFromType(ValueType type) {
+  for (const auto& pair : GetTypeMap()) {
+    if (pair.first == type)
+      return pair.second;
+  }
+  LOG(FATAL) << "Type map is missing a type";
+  return std::string();
+}
+
+bool PropType::GetTypeFromTypeString(const std::string& name, ValueType* type) {
+  for (const auto& pair : GetTypeMap()) {
+    if (pair.second == name) {
+      *type = pair.first;
+      return true;
+    }
+  }
+  return false;
+}
+
+std::unique_ptr<PropType> PropType::Create(ValueType type) {
+  PropType* prop = nullptr;
+  switch (type) {
+    case ValueType::Int:
+      prop = new IntPropType;
+      break;
+    case ValueType::Double:
+      prop = new DoublePropType;
+      break;
+    case ValueType::String:
+      prop = new StringPropType;
+      break;
+    case ValueType::Boolean:
+      prop = new BooleanPropType;
+      break;
+    case ValueType::Object:
+      prop = new ObjectPropType;
+      break;
+    case ValueType::Array:
+      prop = new ArrayPropType;
+      break;
+  }
+  return std::unique_ptr<PropType>(prop);
+}
+
+template <typename T>
+static std::unique_ptr<Constraint> LoadOneOfConstraint(
+    const base::DictionaryValue* value,
+    const PropType* prop_type,
+    ErrorPtr* error) {
+  std::unique_ptr<Constraint> constraint;
+  const base::Value* list = nullptr;  // Owned by |value|
+  CHECK(value->Get(commands::attributes::kOneOf_Enum, &list))
+      << "'enum' property missing in JSON dictionary";
+  ValueVector choice_list;
+  ArrayPropType array_type;
+  array_type.SetItemType(prop_type->Clone());
+  if (!TypedValueFromJson(list, &array_type, &choice_list, error))
+    return constraint;
+  InheritableAttribute<ValueVector> val(std::move(choice_list), false);
+  constraint.reset(new ConstraintOneOf{std::move(val)});
+  return constraint;
+}
+
+template <class ConstraintClass, typename T>
+static std::unique_ptr<Constraint> LoadMinMaxConstraint(
+    const char* dict_key,
+    const base::DictionaryValue* value,
+    ErrorPtr* error) {
+  std::unique_ptr<Constraint> constraint;
+  InheritableAttribute<T> limit;
+
+  const base::Value* src_val = nullptr;
+  CHECK(value->Get(dict_key, &src_val)) << "Unable to get min/max constraints";
+  if (!TypedValueFromJson(src_val, nullptr, &limit.value, error))
+    return constraint;
+  limit.is_inherited = false;
+
+  constraint.reset(new ConstraintClass{limit});
+  return constraint;
+}
+
+// PropTypeBase ----------------------------------------------------------------
+
+template <class Derived, class Value, typename T>
+bool PropTypeBase<Derived, Value, T>::ConstraintsFromJson(
+    const base::DictionaryValue* value,
+    std::set<std::string>* processed_keys,
+    ErrorPtr* error) {
+  if (!PropType::ConstraintsFromJson(value, processed_keys, error))
+    return false;
+
+  if (value->HasKey(commands::attributes::kOneOf_Enum)) {
+    auto type = Clone();
+    type->RemoveAllConstraints();
+    auto constraint = LoadOneOfConstraint<T>(value, type.get(), error);
+    if (!constraint)
+      return false;
+    this->AddConstraint(std::move(constraint));
+    this->RemoveConstraint(ConstraintType::Min);
+    this->RemoveConstraint(ConstraintType::Max);
+    processed_keys->insert(commands::attributes::kOneOf_Enum);
+  }
+
+  return true;
+}
+
+// NumericPropTypeBase ---------------------------------------------------------
+
+template <class Derived, class Value, typename T>
+bool NumericPropTypeBase<Derived, Value, T>::ConstraintsFromJson(
+    const base::DictionaryValue* value,
+    std::set<std::string>* processed_keys,
+    ErrorPtr* error) {
+  if (!Base::ConstraintsFromJson(value, processed_keys, error))
+    return false;
+
+  if (processed_keys->find(commands::attributes::kOneOf_Enum) ==
+      processed_keys->end()) {
+    // Process min/max constraints only if "enum" constraint wasn't already
+    // specified.
+    if (value->HasKey(commands::attributes::kNumeric_Min)) {
+      auto constraint = LoadMinMaxConstraint<ConstraintMin<T>, T>(
+          commands::attributes::kNumeric_Min, value, error);
+      if (!constraint)
+        return false;
+      this->AddConstraint(std::move(constraint));
+      this->RemoveConstraint(ConstraintType::OneOf);
+      processed_keys->insert(commands::attributes::kNumeric_Min);
+    }
+    if (value->HasKey(commands::attributes::kNumeric_Max)) {
+      auto constraint = LoadMinMaxConstraint<ConstraintMax<T>, T>(
+          commands::attributes::kNumeric_Max, value, error);
+      if (!constraint)
+        return false;
+      this->AddConstraint(std::move(constraint));
+      this->RemoveConstraint(ConstraintType::OneOf);
+      processed_keys->insert(commands::attributes::kNumeric_Max);
+    }
+  }
+
+  return true;
+}
+
+// StringPropType -------------------------------------------------------------
+
+bool StringPropType::ConstraintsFromJson(const base::DictionaryValue* value,
+                                         std::set<std::string>* processed_keys,
+                                         ErrorPtr* error) {
+  if (!Base::ConstraintsFromJson(value, processed_keys, error))
+    return false;
+
+  if (processed_keys->find(commands::attributes::kOneOf_Enum) ==
+      processed_keys->end()) {
+    // Process min/max constraints only if "enum" constraint wasn't already
+    // specified.
+    if (value->HasKey(commands::attributes::kString_MinLength)) {
+      auto constraint = LoadMinMaxConstraint<ConstraintStringLengthMin, int>(
+          commands::attributes::kString_MinLength, value, error);
+      if (!constraint)
+        return false;
+      AddConstraint(std::move(constraint));
+      RemoveConstraint(ConstraintType::OneOf);
+      processed_keys->insert(commands::attributes::kString_MinLength);
+    }
+    if (value->HasKey(commands::attributes::kString_MaxLength)) {
+      auto constraint = LoadMinMaxConstraint<ConstraintStringLengthMax, int>(
+          commands::attributes::kString_MaxLength, value, error);
+      if (!constraint)
+        return false;
+      AddConstraint(std::move(constraint));
+      RemoveConstraint(ConstraintType::OneOf);
+      processed_keys->insert(commands::attributes::kString_MaxLength);
+    }
+  }
+  return true;
+}
+
+void StringPropType::AddLengthConstraint(int min_len, int max_len) {
+  InheritableAttribute<int> min_attr(min_len, false);
+  InheritableAttribute<int> max_attr(max_len, false);
+  AddConstraint(std::unique_ptr<ConstraintStringLengthMin>{
+      new ConstraintStringLengthMin{min_attr}});
+  AddConstraint(std::unique_ptr<ConstraintStringLengthMax>{
+      new ConstraintStringLengthMax{max_attr}});
+}
+
+int StringPropType::GetMinLength() const {
+  auto slc = static_cast<const ConstraintStringLength*>(
+      GetConstraint(ConstraintType::StringLengthMin));
+  return slc ? slc->limit_.value : 0;
+}
+
+int StringPropType::GetMaxLength() const {
+  auto slc = static_cast<const ConstraintStringLength*>(
+      GetConstraint(ConstraintType::StringLengthMax));
+  return slc ? slc->limit_.value : std::numeric_limits<int>::max();
+}
+
+// ObjectPropType -------------------------------------------------------------
+
+ObjectPropType::ObjectPropType()
+    : object_schema_{ObjectSchema::Create(), false} {}
+
+bool ObjectPropType::HasOverriddenAttributes() const {
+  return PropType::HasOverriddenAttributes() || !object_schema_.is_inherited;
+}
+
+std::unique_ptr<PropType> ObjectPropType::Clone() const {
+  auto cloned = Base::Clone();
+
+  cloned->GetObject()->object_schema_.is_inherited =
+      object_schema_.is_inherited;
+  cloned->GetObject()->object_schema_.value = object_schema_.value->Clone();
+  return cloned;
+}
+
+std::unique_ptr<base::Value> ObjectPropType::ToJson(bool full_schema,
+                                                    bool in_command_def) const {
+  std::unique_ptr<base::Value> value =
+      PropType::ToJson(full_schema, in_command_def);
+  CHECK(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, false);
+    CHECK(object_schema);
+
+    dict->SetWithoutPathExpansion(commands::attributes::kObject_Properties,
+                                  object_schema.release());
+    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;
+}
+
+bool ObjectPropType::ObjectSchemaFromJson(const base::DictionaryValue* value,
+                                          const PropType* base_schema,
+                                          std::set<std::string>* processed_keys,
+                                          ErrorPtr* error) {
+  if (!Base::ObjectSchemaFromJson(value, base_schema, processed_keys, error))
+    return false;
+
+  using commands::attributes::kObject_Properties;
+  using commands::attributes::kObject_AdditionalProperties;
+
+  const ObjectSchema* base_object_schema = nullptr;
+  if (base_schema)
+    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);
+    object_schema.reset(new ObjectSchema);
+    if (!object_schema->FromJson(props, base_object_schema, error)) {
+      Error::AddTo(error, FROM_HERE, errors::commands::kDomain,
+                   errors::commands::kInvalidObjectSchema,
+                   "Error parsing object property schema");
+      return false;
+    }
+  } else if (base_object_schema) {
+    object_schema = base_object_schema->Clone();
+    inherited = true;
+  } else {
+    Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                       errors::commands::kInvalidObjectSchema,
+                       "Object type definition must include the "
+                       "object schema ('%s' field not found)",
+                       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)) {
+      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));
+        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;
+}
+
+void ObjectPropType::SetObjectSchema(
+    std::unique_ptr<const ObjectSchema> schema) {
+  object_schema_.value = std::move(schema);
+  object_schema_.is_inherited = false;
+}
+
+// ArrayPropType -------------------------------------------------------------
+
+ArrayPropType::ArrayPropType() {}
+
+bool ArrayPropType::HasOverriddenAttributes() const {
+  return PropType::HasOverriddenAttributes() || !item_type_.is_inherited;
+}
+
+std::unique_ptr<PropType> ArrayPropType::Clone() const {
+  auto cloned = Base::Clone();
+
+  cloned->GetArray()->item_type_.is_inherited = item_type_.is_inherited;
+  cloned->GetArray()->item_type_.value = item_type_.value->Clone();
+  return cloned;
+}
+
+std::unique_ptr<base::Value> ArrayPropType::ToJson(bool full_schema,
+                                                   bool in_command_def) const {
+  std::unique_ptr<base::Value> value =
+      PropType::ToJson(full_schema, in_command_def);
+  CHECK(value);
+  if (!item_type_.is_inherited || full_schema) {
+    base::DictionaryValue* dict = nullptr;
+    CHECK(value->GetAsDictionary(&dict)) << "Expecting a JSON object";
+    auto type = item_type_.value->ToJson(full_schema, false);
+    CHECK(type);
+    dict->SetWithoutPathExpansion(commands::attributes::kItems, type.release());
+  }
+  return value;
+}
+
+bool ArrayPropType::ObjectSchemaFromJson(const base::DictionaryValue* value,
+                                         const PropType* base_schema,
+                                         std::set<std::string>* processed_keys,
+                                         ErrorPtr* error) {
+  if (!Base::ObjectSchemaFromJson(value, base_schema, processed_keys, error))
+    return false;
+
+  using commands::attributes::kItems;
+
+  const PropType* base_type = nullptr;
+  if (base_schema)
+    base_type = base_schema->GetArray()->GetItemTypePtr();
+
+  const base::Value* type_value = nullptr;
+  if (value->GetWithoutPathExpansion(kItems, &type_value)) {
+    processed_keys->insert(kItems);
+    auto item_type = ObjectSchema::PropFromJson(*type_value, base_type, error);
+    if (!item_type)
+      return false;
+    if (item_type->GetType() == ValueType::Array) {
+      Error::AddTo(error, FROM_HERE, errors::commands::kDomain,
+                   errors::commands::kInvalidObjectSchema,
+                   "Arrays of arrays are not supported");
+      return false;
+    }
+    SetItemType(std::move(item_type));
+  } else if (!item_type_.value) {
+    if (base_type) {
+      item_type_.value = base_type->Clone();
+      item_type_.is_inherited = true;
+    } else {
+      Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                         errors::commands::kInvalidObjectSchema,
+                         "Array type definition must include the "
+                         "array item type ('%s' field not found)",
+                         kItems);
+      return false;
+    }
+  }
+  return true;
+}
+
+void ArrayPropType::SetItemType(std::unique_ptr<const PropType> item_type) {
+  item_type_.value = std::move(item_type);
+  item_type_.is_inherited = false;
+}
+
+}  // namespace weave
diff --git a/src/commands/prop_types.h b/src/commands/prop_types.h
new file mode 100644
index 0000000..f784eb6
--- /dev/null
+++ b/src/commands/prop_types.h
@@ -0,0 +1,352 @@
+// Copyright 2015 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_SRC_COMMANDS_PROP_TYPES_H_
+#define LIBWEAVE_SRC_COMMANDS_PROP_TYPES_H_
+
+#include <limits>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <weave/error.h>
+
+#include "src/commands/prop_constraints.h"
+#include "src/commands/prop_values.h"
+
+namespace weave {
+
+class IntPropType;
+class DoublePropType;
+class StringPropType;
+class BooleanPropType;
+class ObjectPropType;
+class ArrayPropType;
+
+// PropType is a base class for all parameter type definition objects.
+// Property definitions of a particular type will derive from this class and
+// provide type-specific implementations.
+class PropType {
+ public:
+  // ConstraintMap is a type alias for a map containing parameter
+  // constraints. It is implemented as a map for fast look-ups of constraints
+  // of particular type. Also it is expected to have at most one constraint
+  // of each type (e.g. it makes no sense to impose two "minimum" constraints
+  // onto a numeric parameter).
+  using ConstraintMap = std::map<ConstraintType, std::unique_ptr<Constraint>>;
+
+  PropType();
+  virtual ~PropType();
+
+  // Gets the parameter type as an enumeration.
+  virtual ValueType GetType() const = 0;
+  // Gets the parameter type as a string.
+  std::string GetTypeAsString() const;
+  // Returns true if this parameter definition inherits a type
+  // definition from a base object schema.
+  bool IsBasedOnSchema() const { return based_on_schema_; }
+  // Returns a default value specified for the type, or nullptr if no default
+  // is available.
+  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.
+  // An attribute could be the value of any of the constraints, default
+  // value of a parameter or any other data that may be specified in
+  // parameter type definition in and can be inherited from the base schema.
+  virtual bool HasOverriddenAttributes() const;
+
+  // Type conversion methods. Used in lieu of RTTI and dynamic_cast<>.
+  virtual IntPropType* GetInt() { return nullptr; }
+  virtual IntPropType const* GetInt() const { return nullptr; }
+  virtual DoublePropType* GetDouble() { return nullptr; }
+  virtual DoublePropType const* GetDouble() const { return nullptr; }
+  virtual StringPropType* GetString() { return nullptr; }
+  virtual StringPropType const* GetString() const { return nullptr; }
+  virtual BooleanPropType* GetBoolean() { return nullptr; }
+  virtual BooleanPropType const* GetBoolean() const { return nullptr; }
+  virtual ObjectPropType* GetObject() { return nullptr; }
+  virtual ObjectPropType const* GetObject() const { return nullptr; }
+  virtual ArrayPropType* GetArray() { return nullptr; }
+  virtual ArrayPropType const* GetArray() const { return nullptr; }
+
+  // Makes a full copy of this type definition.
+  virtual std::unique_ptr<PropType> Clone() const;
+
+  // Creates an instance of associated value object.
+  virtual std::unique_ptr<PropValue> CreatePropValue(const base::Value& value,
+                                                     ErrorPtr* error) const = 0;
+
+  // Saves the parameter type definition as a JSON object.
+  // If |full_schema| is set to true, the full type definition is saved,
+  // otherwise only the overridden properties and attributes from the base
+  // schema is saved. That is, inherited properties and attributes are not
+  // 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) const;
+  // Parses an JSON parameter type definition. Optional |base_schema| may
+  // specify the base schema type definition this type should be based upon.
+  // If not specified (nullptr), the parameter type is assumed to be a full
+  // definition and any omitted required properties are treated as an error.
+  // Returns true on success, otherwise fills in the |error| with additional
+  // error information.
+  virtual bool FromJson(const base::DictionaryValue* value,
+                        const PropType* base_schema,
+                        ErrorPtr* error);
+  // Helper function to load object schema from JSON.
+  virtual bool ObjectSchemaFromJson(const base::DictionaryValue* value,
+                                    const PropType* base_schema,
+                                    std::set<std::string>* processed_keys,
+                                    ErrorPtr* error) {
+    return true;
+  }
+  // Helper function to load type-specific constraints from JSON.
+  virtual bool ConstraintsFromJson(const base::DictionaryValue* value,
+                                   std::set<std::string>* processed_keys,
+                                   ErrorPtr* error) {
+    return true;
+  }
+
+  // Additional helper static methods to help with converting a type enum
+  // value into a string and back.
+  using TypeMap = std::vector<std::pair<ValueType, std::string>>;
+  // Returns a list of value types and corresponding type names.
+  static const TypeMap& GetTypeMap();
+  // Gets the type name string for the given type.
+  static std::string GetTypeStringFromType(ValueType type);
+  // Finds the type for the given type name. Returns true on success.
+  static bool GetTypeFromTypeString(const std::string& name, ValueType* type);
+
+  // Creates an instance of PropType-derived class for the specified
+  // parameter type.
+  static std::unique_ptr<PropType> Create(ValueType type);
+
+  // Adds a constraint to the type definition.
+  void AddConstraint(std::unique_ptr<Constraint> constraint);
+  // Removes a constraint of given type, if it exists.
+  void RemoveConstraint(ConstraintType constraint_type);
+  // Removes all constraints.
+  void RemoveAllConstraints();
+
+  // Finds a constraint of given type. Returns nullptr if not found.
+  const Constraint* GetConstraint(ConstraintType constraint_type) const;
+  Constraint* GetConstraint(ConstraintType constraint_type);
+
+  // Validates the given value against all the constraints.
+  bool ValidateConstraints(const PropValue& value, ErrorPtr* error) const;
+
+ protected:
+  friend class StatePackage;
+  virtual std::unique_ptr<PropValue> CreateDefaultValue() const = 0;
+
+  // Specifies if this parameter definition is derived from a base
+  // object schema.
+  bool based_on_schema_ = false;
+  // A list of constraints specified for the parameter.
+  ConstraintMap constraints_;
+  // The default value specified for the parameter, if any. If the default
+  // value is present, the parameter is treated as optional and the default
+  // value is used if the parameter value is omitted when sending a command.
+  // 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
+// type classes. Provides implementations for common methods of PropType base.
+template <class Derived, class Value, typename T>
+class PropTypeBase : public PropType {
+ public:
+  // Overrides from PropType.
+  ValueType GetType() const override { return GetValueType<T>(); }
+
+  std::unique_ptr<PropValue> CreatePropValue(const base::Value& value,
+                                             ErrorPtr* error) const override {
+    return CreateValue(value, error);
+  }
+
+  std::unique_ptr<Value> CreateValue(const base::Value& value,
+                                     ErrorPtr* error) const {
+    return Value::CreateFromJson(value, *this, error);
+  }
+
+  bool ConstraintsFromJson(const base::DictionaryValue* value,
+                           std::set<std::string>* processed_keys,
+                           ErrorPtr* error) override;
+
+ protected:
+  std::unique_ptr<PropValue> CreateDefaultValue() const override {
+    if (GetDefaultValue())
+      return GetDefaultValue()->Clone();
+    return std::unique_ptr<PropValue>{new Value{*this}};
+  }
+};
+
+// Helper base class for Int and Double parameter types.
+template <class Derived, class Value, typename T>
+class NumericPropTypeBase : public PropTypeBase<Derived, Value, T> {
+ public:
+  using Base = PropTypeBase<Derived, Value, T>;
+  bool ConstraintsFromJson(const base::DictionaryValue* value,
+                           std::set<std::string>* processed_keys,
+                           ErrorPtr* error) override;
+
+  // Helper method to set and obtain a min/max constraint values.
+  // Used mostly for unit testing.
+  void AddMinMaxConstraint(T min_value, T max_value) {
+    InheritableAttribute<T> min_attr(min_value, false);
+    InheritableAttribute<T> max_attr(max_value, false);
+    this->AddConstraint(
+        std::unique_ptr<ConstraintMin<T>>{new ConstraintMin<T>{min_attr}});
+    this->AddConstraint(
+        std::unique_ptr<ConstraintMax<T>>{new ConstraintMax<T>{max_attr}});
+  }
+  T GetMinValue() const {
+    auto mmc = static_cast<const ConstraintMin<T>*>(
+        this->GetConstraint(ConstraintType::Min));
+    return mmc ? mmc->limit_.value : std::numeric_limits<T>::lowest();
+  }
+  T GetMaxValue() const {
+    auto mmc = static_cast<const ConstraintMax<T>*>(
+        this->GetConstraint(ConstraintType::Max));
+    return mmc ? mmc->limit_.value : (std::numeric_limits<T>::max)();
+  }
+};
+
+// Property definition of Integer type.
+class IntPropType : public NumericPropTypeBase<IntPropType, IntValue, int> {
+ public:
+  // Overrides from the PropType base class.
+  IntPropType* GetInt() override { return this; }
+  IntPropType const* GetInt() const override { return this; }
+};
+
+// Property definition of Number type.
+class DoublePropType
+    : public NumericPropTypeBase<DoublePropType, DoubleValue, double> {
+ public:
+  // Overrides from the PropType base class.
+  DoublePropType* GetDouble() override { return this; }
+  DoublePropType const* GetDouble() const override { return this; }
+};
+
+// Property definition of String type.
+class StringPropType
+    : public PropTypeBase<StringPropType, StringValue, std::string> {
+ public:
+  using Base = PropTypeBase<StringPropType, StringValue, std::string>;
+  // Overrides from the PropType base class.
+  StringPropType* GetString() override { return this; }
+  StringPropType const* GetString() const override { return this; }
+
+  bool ConstraintsFromJson(const base::DictionaryValue* value,
+                           std::set<std::string>* processed_keys,
+                           ErrorPtr* error) override;
+
+  // Helper methods to add and inspect simple constraints.
+  // Used mostly for unit testing.
+  void AddLengthConstraint(int min_len, int max_len);
+  int GetMinLength() const;
+  int GetMaxLength() const;
+};
+
+// Property definition of Boolean type.
+class BooleanPropType
+    : public PropTypeBase<BooleanPropType, BooleanValue, bool> {
+ public:
+  // Overrides from the PropType base class.
+  BooleanPropType* GetBoolean() override { return this; }
+  BooleanPropType const* GetBoolean() const override { return this; }
+};
+
+// Parameter definition of Object type.
+class ObjectPropType
+    : public PropTypeBase<ObjectPropType, ObjectValue, ValueMap> {
+ public:
+  using Base = PropTypeBase<ObjectPropType, ObjectValue, ValueMap>;
+  ObjectPropType();
+
+  // Overrides from the ParamType base class.
+  bool HasOverriddenAttributes() const override;
+
+  ObjectPropType* GetObject() override { return this; }
+  ObjectPropType const* GetObject() const override { return this; }
+
+  std::unique_ptr<PropType> Clone() const override;
+
+  std::unique_ptr<base::Value> ToJson(bool full_schema,
+                                      bool in_command_def) const override;
+  bool ObjectSchemaFromJson(const base::DictionaryValue* value,
+                            const PropType* base_schema,
+                            std::set<std::string>* processed_keys,
+                            ErrorPtr* error) override;
+
+  // Returns a schema for Object-type parameter.
+  inline const ObjectSchema* GetObjectSchemaPtr() const {
+    return object_schema_.value.get();
+  }
+  void SetObjectSchema(std::unique_ptr<const ObjectSchema> schema);
+
+ private:
+  InheritableAttribute<std::unique_ptr<const ObjectSchema>> object_schema_;
+};
+
+// Parameter definition of Array type.
+class ArrayPropType
+    : public PropTypeBase<ArrayPropType, ArrayValue, ValueVector> {
+ public:
+  using Base = PropTypeBase<ArrayPropType, ArrayValue, ValueVector>;
+  ArrayPropType();
+
+  // Overrides from the ParamType base class.
+  bool HasOverriddenAttributes() const override;
+
+  ArrayPropType* GetArray() override { return this; }
+  ArrayPropType const* GetArray() const override { return this; }
+
+  std::unique_ptr<PropType> Clone() const override;
+
+  std::unique_ptr<base::Value> ToJson(bool full_schema,
+                                      bool in_command_def) const override;
+
+  bool ObjectSchemaFromJson(const base::DictionaryValue* value,
+                            const PropType* base_schema,
+                            std::set<std::string>* processed_keys,
+                            ErrorPtr* error) override;
+
+  // Returns a type for Array elements.
+  inline const PropType* GetItemTypePtr() const {
+    return item_type_.value.get();
+  }
+  void SetItemType(std::unique_ptr<const PropType> item_type);
+
+ private:
+  InheritableAttribute<std::unique_ptr<const PropType>> item_type_;
+};
+
+}  // namespace weave
+
+#endif  // LIBWEAVE_SRC_COMMANDS_PROP_TYPES_H_
diff --git a/src/commands/prop_values.cc b/src/commands/prop_values.cc
new file mode 100644
index 0000000..6f30bee
--- /dev/null
+++ b/src/commands/prop_values.cc
@@ -0,0 +1,17 @@
+// Copyright 2015 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/commands/prop_values.h"
+
+#include "src/commands/prop_types.h"
+
+namespace weave {
+
+PropValue::PropValue(const PropType& type) : type_{type.Clone()} {}
+
+PropValue::PropValue(const PropValue& other) : PropValue{*other.type_} {}
+
+PropValue::~PropValue() {}
+
+}  // namespace weave
diff --git a/src/commands/prop_values.h b/src/commands/prop_values.h
new file mode 100644
index 0000000..f0a401e
--- /dev/null
+++ b/src/commands/prop_values.h
@@ -0,0 +1,250 @@
+// Copyright 2015 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_SRC_COMMANDS_PROP_VALUES_H_
+#define LIBWEAVE_SRC_COMMANDS_PROP_VALUES_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include <weave/error.h>
+
+#include "src/commands/schema_utils.h"
+
+namespace base {
+class Value;
+class DictionaryValue;
+}  // namespace base
+
+namespace weave {
+
+// Enumeration to indicate supported command parameter types.
+enum class ValueType {
+  Int,
+  Double,
+  String,
+  Boolean,
+  Object,
+  Array,
+};
+
+class PropValue;
+class IntValue;
+class DoubleValue;
+class StringValue;
+class BooleanValue;
+class ObjectValue;
+class ArrayValue;
+
+class PropType;
+
+// Helper methods to get the parameter type enum value for the given
+// native C++ data representation.
+// The generic GetValueType<T>() is undefined, however particular
+// type specializations return the appropriate ValueType.
+template <typename T>
+ValueType GetValueType();  // Undefined.
+template <>
+inline ValueType GetValueType<int>() {
+  return ValueType::Int;
+}
+template <>
+inline ValueType GetValueType<double>() {
+  return ValueType::Double;
+}
+template <>
+inline ValueType GetValueType<std::string>() {
+  return ValueType::String;
+}
+template <>
+inline ValueType GetValueType<bool>() {
+  return ValueType::Boolean;
+}
+template <>
+inline ValueType GetValueType<ValueMap>() {
+  return ValueType::Object;
+}
+template <>
+inline ValueType GetValueType<ValueVector>() {
+  return ValueType::Array;
+}
+
+// The base class for property values.
+// Concrete value classes of various types will be derived from this base.
+// A property value is the actual command parameter value (or a concrete value
+// that can be used in constraints and presets). The PropValue is mostly
+// just parsed content of base::Value when a command is dispatched, however
+// it does have some additional functionality:
+//   - it has a reference to the type definition (PropType) which is used
+//     when validating the value, especially for "object" types.
+//   - it can be compared with another instances of values of the same type.
+//     This is used to validate the values against "enum"/"one of" constraints.
+class PropValue {
+ public:
+  // Only CreateDefaultValue should use this constructor.
+  explicit PropValue(const PropType& type);
+  virtual ~PropValue();
+
+  // Gets the type of the value.
+  virtual ValueType GetType() const = 0;
+
+  // Type conversion methods. Used in lieu of RTTI and dynamic_cast<>.
+  virtual IntValue* GetInt() { return nullptr; }
+  virtual IntValue const* GetInt() const { return nullptr; }
+  virtual DoubleValue* GetDouble() { return nullptr; }
+  virtual DoubleValue const* GetDouble() const { return nullptr; }
+  virtual StringValue* GetString() { return nullptr; }
+  virtual StringValue const* GetString() const { return nullptr; }
+  virtual BooleanValue* GetBoolean() { return nullptr; }
+  virtual BooleanValue const* GetBoolean() const { return nullptr; }
+  virtual ObjectValue* GetObject() { return nullptr; }
+  virtual ObjectValue const* GetObject() const { return nullptr; }
+  virtual ArrayValue* GetArray() { return nullptr; }
+  virtual ArrayValue const* GetArray() const { return nullptr; }
+
+  // Makes a full copy of this value class.
+  virtual std::unique_ptr<PropValue> Clone() const = 0;
+
+  // Saves the value as a JSON object. Never fails.
+  virtual std::unique_ptr<base::Value> ToJson() const = 0;
+
+  // Return the type definition of this value.
+  const PropType* GetPropType() const { return type_.get(); }
+  // Compares two values and returns true if they are equal.
+  virtual bool IsEqual(const PropValue* value) const = 0;
+
+ protected:
+  // Special out-of-line constructor to help implement PropValue::Clone().
+  // That method needs to clone the underlying type but can't do this in this
+  // header file since PropType is just forward-declared (it needs PropValue
+  // fully defined in its own inner workings).
+  explicit PropValue(const PropValue& other);
+
+ private:
+  std::unique_ptr<const PropType> type_;
+};
+
+// A helper template base class for implementing value classes.
+template <typename T>
+class TypedValueBase : public PropValue {
+ public:
+  using PropValue::PropValue;
+
+  // Overrides from PropValue base class.
+  ValueType GetType() const override { return GetValueType<T>(); }
+
+  std::unique_ptr<base::Value> ToJson() const override {
+    return TypedValueToJson(value_);
+  }
+
+  bool IsEqual(const PropValue* value) const override {
+    if (GetType() != value->GetType())
+      return false;
+    return CompareValue(GetValue(),
+                        static_cast<const TypedValueBase*>(value)->GetValue());
+  }
+
+  // Helper methods to get and set the C++ representation of the value.
+  const T& GetValue() const { return value_; }
+
+ protected:
+  explicit TypedValueBase(const TypedValueBase& other)
+      : PropValue(other), value_(other.value_) {}
+
+  TypedValueBase(const PropType& type, T value)
+      : PropValue(type), value_(value) {}
+
+ private:
+  T value_{};  // The value of the parameter in C++ data representation.
+};
+
+// A helper template base class for implementing value classes.
+template <typename Derived, typename T>
+class TypedValueWithClone : public TypedValueBase<T> {
+ public:
+  using Base = TypedValueWithClone<Derived, T>;
+
+  // Expose the custom constructor of the base class.
+  using TypedValueBase<T>::TypedValueBase;
+  using PropValue::GetPropType;
+
+  std::unique_ptr<PropValue> Clone() const override {
+    return std::unique_ptr<PropValue>{
+        new Derived{*static_cast<const Derived*>(this)}};
+  }
+
+  static std::unique_ptr<Derived> CreateFromJson(const base::Value& value,
+                                                 const PropType& type,
+                                                 ErrorPtr* error) {
+    T tmp_value;
+    if (!TypedValueFromJson(&value, &type, &tmp_value, error))
+      return nullptr;
+
+    // Only place where invalid value can exist.
+    std::unique_ptr<Derived> result{new Derived{type, tmp_value}};
+    if (!result->GetPropType()->ValidateConstraints(*result, error))
+      return nullptr;
+
+    return result;
+  }
+};
+
+// Value of type Integer.
+class IntValue final : public TypedValueWithClone<IntValue, int> {
+ public:
+  using Base::Base;
+  friend class TypedValueWithClone<IntValue, int>;
+  IntValue* GetInt() override { return this; }
+  IntValue const* GetInt() const override { return this; }
+};
+
+// Value of type Number.
+class DoubleValue final : public TypedValueWithClone<DoubleValue, double> {
+ public:
+  using Base::Base;
+  friend class TypedValueWithClone<DoubleValue, double>;
+  DoubleValue* GetDouble() override { return this; }
+  DoubleValue const* GetDouble() const override { return this; }
+};
+
+// Value of type String.
+class StringValue final : public TypedValueWithClone<StringValue, std::string> {
+ public:
+  using Base::Base;
+  friend class TypedValueWithClone<StringValue, std::string>;
+  StringValue* GetString() override { return this; }
+  StringValue const* GetString() const override { return this; }
+};
+
+// Value of type Boolean.
+class BooleanValue final : public TypedValueWithClone<BooleanValue, bool> {
+ public:
+  using Base::Base;
+  friend class TypedValueWithClone<BooleanValue, bool>;
+  BooleanValue* GetBoolean() override { return this; }
+  BooleanValue const* GetBoolean() const override { return this; }
+};
+
+// Value of type Object.
+class ObjectValue final : public TypedValueWithClone<ObjectValue, ValueMap> {
+ public:
+  using Base::Base;
+  friend class TypedValueWithClone<ObjectValue, ValueMap>;
+  ObjectValue* GetObject() override { return this; }
+  ObjectValue const* GetObject() const override { return this; }
+};
+
+// Value of type Array.
+class ArrayValue final : public TypedValueWithClone<ArrayValue, ValueVector> {
+ public:
+  using Base::Base;
+  friend class TypedValueWithClone<ArrayValue, ValueVector>;
+  ArrayValue* GetArray() override { return this; }
+  ArrayValue const* GetArray() const override { return this; }
+};
+
+}  // namespace weave
+
+#endif  // LIBWEAVE_SRC_COMMANDS_PROP_VALUES_H_
diff --git a/src/commands/schema_constants.cc b/src/commands/schema_constants.cc
index cd8bf84..c99536b 100644
--- a/src/commands/schema_constants.cc
+++ b/src/commands/schema_constants.cc
@@ -10,11 +10,20 @@
 namespace commands {
 const char kDomain[] = "command_schema";
 
+const char kOutOfRange[] = "out_of_range";
 const char kTypeMismatch[] = "type_mismatch";
+const char kPropTypeChanged[] = "param_type_changed";
+const char kUnknownType[] = "unknown_type";
+const char kInvalidPropDef[] = "invalid_parameter_definition";
 const char kInvalidPropValue[] = "invalid_parameter_value";
+const char kNoTypeInfo[] = "no_type_info";
 const char kPropertyMissing[] = "parameter_missing";
+const char kUnknownProperty[] = "unexpected_parameter";
+const char kInvalidObjectSchema[] = "invalid_object_schema";
+const char kDuplicateCommandDef[] = "duplicate_command_definition";
 const char kInvalidCommandName[] = "invalid_command_name";
 const char kCommandFailed[] = "command_failed";
+const char kInvalidCommandVisibility[] = "invalid_command_visibility";
 const char kInvalidMinimalRole[] = "invalid_minimal_role";
 const char kCommandDestroyed[] = "command_destroyed";
 const char kInvalidState[] = "invalid_state";
@@ -24,6 +33,25 @@
 namespace commands {
 namespace attributes {
 
+const char kType[] = "type";
+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";
+
+const char kString_MinLength[] = "minLength";
+const char kString_MaxLength[] = "maxLength";
+
+const char kOneOf_Enum[] = "enum";
+const char kOneOf_Metadata[] = "metadata";
+
+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";
 const char kCommand_Parameters[] = "parameters";
@@ -38,6 +66,12 @@
 const char kCommand_Role_User[] = "user";
 const char kCommand_Role_Viewer[] = "viewer";
 
+const char kCommand_Visibility[] = "visibility";
+const char kCommand_Visibility_None[] = "none";
+const char kCommand_Visibility_Local[] = "local";
+const char kCommand_Visibility_Cloud[] = "cloud";
+const char kCommand_Visibility_All[] = "all";
+
 }  // namespace attributes
 }  // namespace commands
 
diff --git a/src/commands/schema_constants.h b/src/commands/schema_constants.h
index 57766e6..742245f 100644
--- a/src/commands/schema_constants.h
+++ b/src/commands/schema_constants.h
@@ -13,11 +13,20 @@
 extern const char kDomain[];
 
 // Common command definition error codes.
+extern const char kOutOfRange[];
 extern const char kTypeMismatch[];
+extern const char kPropTypeChanged[];
+extern const char kUnknownType[];
+extern const char kInvalidPropDef[];
 extern const char kInvalidPropValue[];
+extern const char kNoTypeInfo[];
 extern const char kPropertyMissing[];
+extern const char kUnknownProperty[];
+extern const char kInvalidObjectSchema[];
+extern const char kDuplicateCommandDef[];
 extern const char kInvalidCommandName[];
 extern const char kCommandFailed[];
+extern const char kInvalidCommandVisibility[];
 extern const char kInvalidMinimalRole[];
 extern const char kCommandDestroyed[];
 extern const char kInvalidState[];
@@ -27,6 +36,25 @@
 namespace commands {
 namespace attributes {
 // Command description JSON schema attributes.
+extern const char kType[];
+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[];
+
+extern const char kString_MinLength[];
+extern const char kString_MaxLength[];
+
+extern const char kOneOf_Enum[];
+extern const char kOneOf_Metadata[];
+
+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[];
 extern const char kCommand_Parameters[];
@@ -41,6 +69,12 @@
 extern const char kCommand_Role_User[];
 extern const char kCommand_Role_Viewer[];
 
+extern const char kCommand_Visibility[];
+extern const char kCommand_Visibility_None[];
+extern const char kCommand_Visibility_Local[];
+extern const char kCommand_Visibility_Cloud[];
+extern const char kCommand_Visibility_All[];
+
 }  // namespace attributes
 }  // namespace commands
 
diff --git a/src/commands/schema_utils.cc b/src/commands/schema_utils.cc
new file mode 100644
index 0000000..3c3e949
--- /dev/null
+++ b/src/commands/schema_utils.cc
@@ -0,0 +1,266 @@
+// Copyright 2015 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/commands/schema_utils.h"
+
+#include <algorithm>
+#include <set>
+#include <string>
+
+#include <base/json/json_writer.h>
+#include <base/logging.h>
+
+#include "src/commands/object_schema.h"
+#include "src/commands/prop_types.h"
+#include "src/commands/prop_values.h"
+
+namespace weave {
+namespace {
+// Helper function to report "type mismatch" errors when parsing JSON.
+void ReportJsonTypeMismatch(const tracked_objects::Location& location,
+                            const base::Value* value_in,
+                            const std::string& expected_type,
+                            ErrorPtr* error) {
+  std::string value_as_string;
+  base::JSONWriter::Write(*value_in, &value_as_string);
+  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());
+}
+
+// Template version of ReportJsonTypeMismatch that deduces the type of expected
+// data from the value_out parameter passed to particular overload of
+// TypedValueFromJson() function. Always returns false.
+template <typename T>
+bool ReportUnexpectedJson(const tracked_objects::Location& location,
+                          const base::Value* value_in,
+                          T*,
+                          ErrorPtr* error) {
+  ReportJsonTypeMismatch(location, value_in,
+                         PropType::GetTypeStringFromType(GetValueType<T>()),
+                         error);
+  return false;
+}
+
+bool ErrorMissingProperty(ErrorPtr* error,
+                          const tracked_objects::Location& location,
+                          const char* param_name) {
+  Error::AddToPrintf(error, location, errors::commands::kDomain,
+                     errors::commands::kPropertyMissing,
+                     "Required parameter missing: %s", param_name);
+  return false;
+}
+
+}  // namespace
+
+// Specializations of TypedValueToJson<T>() for supported C++ types.
+std::unique_ptr<base::FundamentalValue> TypedValueToJson(bool value) {
+  return std::unique_ptr<base::FundamentalValue>(
+      new base::FundamentalValue(value));
+}
+
+std::unique_ptr<base::FundamentalValue> TypedValueToJson(int value) {
+  return std::unique_ptr<base::FundamentalValue>(
+      new base::FundamentalValue(value));
+}
+
+std::unique_ptr<base::FundamentalValue> TypedValueToJson(double value) {
+  return std::unique_ptr<base::FundamentalValue>(
+      new base::FundamentalValue(value));
+}
+
+std::unique_ptr<base::StringValue> TypedValueToJson(const std::string& value) {
+  return std::unique_ptr<base::StringValue>(new base::StringValue(value));
+}
+
+std::unique_ptr<base::DictionaryValue> TypedValueToJson(const ValueMap& value) {
+  std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+  for (const auto& pair : value) {
+    auto prop_value = pair.second->ToJson();
+    CHECK(prop_value);
+    dict->SetWithoutPathExpansion(pair.first, prop_value.release());
+  }
+  return dict;
+}
+
+std::unique_ptr<base::ListValue> TypedValueToJson(const ValueVector& value) {
+  std::unique_ptr<base::ListValue> list(new base::ListValue);
+  for (const auto& item : value) {
+    auto json = item->ToJson();
+    CHECK(json);
+    list->Append(json.release());
+  }
+  return list;
+}
+
+bool TypedValueFromJson(const base::Value* value_in,
+                        const PropType* type,
+                        bool* value_out,
+                        ErrorPtr* error) {
+  return value_in->GetAsBoolean(value_out) ||
+         ReportUnexpectedJson(FROM_HERE, value_in, value_out, error);
+}
+
+bool TypedValueFromJson(const base::Value* value_in,
+                        const PropType* type,
+                        int* value_out,
+                        ErrorPtr* error) {
+  return value_in->GetAsInteger(value_out) ||
+         ReportUnexpectedJson(FROM_HERE, value_in, value_out, error);
+}
+
+bool TypedValueFromJson(const base::Value* value_in,
+                        const PropType* type,
+                        double* value_out,
+                        ErrorPtr* error) {
+  return value_in->GetAsDouble(value_out) ||
+         ReportUnexpectedJson(FROM_HERE, value_in, value_out, error);
+}
+
+bool TypedValueFromJson(const base::Value* value_in,
+                        const PropType* type,
+                        std::string* value_out,
+                        ErrorPtr* error) {
+  return value_in->GetAsString(value_out) ||
+         ReportUnexpectedJson(FROM_HERE, value_in, value_out, error);
+}
+
+bool TypedValueFromJson(const base::Value* value_in,
+                        const PropType* type,
+                        ValueMap* value_out,
+                        ErrorPtr* error) {
+  const base::DictionaryValue* dict = nullptr;
+  if (!value_in->GetAsDictionary(&dict))
+    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";
+
+  const ObjectSchema* object_schema = type->GetObject()->GetObjectSchemaPtr();
+  std::set<std::string> keys_processed;
+  value_out->clear();  // Clear possible default values already in |value_out|.
+  for (const auto& pair : object_schema->GetProps()) {
+    const PropValue* def_value = pair.second->GetDefaultValue();
+    if (dict->HasKey(pair.first)) {
+      const base::Value* param_value = nullptr;
+      CHECK(dict->GetWithoutPathExpansion(pair.first, &param_value))
+          << "Unable to get parameter";
+      auto value = pair.second->CreatePropValue(*param_value, error);
+      if (!value) {
+        Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                           errors::commands::kInvalidPropValue,
+                           "Invalid value for property '%s'",
+                           pair.first.c_str());
+        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());
+      keys_processed.insert(pair.first);
+    } else if (pair.second->IsRequired()) {
+      return ErrorMissingProperty(error, FROM_HERE, pair.first.c_str());
+    }
+  }
+
+  // Just for sanity, make sure that we processed all the necessary properties
+  // and there weren't any extra (unknown) ones specified. If so, ignore
+  // them, but log as warnings...
+  base::DictionaryValue::Iterator iter(*dict);
+  while (!iter.IsAtEnd()) {
+    std::string key = iter.key();
+    if (keys_processed.find(key) == keys_processed.end() &&
+        !object_schema->GetExtraPropertiesAllowed()) {
+      Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                         errors::commands::kUnknownProperty,
+                         "Unrecognized parameter '%s'", key.c_str());
+      return false;
+    }
+    iter.Advance();
+  }
+
+  // Now go over all property values and validate them.
+  for (const auto& pair : *value_out) {
+    const PropType* prop_type = pair.second->GetPropType();
+    CHECK(prop_type) << "Value property type must be available";
+    if (!prop_type->ValidateConstraints(*pair.second, error)) {
+      Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                         errors::commands::kInvalidPropValue,
+                         "Invalid value for property '%s'", pair.first.c_str());
+      return false;
+    }
+  }
+  return true;
+}
+
+bool TypedValueFromJson(const base::Value* value_in,
+                        const PropType* type,
+                        ValueVector* value_out,
+                        ErrorPtr* error) {
+  const base::ListValue* list = nullptr;
+  if (!value_in->GetAsList(&list))
+    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";
+  const PropType* item_type = type->GetArray()->GetItemTypePtr();
+  CHECK(item_type) << "Incomplete array type definition";
+
+  // This value might already contain values from the type defaults.
+  // Clear them first before proceeding.
+  value_out->clear();
+  value_out->reserve(list->GetSize());
+  for (const base::Value* item : *list) {
+    std::unique_ptr<PropValue> prop_value =
+        item_type->CreatePropValue(*item, error);
+    if (!prop_value)
+      return false;
+    value_out->push_back(std::move(prop_value));
+  }
+  return true;
+}
+
+// Compares two sets of key-value pairs from two Objects.
+static bool obj_cmp(const ValueMap::value_type& v1,
+                    const ValueMap::value_type& v2) {
+  return (v1.first == v2.first) && v1.second->IsEqual(v2.second.get());
+}
+
+bool operator==(const ValueMap& obj1, const ValueMap& obj2) {
+  if (obj1.size() != obj2.size())
+    return false;
+
+  auto pair = std::mismatch(obj1.begin(), obj1.end(), obj2.begin(), obj_cmp);
+  return pair == std::make_pair(obj1.end(), obj2.end());
+}
+
+bool operator==(const ValueVector& arr1, const ValueVector& arr2) {
+  if (arr1.size() != arr2.size())
+    return false;
+
+  using Type = const ValueVector::value_type;
+  // Compare two array items.
+  auto arr_cmp = [](const Type& v1, const Type& v2) {
+    return v1->IsEqual(v2.get());
+  };
+  auto pair = std::mismatch(arr1.begin(), arr1.end(), arr2.begin(), arr_cmp);
+  return pair == std::make_pair(arr1.end(), arr2.end());
+}
+
+std::string ToString(const ValueMap& obj) {
+  auto val = TypedValueToJson(obj);
+  std::string str;
+  base::JSONWriter::Write(*val, &str);
+  return str;
+}
+
+std::string ToString(const ValueVector& arr) {
+  auto val = TypedValueToJson(arr);
+  std::string str;
+  base::JSONWriter::Write(*val, &str);
+  return str;
+}
+
+}  // namespace weave
diff --git a/src/commands/schema_utils.h b/src/commands/schema_utils.h
new file mode 100644
index 0000000..0c1d1b3
--- /dev/null
+++ b/src/commands/schema_utils.h
@@ -0,0 +1,136 @@
+// Copyright 2015 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_SRC_COMMANDS_SCHEMA_UTILS_H_
+#define LIBWEAVE_SRC_COMMANDS_SCHEMA_UTILS_H_
+
+#include <cmath>
+#include <limits>
+#include <map>
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/values.h>
+#include <weave/error.h>
+
+namespace weave {
+
+class PropType;
+class PropValue;
+class ObjectSchema;
+class ObjectValue;
+
+// C++ representation of object values.
+using ValueMap = std::map<std::string, std::shared_ptr<const PropValue>>;
+
+// C++ representation of array of values.
+using ValueVector = std::vector<std::shared_ptr<const PropValue>>;
+
+// Converts an object to string.
+std::string ToString(const ValueMap& obj);
+
+// Converts an array to string.
+std::string ToString(const ValueVector& arr);
+
+// InheritableAttribute class is used for specifying various command parameter
+// attributes that can be inherited from a base (parent) schema.
+// The |value| still specifies the actual attribute values, whether it
+// is inherited or overridden, while |is_inherited| can be used to identify
+// if the attribute was inherited (true) or overridden (false).
+template <typename T>
+class InheritableAttribute final {
+ public:
+  InheritableAttribute() = default;
+  explicit InheritableAttribute(T val)
+      : value(std::move(val)), is_inherited(true) {}
+  InheritableAttribute(T val, bool inherited)
+      : value(std::move(val)), is_inherited(inherited) {}
+  T value{};
+  bool is_inherited{true};
+};
+
+// A bunch of helper function to create base::Value for specific C++ classes,
+// including vectors of types. These are used in template classes below
+// to simplify specialization logic.
+std::unique_ptr<base::FundamentalValue> TypedValueToJson(bool value);
+std::unique_ptr<base::FundamentalValue> TypedValueToJson(int value);
+std::unique_ptr<base::FundamentalValue> TypedValueToJson(double value);
+std::unique_ptr<base::StringValue> TypedValueToJson(const std::string& value);
+std::unique_ptr<base::DictionaryValue> TypedValueToJson(const ValueMap& value);
+std::unique_ptr<base::ListValue> TypedValueToJson(const ValueVector& value);
+template <typename T>
+std::unique_ptr<base::ListValue> TypedValueToJson(
+    const std::vector<T>& values) {
+  std::unique_ptr<base::ListValue> list(new base::ListValue);
+  for (const auto& v : values) {
+    auto json = TypedValueToJson(v);
+    CHECK(json);
+    list->Append(json.release());
+  }
+  return list;
+}
+
+// Similarly to TypedValueToJson() function above, the following overloaded
+// helper methods allow to extract specific C++ data types from base::Value.
+// Also used in template classes below to simplify specialization logic.
+// TODO(vitalybuka): Fix this. Interface is misleading. Seeing PropType internal
+// type validation is expected. In reality only ValueMap and ValueVector do
+// validation.
+bool TypedValueFromJson(const base::Value* value_in,
+                        const PropType* type,
+                        bool* value_out,
+                        ErrorPtr* error);
+bool TypedValueFromJson(const base::Value* value_in,
+                        const PropType* type,
+                        int* value_out,
+                        ErrorPtr* error);
+bool TypedValueFromJson(const base::Value* value_in,
+                        const PropType* type,
+                        double* value_out,
+                        ErrorPtr* error);
+bool TypedValueFromJson(const base::Value* value_in,
+                        const PropType* type,
+                        std::string* value_out,
+                        ErrorPtr* error);
+bool TypedValueFromJson(const base::Value* value_in,
+                        const PropType* type,
+                        ValueMap* value_out,
+                        ErrorPtr* error);
+bool TypedValueFromJson(const base::Value* value_in,
+                        const PropType* type,
+                        ValueVector* value_out,
+                        ErrorPtr* error);
+
+bool operator==(const ValueMap& obj1, const ValueMap& obj2);
+bool operator==(const ValueVector& arr1, const ValueVector& arr2);
+
+// CompareValue is a helper function to help with implementing EqualsTo operator
+// for various data types. For most scalar types it is using operator==(),
+// however, for floating point values, rounding errors in binary representation
+// of IEEE floats/doubles can cause straight == comparison to fail for seemingly
+// equivalent values. For these, use approximate comparison with the error
+// margin equal to the epsilon value defined for the corresponding data type.
+// This is used when looking up values for implementation of OneOf constraints
+// which should work reliably for floating points also ("number" type).
+
+// Compare exact types using ==.
+template <typename T>
+inline typename std::enable_if<!std::is_floating_point<T>::value, bool>::type
+CompareValue(const T& v1, const T& v2) {
+  return v1 == v2;
+}
+
+// Compare non-exact types (such as double) using precision margin (epsilon).
+template <typename T>
+inline typename std::enable_if<std::is_floating_point<T>::value, bool>::type
+CompareValue(const T& v1, const T& v2) {
+  return std::abs(v1 - v2) <= std::numeric_limits<T>::epsilon();
+}
+
+}  // namespace weave
+
+#endif  // LIBWEAVE_SRC_COMMANDS_SCHEMA_UTILS_H_
diff --git a/src/commands/schema_utils_unittest.cc b/src/commands/schema_utils_unittest.cc
new file mode 100644
index 0000000..1ea4c90
--- /dev/null
+++ b/src/commands/schema_utils_unittest.cc
@@ -0,0 +1,212 @@
+// Copyright 2015 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/commands/schema_utils.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/values.h>
+#include <gtest/gtest.h>
+
+#include "src/commands/object_schema.h"
+#include "src/commands/prop_types.h"
+#include "src/commands/prop_values.h"
+#include "src/commands/schema_constants.h"
+#include "src/commands/unittest_utils.h"
+
+namespace weave {
+
+using test::CreateDictionaryValue;
+using test::CreateValue;
+
+TEST(CommandSchemaUtils, TypedValueToJson_Scalar) {
+  EXPECT_JSON_EQ("true", *TypedValueToJson(true));
+  EXPECT_JSON_EQ("false", *TypedValueToJson(false));
+
+  EXPECT_JSON_EQ("0", *TypedValueToJson(0));
+  EXPECT_JSON_EQ("-10", *TypedValueToJson(-10));
+  EXPECT_JSON_EQ("20", *TypedValueToJson(20));
+
+  EXPECT_JSON_EQ("0.0", *TypedValueToJson(0.0));
+  EXPECT_JSON_EQ("1.2", *TypedValueToJson(1.2));
+
+  EXPECT_JSON_EQ("'abc'", *TypedValueToJson(std::string("abc")));
+
+  std::vector<bool> bool_array{true, false};
+  EXPECT_JSON_EQ("[true,false]", *TypedValueToJson(bool_array));
+
+  std::vector<int> int_array{1, 2, 5};
+  EXPECT_JSON_EQ("[1,2,5]", *TypedValueToJson(int_array));
+
+  std::vector<double> dbl_array{1.1, 2.2};
+  EXPECT_JSON_EQ("[1.1,2.2]", *TypedValueToJson(dbl_array));
+
+  std::vector<std::string> str_array{"a", "bc"};
+  EXPECT_JSON_EQ("['a','bc']", *TypedValueToJson(str_array));
+}
+
+TEST(CommandSchemaUtils, TypedValueToJson_Object) {
+  IntPropType int_type;
+  ValueMap object;
+
+  object.insert(std::make_pair(
+      "width", int_type.CreateValue(base::FundamentalValue{640}, nullptr)));
+  object.insert(std::make_pair(
+      "height", int_type.CreateValue(base::FundamentalValue{480}, nullptr)));
+  EXPECT_JSON_EQ("{'height':480,'width':640}", *TypedValueToJson(object));
+}
+
+TEST(CommandSchemaUtils, TypedValueToJson_Array) {
+  IntPropType int_type;
+  ValueVector arr;
+
+  arr.push_back(int_type.CreateValue(base::FundamentalValue{640}, nullptr));
+  arr.push_back(int_type.CreateValue(base::FundamentalValue{480}, nullptr));
+  EXPECT_JSON_EQ("[640,480]", *TypedValueToJson(arr));
+}
+
+TEST(CommandSchemaUtils, TypedValueFromJson_Bool) {
+  bool value;
+
+  EXPECT_TRUE(
+      TypedValueFromJson(CreateValue("true").get(), nullptr, &value, nullptr));
+  EXPECT_TRUE(value);
+
+  EXPECT_TRUE(
+      TypedValueFromJson(CreateValue("false").get(), nullptr, &value, nullptr));
+  EXPECT_FALSE(value);
+
+  ErrorPtr error;
+  EXPECT_FALSE(
+      TypedValueFromJson(CreateValue("0").get(), nullptr, &value, &error));
+  EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+  error.reset();
+}
+
+TEST(CommandSchemaUtils, TypedValueFromJson_Int) {
+  int value;
+
+  EXPECT_TRUE(
+      TypedValueFromJson(CreateValue("0").get(), nullptr, &value, nullptr));
+  EXPECT_EQ(0, value);
+
+  EXPECT_TRUE(
+      TypedValueFromJson(CreateValue("23").get(), nullptr, &value, nullptr));
+  EXPECT_EQ(23, value);
+
+  EXPECT_TRUE(
+      TypedValueFromJson(CreateValue("-1234").get(), nullptr, &value, nullptr));
+  EXPECT_EQ(-1234, value);
+
+  ErrorPtr error;
+  EXPECT_FALSE(
+      TypedValueFromJson(CreateValue("'abc'").get(), nullptr, &value, &error));
+  EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+  error.reset();
+}
+
+TEST(CommandSchemaUtils, TypedValueFromJson_Double) {
+  double value;
+
+  EXPECT_TRUE(
+      TypedValueFromJson(CreateValue("0").get(), nullptr, &value, nullptr));
+  EXPECT_DOUBLE_EQ(0.0, value);
+  EXPECT_TRUE(
+      TypedValueFromJson(CreateValue("0.0").get(), nullptr, &value, nullptr));
+  EXPECT_DOUBLE_EQ(0.0, value);
+
+  EXPECT_TRUE(
+      TypedValueFromJson(CreateValue("23").get(), nullptr, &value, nullptr));
+  EXPECT_EQ(23.0, value);
+  EXPECT_TRUE(
+      TypedValueFromJson(CreateValue("23.1").get(), nullptr, &value, nullptr));
+  EXPECT_EQ(23.1, value);
+
+  EXPECT_TRUE(TypedValueFromJson(CreateValue("-1.23E+02").get(), nullptr,
+                                 &value, nullptr));
+  EXPECT_EQ(-123.0, value);
+
+  ErrorPtr error;
+  EXPECT_FALSE(
+      TypedValueFromJson(CreateValue("'abc'").get(), nullptr, &value, &error));
+  EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+  error.reset();
+}
+
+TEST(CommandSchemaUtils, TypedValueFromJson_String) {
+  std::string value;
+
+  EXPECT_TRUE(
+      TypedValueFromJson(CreateValue("''").get(), nullptr, &value, nullptr));
+  EXPECT_EQ("", value);
+
+  EXPECT_TRUE(
+      TypedValueFromJson(CreateValue("'23'").get(), nullptr, &value, nullptr));
+  EXPECT_EQ("23", value);
+
+  EXPECT_TRUE(
+      TypedValueFromJson(CreateValue("'abc'").get(), nullptr, &value, nullptr));
+  EXPECT_EQ("abc", value);
+
+  ErrorPtr error;
+  EXPECT_FALSE(
+      TypedValueFromJson(CreateValue("12").get(), nullptr, &value, &error));
+  EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+  error.reset();
+}
+
+TEST(CommandSchemaUtils, TypedValueFromJson_Object) {
+  ValueMap value;
+  std::unique_ptr<ObjectSchema> schema{new ObjectSchema};
+
+  IntPropType age_prop;
+  age_prop.AddMinMaxConstraint(0, 150);
+  schema->AddProp("age", age_prop.Clone());
+
+  StringPropType name_prop;
+  name_prop.AddLengthConstraint(1, 30);
+  schema->AddProp("name", name_prop.Clone());
+
+  ObjectPropType type;
+  type.SetObjectSchema(std::move(schema));
+  EXPECT_TRUE(TypedValueFromJson(CreateValue("{'age':20,'name':'Bob'}").get(),
+                                 &type, &value, nullptr));
+  ValueMap value2;
+  value2.insert(std::make_pair(
+      "age", age_prop.CreateValue(base::FundamentalValue{20}, nullptr)));
+  value2.insert(std::make_pair(
+      "name", name_prop.CreateValue(base::StringValue("Bob"), nullptr)));
+  EXPECT_EQ(value2, value);
+
+  ErrorPtr error;
+  EXPECT_FALSE(
+      TypedValueFromJson(CreateValue("'abc'").get(), nullptr, &value, &error));
+  EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+  error.reset();
+}
+
+TEST(CommandSchemaUtils, TypedValueFromJson_Array) {
+  ValueVector arr;
+  StringPropType str_type;
+  str_type.AddLengthConstraint(3, 100);
+  ArrayPropType type;
+  type.SetItemType(str_type.Clone());
+
+  EXPECT_TRUE(TypedValueFromJson(CreateValue("['foo', 'bar']").get(), &type,
+                                 &arr, nullptr));
+  ValueVector arr2;
+  arr2.push_back(str_type.CreateValue(base::StringValue{"foo"}, nullptr));
+  arr2.push_back(str_type.CreateValue(base::StringValue{"bar"}, nullptr));
+  EXPECT_EQ(arr2, arr);
+
+  ErrorPtr error;
+  EXPECT_FALSE(TypedValueFromJson(CreateValue("['baz', 'ab']").get(), &type,
+                                  &arr, &error));
+  EXPECT_EQ(errors::commands::kOutOfRange, error->GetCode());
+  error.reset();
+}
+
+}  // namespace weave
diff --git a/src/commands/unittest_utils.h b/src/commands/unittest_utils.h
new file mode 100644
index 0000000..25392ee
--- /dev/null
+++ b/src/commands/unittest_utils.h
@@ -0,0 +1,47 @@
+// Copyright 2015 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_SRC_COMMANDS_UNITTEST_UTILS_H_
+#define LIBWEAVE_SRC_COMMANDS_UNITTEST_UTILS_H_
+
+#include <memory>
+#include <string>
+
+#include <base/values.h>
+#include <gtest/gtest.h>
+#include <weave/test/unittest_utils.h>
+
+#include "src/commands/prop_types.h"
+#include "src/commands/prop_values.h"
+
+namespace weave {
+namespace test {
+
+template <typename T>
+std::unique_ptr<const PropValue> make_prop_value(const base::Value& value) {
+  auto prop_type = PropType::Create(GetValueType<T>());
+  return prop_type->CreatePropValue(value, nullptr);
+}
+
+inline std::unique_ptr<const PropValue> make_int_prop_value(int value) {
+  return make_prop_value<int>(base::FundamentalValue{value});
+}
+
+inline std::unique_ptr<const PropValue> make_double_prop_value(double value) {
+  return make_prop_value<double>(base::FundamentalValue{value});
+}
+
+inline std::unique_ptr<const PropValue> make_bool_prop_value(bool value) {
+  return make_prop_value<bool>(base::FundamentalValue{value});
+}
+
+inline std::unique_ptr<const PropValue> make_string_prop_value(
+    const std::string& value) {
+  return make_prop_value<std::string>(base::StringValue{value});
+}
+
+}  // namespace test
+}  // namespace weave
+
+#endif  // LIBWEAVE_SRC_COMMANDS_UNITTEST_UTILS_H_
diff --git a/src/config.cc b/src/config.cc
index fe50e3b..52f5ea1 100644
--- a/src/config.cc
+++ b/src/config.cc
@@ -65,7 +65,7 @@
   result.oauth_url = "https://accounts.google.com/o/oauth2/";
   result.service_url = kWeaveUrl;
   result.local_anonymous_access_role = AuthScope::kViewer;
-  result.pairing_modes.insert(PairingType::kPinCode);
+  result.pairing_modes.emplace(PairingType::kPinCode);
   result.device_id = base::GenerateGUID();
   return result;
 }
diff --git a/src/config_unittest.cc b/src/config_unittest.cc
index c36bb25..10ed07e 100644
--- a/src/config_unittest.cc
+++ b/src/config_unittest.cc
@@ -10,7 +10,8 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <weave/provider/test/mock_config_store.h>
-#include <weave/test/unittest_utils.h>
+
+#include "src/commands/unittest_utils.h"
 
 using testing::_;
 using testing::Invoke;
diff --git a/src/data_encoding_unittest.cc b/src/data_encoding_unittest.cc
index 53dc534..7e3bfe4 100644
--- a/src/data_encoding_unittest.cc
+++ b/src/data_encoding_unittest.cc
@@ -30,7 +30,7 @@
   EXPECT_EQ("q=test&path=%2Fusr%2Fbin&%23=%25", encoded);
 
   auto params = WebParamsDecode(encoded);
-  EXPECT_EQ(3u, params.size());
+  EXPECT_EQ(3, params.size());
   EXPECT_EQ("q", params[0].first);
   EXPECT_EQ("test", params[0].second);
   EXPECT_EQ("path", params[1].first);
diff --git a/src/device_manager.cc b/src/device_manager.cc
index 52b2882..c0b8e9a 100644
--- a/src/device_manager.cc
+++ b/src/device_manager.cc
@@ -134,7 +134,7 @@
   return state_manager_->SetProperties(dict, error);
 }
 
-const base::Value* DeviceManager::GetStateProperty(
+std::unique_ptr<base::Value> DeviceManager::GetStateProperty(
     const std::string& name) const {
   return state_manager_->GetProperty(name);
 }
@@ -145,7 +145,7 @@
   return state_manager_->SetProperty(name, value, error);
 }
 
-const base::DictionaryValue& DeviceManager::GetState() const {
+std::unique_ptr<base::DictionaryValue> DeviceManager::GetState() const {
   return state_manager_->GetState();
 }
 
diff --git a/src/device_manager.h b/src/device_manager.h
index 6c3df05..ccf8778 100644
--- a/src/device_manager.h
+++ b/src/device_manager.h
@@ -52,11 +52,12 @@
                                   ErrorPtr* error) override;
   bool SetStateProperties(const base::DictionaryValue& dict,
                           ErrorPtr* error) override;
-  const base::Value* GetStateProperty(const std::string& name) const override;
+  std::unique_ptr<base::Value> GetStateProperty(
+      const std::string& name) const override;
   bool SetStateProperty(const std::string& name,
                         const base::Value& value,
                         ErrorPtr* error) override;
-  const base::DictionaryValue& GetState() const override;
+  std::unique_ptr<base::DictionaryValue> GetState() const override;
   void Register(const std::string& ticket_id,
                 const DoneCallback& callback) override;
   GcdState GetGcdState() const override;
diff --git a/src/device_registration_info.cc b/src/device_registration_info.cc
index 0c914b7..751e530 100644
--- a/src/device_registration_info.cc
+++ b/src/device_registration_info.cc
@@ -480,12 +480,14 @@
 std::unique_ptr<base::DictionaryValue>
 DeviceRegistrationInfo::BuildDeviceResource(ErrorPtr* error) {
   // Limit only to commands that are visible to the cloud.
-  auto commands =
-      command_manager_->GetCommandDictionary().GetCommandsAsJson(error);
+  auto commands = command_manager_->GetCommandDictionary().GetCommandsAsJson(
+      [](const CommandDefinition* def) { return def->GetVisibility().cloud; },
+      true, error);
   if (!commands)
     return nullptr;
 
-  const base::DictionaryValue& state = state_manager_->GetState();
+  std::unique_ptr<base::DictionaryValue> state = state_manager_->GetState();
+  CHECK(state);
 
   std::unique_ptr<base::DictionaryValue> resource{new base::DictionaryValue};
   if (!GetSettings().cloud_id.empty())
@@ -506,7 +508,7 @@
   }
   resource->Set("channel", channel.release());
   resource->Set("commandDefs", commands.release());
-  resource->Set("state", state.DeepCopy());
+  resource->Set("state", state.release());
 
   return resource;
 }
@@ -1119,11 +1121,23 @@
     return;
 
   std::unique_ptr<base::ListValue> patches{new base::ListValue};
-  for (auto& state_change : state_changes) {
+  for (const auto& state_change : state_changes) {
     std::unique_ptr<base::DictionaryValue> patch{new base::DictionaryValue};
     patch->SetString("timeMs",
                      std::to_string(state_change.timestamp.ToJavaTime()));
-    patch->Set("patch", state_change.changed_properties.release());
+
+    std::unique_ptr<base::DictionaryValue> changes{new base::DictionaryValue};
+    for (const auto& pair : state_change.changed_properties) {
+      auto value = pair.second->ToJson();
+      CHECK(value);
+      // The key in |pair.first| is the full property name in format
+      // "package.property_name", so must use DictionaryValue::Set() instead of
+      // DictionaryValue::SetWithoutPathExpansion to recreate the JSON
+      // property tree properly.
+      changes->Set(pair.first, value.release());
+    }
+    patch->Set("patch", changes.release());
+
     patches->Append(patch.release());
   }
 
diff --git a/src/device_registration_info_unittest.cc b/src/device_registration_info_unittest.cc
index f2fe4c4..df4a438 100644
--- a/src/device_registration_info_unittest.cc
+++ b/src/device_registration_info_unittest.cc
@@ -11,10 +11,10 @@
 #include <weave/provider/test/fake_task_runner.h>
 #include <weave/provider/test/mock_config_store.h>
 #include <weave/provider/test/mock_http_client.h>
-#include <weave/test/unittest_utils.h>
 
 #include "src/bind_lambda.h"
 #include "src/commands/command_manager.h"
+#include "src/commands/unittest_utils.h"
 #include "src/http_constants.h"
 #include "src/states/mock_state_change_queue_interface.h"
 #include "src/states/state_manager.h"
@@ -355,14 +355,16 @@
   auto json_cmds = CreateDictionaryValue(R"({
     'base': {
       'reboot': {
-        'parameters': {'delay': {'minimum': 10, 'type': 'integer'}},
-        'minimalRole': 'user'
+        'parameters': {'delay': {'minimum': 10}},
+        'minimalRole': 'user',
+        'results': {}
       }
     },
     'robot': {
       '_jump': {
-        'parameters': {'_height': {'type': 'integer'}},
-        'minimalRole': 'user'
+        'parameters': {'_height': 'integer'},
+        'minimalRole': 'user',
+        'results': {}
       }
     }
   })");
diff --git a/src/notification/notification_channel.h b/src/notification/notification_channel.h
index ef152a8..5fb7993 100644
--- a/src/notification/notification_channel.h
+++ b/src/notification/notification_channel.h
@@ -17,7 +17,7 @@
 
 class NotificationChannel {
  public:
-  virtual ~NotificationChannel() {}
+  virtual ~NotificationChannel() = default;
 
   virtual std::string GetName() const = 0;
   virtual bool IsConnected() const = 0;
diff --git a/src/notification/notification_delegate.h b/src/notification/notification_delegate.h
index 263c2f3..719d76d 100644
--- a/src/notification/notification_delegate.h
+++ b/src/notification/notification_delegate.h
@@ -24,7 +24,7 @@
   virtual void OnDeviceDeleted(const std::string& cloud_id) = 0;
 
  protected:
-  virtual ~NotificationDelegate() {}
+  virtual ~NotificationDelegate() = default;
 };
 
 }  // namespace weave
diff --git a/src/notification/notification_parser.cc b/src/notification/notification_parser.cc
index d7c4f48..0a27f1c 100644
--- a/src/notification/notification_parser.cc
+++ b/src/notification/notification_parser.cc
@@ -46,9 +46,9 @@
 
   std::string kind;
   if (!notification.GetString("kind", &kind) ||
-      kind != "weave#notification") {
+      kind != "clouddevices#notification") {
     LOG(WARNING) << "Push notification should have 'kind' property set to "
-                    "weave#notification";
+                    "clouddevices#notification";
     return false;
   }
 
diff --git a/src/notification/notification_parser_unittest.cc b/src/notification/notification_parser_unittest.cc
index c69f9dd..d0b43fe 100644
--- a/src/notification/notification_parser_unittest.cc
+++ b/src/notification/notification_parser_unittest.cc
@@ -6,7 +6,8 @@
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
-#include <weave/test/unittest_utils.h>
+
+#include "src/commands/unittest_utils.h"
 
 using testing::SaveArg;
 using testing::Invoke;
@@ -37,11 +38,11 @@
 
 TEST_F(NotificationParserTest, CommandCreated) {
   auto json = CreateDictionaryValue(R"({
-    "kind": "weave#notification",
+    "kind": "clouddevices#notification",
     "type": "COMMAND_CREATED",
     "deviceId": "device_id",
     "command": {
-      "kind": "weave#command",
+      "kind": "clouddevices#command",
       "deviceId": "device_id",
       "state": "queued",
       "name": "storage.list",
@@ -56,7 +57,7 @@
   })");
 
   const char expected_json[] = R"({
-      "kind": "weave#command",
+      "kind": "clouddevices#command",
       "deviceId": "device_id",
       "state": "queued",
       "name": "storage.list",
@@ -75,7 +76,7 @@
 
 TEST_F(NotificationParserTest, DeviceDeleted) {
   auto json = CreateDictionaryValue(R"({
-    "kind":"weave#notification",
+    "kind":"clouddevices#notification",
     "type":"DEVICE_DELETED",
     "deviceId":"some_device_id"
   })");
@@ -91,7 +92,7 @@
     "type": "COMMAND_CREATED",
     "deviceId": "device_id",
     "command": {
-      "kind": "weave#command",
+      "kind": "clouddevices#command",
       "deviceId": "device_id",
       "state": "queued",
       "name": "storage.list",
@@ -110,10 +111,10 @@
 
 TEST_F(NotificationParserTest, Failure_NoType) {
   auto json = CreateDictionaryValue(R"({
-    "kind": "weave#notification",
+    "kind": "clouddevices#notification",
     "deviceId": "device_id",
     "command": {
-      "kind": "weave#command",
+      "kind": "clouddevices#command",
       "deviceId": "device_id",
       "state": "queued",
       "name": "storage.list",
@@ -132,11 +133,11 @@
 
 TEST_F(NotificationParserTest, IgnoredNotificationType) {
   auto json = CreateDictionaryValue(R"({
-    "kind": "weave#notification",
+    "kind": "clouddevices#notification",
     "type": "COMMAND_EXPIRED",
     "deviceId": "device_id",
     "command": {
-      "kind": "weave#command",
+      "kind": "clouddevices#command",
       "deviceId": "device_id",
       "state": "queued",
       "name": "storage.list",
diff --git a/src/notification/xmpp_channel.h b/src/notification/xmpp_channel.h
index 50e84d2..e6185d9 100644
--- a/src/notification/xmpp_channel.h
+++ b/src/notification/xmpp_channel.h
@@ -33,7 +33,7 @@
   virtual void SendMessage(const std::string& message) = 0;
 
  protected:
-  virtual ~XmppChannelInterface() {}
+  virtual ~XmppChannelInterface() = default;
 };
 
 class XmppChannel : public NotificationChannel,
diff --git a/src/notification/xmpp_iq_stanza_handler.cc b/src/notification/xmpp_iq_stanza_handler.cc
index 9ac4223..8753030 100644
--- a/src/notification/xmpp_iq_stanza_handler.cc
+++ b/src/notification/xmpp_iq_stanza_handler.cc
@@ -72,7 +72,7 @@
     const ResponseCallback& response_callback,
     const TimeoutCallback& timeout_callback) {
   // Remember the response callback to call later.
-  requests_.insert(std::make_pair(++last_request_id_, response_callback));
+  requests_.emplace(++last_request_id_, response_callback);
   // Schedule a time-out callback for this request.
   if (timeout < base::TimeDelta::Max()) {
     task_runner_->PostDelayedTask(
diff --git a/src/notification/xmpp_stream_parser.cc b/src/notification/xmpp_stream_parser.cc
index 9efaeb5..de3b8f1 100644
--- a/src/notification/xmpp_stream_parser.cc
+++ b/src/notification/xmpp_stream_parser.cc
@@ -36,7 +36,7 @@
   std::map<std::string, std::string> attributes;
   if (attr != nullptr) {
     for (size_t n = 0; attr[n] != nullptr && attr[n + 1] != nullptr; n += 2) {
-      attributes.insert(std::make_pair(attr[n], attr[n + 1]));
+      attributes.emplace(attr[n], attr[n + 1]);
     }
   }
   self->OnOpenElement(element, std::move(attributes));
diff --git a/src/notification/xmpp_stream_parser.h b/src/notification/xmpp_stream_parser.h
index b8f5723..41faaff 100644
--- a/src/notification/xmpp_stream_parser.h
+++ b/src/notification/xmpp_stream_parser.h
@@ -49,7 +49,7 @@
     virtual void OnStanza(std::unique_ptr<XmlNode> stanza) = 0;
 
    protected:
-    virtual ~Delegate() {}
+    virtual ~Delegate() = default;
   };
 
   explicit XmppStreamParser(Delegate* delegate);
diff --git a/src/privet/cloud_delegate.cc b/src/privet/cloud_delegate.cc
index c771366..fddc8e1 100644
--- a/src/privet/cloud_delegate.cc
+++ b/src/privet/cloud_delegate.cc
@@ -134,9 +134,7 @@
   }
 
   std::string GetCloudId() const override {
-    return connection_state_.status() > ConnectionState::kUnconfigured
-               ? device_->GetSettings().cloud_id
-               : "";
+    return device_->GetSettings().cloud_id;
   }
 
   const base::DictionaryValue& GetState() const override { return state_; }
@@ -213,7 +211,7 @@
  private:
   void OnCommandAdded(Command* command) {
     // Set to 0 for any new unknown command.
-    command_owners_.insert(std::make_pair(command->GetID(), 0));
+    command_owners_.emplace(command->GetID(), 0);
   }
 
   void OnCommandRemoved(Command* command) {
@@ -243,15 +241,17 @@
 
   void OnStateChanged() {
     state_.Clear();
-    const auto& state = state_manager_->GetState();
-    state_.MergeDictionary(&state);
+    auto state = state_manager_->GetState();
+    CHECK(state);
+    state_.MergeDictionary(state.get());
     NotifyOnStateChanged();
   }
 
   void OnCommandDefChanged() {
     command_defs_.Clear();
-    auto commands =
-      command_manager_->GetCommandDictionary().GetCommandsAsJson(nullptr);
+    auto commands = command_manager_->GetCommandDictionary().GetCommandsAsJson(
+        [](const CommandDefinition* def) { return def->GetVisibility().local; },
+        true, nullptr);
     CHECK(commands);
     command_defs_.MergeDictionary(commands.get());
     NotifyOnCommandDefsChanged();
diff --git a/src/privet/cloud_delegate.h b/src/privet/cloud_delegate.h
index 6396519..8763fbe 100644
--- a/src/privet/cloud_delegate.h
+++ b/src/privet/cloud_delegate.h
@@ -45,7 +45,7 @@
 
   class Observer {
    public:
-    virtual ~Observer() {}
+    virtual ~Observer() = default;
 
     virtual void OnDeviceInfoChanged() {}
     virtual void OnCommandDefsChanged() {}
diff --git a/src/privet/privet_handler.cc b/src/privet/privet_handler.cc
index 157feed..157c982 100644
--- a/src/privet/privet_handler.cc
+++ b/src/privet/privet_handler.cc
@@ -469,7 +469,7 @@
   params.handler = handler;
   params.scope = scope;
   params.https_only = false;
-  CHECK(handlers_.insert(std::make_pair(path, params)).second);
+  CHECK(handlers_.emplace(path, params).second);
 }
 
 void PrivetHandler::AddSecureHandler(const std::string& path,
@@ -479,7 +479,7 @@
   params.handler = handler;
   params.scope = scope;
   params.https_only = true;
-  CHECK(handlers_.insert(std::make_pair(path, params)).second);
+  CHECK(handlers_.emplace(path, params).second);
 }
 
 void PrivetHandler::HandleInfo(const base::DictionaryValue&,
diff --git a/src/privet/security_delegate.h b/src/privet/security_delegate.h
index 40f297f..1d28ba3 100644
--- a/src/privet/security_delegate.h
+++ b/src/privet/security_delegate.h
@@ -19,7 +19,7 @@
 // Interface to provide Security related logic for |PrivetHandler|.
 class SecurityDelegate {
  public:
-  virtual ~SecurityDelegate() {}
+  virtual ~SecurityDelegate() = default;
 
   // Creates access token for the given scope, user id and |time|.
   virtual std::string CreateAccessToken(const UserInfo& user_info,
diff --git a/src/privet/security_manager.cc b/src/privet/security_manager.cc
index 7e35e79..1b4e3c5 100644
--- a/src/privet/security_manager.cc
+++ b/src/privet/security_manager.cc
@@ -24,6 +24,10 @@
 #include "src/string_utils.h"
 #include "third_party/chromium/crypto/p224_spake.h"
 
+#if !defined(ARCH_CPU_LITTLE_ENDIAN)
+#error "Big-endian is not supported. See b/25017606"
+#endif
+
 namespace weave {
 namespace privet {
 
@@ -254,7 +258,7 @@
   } while (confirmed_sessions_.find(session) != confirmed_sessions_.end() ||
            pending_sessions_.find(session) != pending_sessions_.end());
   std::string commitment = spake->GetMessage();
-  pending_sessions_.insert(std::make_pair(session, std::move(spake)));
+  pending_sessions_.emplace(session, std::move(spake));
 
   task_runner_->PostDelayedTask(
       FROM_HERE,
@@ -313,8 +317,7 @@
   std::vector<uint8_t> cert_hmac = HmacSha256(
       std::vector<uint8_t>(key.begin(), key.end()), certificate_fingerprint_);
   *signature = Base64Encode(cert_hmac);
-  confirmed_sessions_.insert(
-      std::make_pair(session->first, std::move(session->second)));
+  confirmed_sessions_.emplace(session->first, std::move(session->second));
   task_runner_->PostDelayedTask(
       FROM_HERE,
       base::Bind(base::IgnoreResult(&SecurityManager::CloseConfirmedSession),
diff --git a/src/privet/security_manager.h b/src/privet/security_manager.h
index bb513e6..c99201b 100644
--- a/src/privet/security_manager.h
+++ b/src/privet/security_manager.h
@@ -41,7 +41,7 @@
 
   class KeyExchanger {
    public:
-    virtual ~KeyExchanger() {}
+    virtual ~KeyExchanger() = default;
 
     virtual const std::string& GetMessage() = 0;
     virtual bool ProcessMessage(const std::string& message,
diff --git a/src/privet/security_manager_unittest.cc b/src/privet/security_manager_unittest.cc
index 0880b14..7229710 100644
--- a/src/privet/security_manager_unittest.cc
+++ b/src/privet/security_manager_unittest.cc
@@ -109,7 +109,7 @@
 };
 
 TEST_F(SecurityManagerTest, RandomSecret) {
-  EXPECT_GE(security_.GetSecret().size(), 32u);
+  EXPECT_GE(security_.GetSecret().size(), 32);
   EXPECT_TRUE(IsBase64(security_.GetSecret()));
 }
 
diff --git a/src/privet/wifi_delegate.h b/src/privet/wifi_delegate.h
index 9bd5157..ae5e520 100644
--- a/src/privet/wifi_delegate.h
+++ b/src/privet/wifi_delegate.h
@@ -18,7 +18,7 @@
 class WifiDelegate {
  public:
   WifiDelegate() = default;
-  virtual ~WifiDelegate() {}
+  virtual ~WifiDelegate() = default;
 
   // Returns status of the WiFi connection.
   virtual const ConnectionState& GetConnectionState() const = 0;
diff --git a/src/privet/wifi_ssid_generator_unittest.cc b/src/privet/wifi_ssid_generator_unittest.cc
index 10680c8..398e105 100644
--- a/src/privet/wifi_ssid_generator_unittest.cc
+++ b/src/privet/wifi_ssid_generator_unittest.cc
@@ -23,7 +23,7 @@
 };
 
 TEST_F(WifiSsidGeneratorTest, GenerateFlagsNoHostedAp) {
-  EXPECT_EQ(ssid_generator_.GenerateFlags().size(), 2u);
+  EXPECT_EQ(ssid_generator_.GenerateFlags().size(), 2);
 
   wifi_.connection_state_ = ConnectionState{ConnectionState::kUnconfigured};
   gcd_.connection_state_ = ConnectionState{ConnectionState::kUnconfigured};
@@ -42,7 +42,7 @@
 TEST_F(WifiSsidGeneratorTest, GenerateFlagsWithHostedAp) {
   EXPECT_CALL(wifi_, GetHostedSsid())
       .WillRepeatedly(Return(ssid_generator_.GenerateSsid()));
-  EXPECT_EQ(ssid_generator_.GenerateFlags().size(), 2u);
+  EXPECT_EQ(ssid_generator_.GenerateFlags().size(), 2);
 
   wifi_.connection_state_ = ConnectionState{ConnectionState::kUnconfigured};
   gcd_.connection_state_ = ConnectionState{ConnectionState::kUnconfigured};
@@ -59,7 +59,7 @@
 }
 
 TEST_F(WifiSsidGeneratorTest, GenerateSsid31orLess) {
-  EXPECT_LE(ssid_generator_.GenerateSsid().size(), 31u);
+  EXPECT_LE(ssid_generator_.GenerateSsid().size(), 31);
 }
 
 TEST_F(WifiSsidGeneratorTest, GenerateSsidValue) {
diff --git a/src/states/mock_state_change_queue_interface.h b/src/states/mock_state_change_queue_interface.h
index 78c6c82..c3f50c0 100644
--- a/src/states/mock_state_change_queue_interface.h
+++ b/src/states/mock_state_change_queue_interface.h
@@ -16,11 +16,9 @@
 class MockStateChangeQueueInterface : public StateChangeQueueInterface {
  public:
   MOCK_CONST_METHOD0(IsEmpty, bool());
-  MOCK_METHOD2(MockNotifyPropertiesUpdated,
-               bool(base::Time timestamp,
-                    const base::DictionaryValue& changed_properties));
-  MOCK_METHOD0(MockGetAndClearRecordedStateChanges,
-               std::vector<StateChange>&());
+  MOCK_METHOD2(NotifyPropertiesUpdated,
+               bool(base::Time timestamp, ValueMap changed_properties));
+  MOCK_METHOD0(GetAndClearRecordedStateChanges, std::vector<StateChange>());
   MOCK_CONST_METHOD0(GetLastStateChangeId, UpdateID());
   MOCK_METHOD1(MockAddOnStateUpdatedCallback,
                base::CallbackList<void(UpdateID)>::Subscription*(
@@ -28,15 +26,6 @@
   MOCK_METHOD1(NotifyStateUpdatedOnServer, void(UpdateID));
 
  private:
-  bool NotifyPropertiesUpdated(
-      base::Time timestamp,
-      std::unique_ptr<base::DictionaryValue> changed_properties) override {
-    return MockNotifyPropertiesUpdated(timestamp, *changed_properties);
-  }
-  std::vector<StateChange> GetAndClearRecordedStateChanges() override {
-    return std::move(MockGetAndClearRecordedStateChanges());
-  }
-
   Token AddOnStateUpdatedCallback(
       const base::Callback<void(UpdateID)>& callback) override {
     return Token{MockAddOnStateUpdatedCallback(callback)};
diff --git a/src/states/state_change_queue.cc b/src/states/state_change_queue.cc
index ba7bf65..288dadc 100644
--- a/src/states/state_change_queue.cc
+++ b/src/states/state_change_queue.cc
@@ -5,7 +5,6 @@
 #include "src/states/state_change_queue.h"
 
 #include <base/logging.h>
-#include <base/values.h>
 
 namespace weave {
 
@@ -14,15 +13,12 @@
   CHECK_GT(max_queue_size_, 0U) << "Max queue size must not be zero";
 }
 
-bool StateChangeQueue::NotifyPropertiesUpdated(
-    base::Time timestamp,
-    std::unique_ptr<base::DictionaryValue> changed_properties) {
+bool StateChangeQueue::NotifyPropertiesUpdated(base::Time timestamp,
+                                               ValueMap changed_properties) {
   auto& stored_changes = state_changes_[timestamp];
   // Merge the old property set.
-  if (stored_changes)
-    stored_changes->MergeDictionary(changed_properties.get());
-  else
-    stored_changes = std::move(changed_properties);
+  changed_properties.insert(stored_changes.begin(), stored_changes.end());
+  stored_changes = std::move(changed_properties);
 
   while (state_changes_.size() > max_queue_size_) {
     // Queue is full.
@@ -34,8 +30,8 @@
     auto element_old = state_changes_.begin();
     auto element_new = std::next(element_old);
     // This will skip elements that exist in both [old] and [new].
-    element_old->second->MergeDictionary(element_new->second.get());
-    std::swap(element_old->second, element_new->second);
+    element_new->second.insert(element_old->second.begin(),
+                               element_old->second.end());
     state_changes_.erase(element_old);
   }
   ++last_change_id_;
@@ -45,8 +41,8 @@
 std::vector<StateChange> StateChangeQueue::GetAndClearRecordedStateChanges() {
   std::vector<StateChange> changes;
   changes.reserve(state_changes_.size());
-  for (auto& pair : state_changes_) {
-    changes.push_back(StateChange{pair.first, std::move(pair.second)});
+  for (const auto& pair : state_changes_) {
+    changes.emplace_back(pair.first, std::move(pair.second));
   }
   state_changes_.clear();
   return changes;
diff --git a/src/states/state_change_queue.h b/src/states/state_change_queue.h
index 314f746..00b827f 100644
--- a/src/states/state_change_queue.h
+++ b/src/states/state_change_queue.h
@@ -21,9 +21,8 @@
 
   // Overrides from StateChangeQueueInterface.
   bool IsEmpty() const override { return state_changes_.empty(); }
-  bool NotifyPropertiesUpdated(
-      base::Time timestamp,
-      std::unique_ptr<base::DictionaryValue> changed_properties) override;
+  bool NotifyPropertiesUpdated(base::Time timestamp,
+                               ValueMap changed_properties) override;
   std::vector<StateChange> GetAndClearRecordedStateChanges() override;
   UpdateID GetLastStateChangeId() const override { return last_change_id_; }
   Token AddOnStateUpdatedCallback(
@@ -36,7 +35,7 @@
   const size_t max_queue_size_;
 
   // Accumulated list of device state change notifications.
-  std::map<base::Time, std::unique_ptr<base::DictionaryValue>> state_changes_;
+  std::map<base::Time, ValueMap> state_changes_;
 
   // An ID of last state change update. Each NotifyPropertiesUpdated()
   // invocation increments this value by 1.
diff --git a/src/states/state_change_queue_interface.h b/src/states/state_change_queue_interface.h
index 631a127..e3b3650 100644
--- a/src/states/state_change_queue_interface.h
+++ b/src/states/state_change_queue_interface.h
@@ -10,9 +10,7 @@
 #include <base/callback_list.h>
 #include <base/time/time.h>
 
-namespace base {
-class DictionaryValue;
-}  // namespace base
+#include "src/commands/schema_utils.h"
 
 namespace weave {
 
@@ -21,11 +19,10 @@
 // |changed_properties| contains a property set with the new property values
 // which were updated at the time the event was recorded.
 struct StateChange {
-  StateChange(base::Time time,
-              std::unique_ptr<base::DictionaryValue> properties)
+  StateChange(base::Time time, ValueMap properties)
       : timestamp{time}, changed_properties{std::move(properties)} {}
   base::Time timestamp;
-  std::unique_ptr<base::DictionaryValue> changed_properties;
+  ValueMap changed_properties;
 };
 
 // An abstract interface to StateChangeQueue to record and retrieve state
@@ -40,9 +37,8 @@
   virtual bool IsEmpty() const = 0;
 
   // Called by StateManager when device state properties are updated.
-  virtual bool NotifyPropertiesUpdated(
-      base::Time timestamp,
-      std::unique_ptr<base::DictionaryValue> changed_properties) = 0;
+  virtual bool NotifyPropertiesUpdated(base::Time timestamp,
+                                       ValueMap changed_properties) = 0;
 
   // Returns the recorded state changes since last time this method was called.
   virtual std::vector<StateChange> GetAndClearRecordedStateChanges() = 0;
diff --git a/src/states/state_change_queue_unittest.cc b/src/states/state_change_queue_unittest.cc
index bb2947a..9f26071 100644
--- a/src/states/state_change_queue_unittest.cc
+++ b/src/states/state_change_queue_unittest.cc
@@ -5,14 +5,12 @@
 #include "src/states/state_change_queue.h"
 
 #include <gtest/gtest.h>
-#include <weave/test/unittest_utils.h>
 
 #include "src/bind_lambda.h"
+#include "src/commands/unittest_utils.h"
 
 namespace weave {
 
-using test::CreateDictionaryValue;
-
 class StateChangeQueueTest : public ::testing::Test {
  public:
   void SetUp() override { queue_.reset(new StateChangeQueue(100)); }
@@ -24,44 +22,48 @@
 
 TEST_F(StateChangeQueueTest, Empty) {
   EXPECT_TRUE(queue_->IsEmpty());
-  EXPECT_EQ(0u, queue_->GetLastStateChangeId());
+  EXPECT_EQ(0, queue_->GetLastStateChangeId());
   EXPECT_TRUE(queue_->GetAndClearRecordedStateChanges().empty());
 }
 
 TEST_F(StateChangeQueueTest, UpdateOne) {
-  auto timestamp = base::Time::Now();
-  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
-      timestamp, CreateDictionaryValue("{'prop': {'name': 23}}")));
+  StateChange change{base::Time::Now(),
+                     ValueMap{{"prop.name", test::make_int_prop_value(23)}}};
+  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(change.timestamp,
+                                              change.changed_properties));
   EXPECT_FALSE(queue_->IsEmpty());
-  EXPECT_EQ(1u, queue_->GetLastStateChangeId());
+  EXPECT_EQ(1, queue_->GetLastStateChangeId());
   auto changes = queue_->GetAndClearRecordedStateChanges();
-  EXPECT_EQ(1u, queue_->GetLastStateChangeId());
-  ASSERT_EQ(1u, changes.size());
-  EXPECT_EQ(timestamp, changes.front().timestamp);
-  EXPECT_JSON_EQ("{'prop':{'name': 23}}", *changes.front().changed_properties);
+  EXPECT_EQ(1, queue_->GetLastStateChangeId());
+  ASSERT_EQ(1, changes.size());
+  EXPECT_EQ(change.timestamp, changes.front().timestamp);
+  EXPECT_EQ(change.changed_properties, changes.front().changed_properties);
   EXPECT_TRUE(queue_->IsEmpty());
   EXPECT_TRUE(queue_->GetAndClearRecordedStateChanges().empty());
 }
 
-TEST_F(StateChangeQueueTest, UpdateMany) {
-  auto timestamp1 = base::Time::Now();
-  const std::string state1 = "{'prop': {'name1': 23}}";
-  auto timestamp2 = timestamp1 + base::TimeDelta::FromSeconds(1);
-  const std::string state2 =
-      "{'prop': {'name1': 17, 'name2': 1.0, 'name3': false}}";
-  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
-      timestamp1, CreateDictionaryValue(state1)));
-  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
-      timestamp2, CreateDictionaryValue(state2)));
-
-  EXPECT_EQ(2u, queue_->GetLastStateChangeId());
+// TODO(vitalybuka): Fix flakiness.
+TEST_F(StateChangeQueueTest, DISABLED_UpdateMany) {
+  StateChange change1{base::Time::Now(),
+                      ValueMap{{"prop.name1", test::make_int_prop_value(23)}}};
+  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(change1.timestamp,
+                                              change1.changed_properties));
+  StateChange change2{base::Time::Now(),
+                      ValueMap{
+                          {"prop.name1", test::make_int_prop_value(17)},
+                          {"prop.name2", test::make_double_prop_value(1.0)},
+                          {"prop.name3", test::make_bool_prop_value(false)},
+                      }};
+  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(change2.timestamp,
+                                              change2.changed_properties));
+  EXPECT_EQ(2, queue_->GetLastStateChangeId());
   EXPECT_FALSE(queue_->IsEmpty());
   auto changes = queue_->GetAndClearRecordedStateChanges();
-  ASSERT_EQ(2u, changes.size());
-  EXPECT_EQ(timestamp1, changes[0].timestamp);
-  EXPECT_JSON_EQ(state1, *changes[0].changed_properties);
-  EXPECT_EQ(timestamp2, changes[1].timestamp);
-  EXPECT_JSON_EQ(state2, *changes[1].changed_properties);
+  ASSERT_EQ(2, changes.size());
+  EXPECT_EQ(change1.timestamp, changes[0].timestamp);
+  EXPECT_EQ(change1.changed_properties, changes[0].changed_properties);
+  EXPECT_EQ(change2.timestamp, changes[1].timestamp);
+  EXPECT_EQ(change2.changed_properties, changes[1].changed_properties);
   EXPECT_TRUE(queue_->IsEmpty());
   EXPECT_TRUE(queue_->GetAndClearRecordedStateChanges().empty());
 }
@@ -71,27 +73,33 @@
   base::TimeDelta time_delta = base::TimeDelta::FromMinutes(1);
 
   ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
-      timestamp, CreateDictionaryValue("{'prop': {'name1': 1}}")));
+      timestamp, ValueMap{{"prop.name1", test::make_int_prop_value(1)}}));
 
   ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
-      timestamp, CreateDictionaryValue("{'prop': {'name2': 2}}")));
+      timestamp, ValueMap{{"prop.name2", test::make_int_prop_value(2)}}));
 
   ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
-      timestamp, CreateDictionaryValue("{'prop': {'name1': 3}}")));
+      timestamp, ValueMap{{"prop.name1", test::make_int_prop_value(3)}}));
 
   ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
-      timestamp + time_delta, CreateDictionaryValue("{'prop': {'name1': 4}}")));
+      timestamp + time_delta,
+      ValueMap{{"prop.name1", test::make_int_prop_value(4)}}));
 
   auto changes = queue_->GetAndClearRecordedStateChanges();
-  EXPECT_EQ(4u, queue_->GetLastStateChangeId());
-  ASSERT_EQ(2u, changes.size());
+  EXPECT_EQ(4, queue_->GetLastStateChangeId());
+  ASSERT_EQ(2, changes.size());
 
-  const std::string expected1 = "{'prop': {'name1': 3, 'name2': 2}}";
-  const std::string expected2 = "{'prop': {'name1': 4}}";
+  ValueMap expected1{
+      {"prop.name1", test::make_int_prop_value(3)},
+      {"prop.name2", test::make_int_prop_value(2)},
+  };
+  ValueMap expected2{
+      {"prop.name1", test::make_int_prop_value(4)},
+  };
   EXPECT_EQ(timestamp, changes[0].timestamp);
-  EXPECT_JSON_EQ(expected1, *changes[0].changed_properties);
+  EXPECT_EQ(expected1, changes[0].changed_properties);
   EXPECT_EQ(timestamp + time_delta, changes[1].timestamp);
-  EXPECT_JSON_EQ(expected2, *changes[1].changed_properties);
+  EXPECT_EQ(expected2, changes[1].changed_properties);
 }
 
 TEST_F(StateChangeQueueTest, MaxQueueSize) {
@@ -101,29 +109,43 @@
   base::TimeDelta time_delta2 = base::TimeDelta::FromMinutes(3);
 
   ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
-      start_time, CreateDictionaryValue("{'prop': {'name1': 1, 'name2': 2}}")));
+      start_time, ValueMap{
+                      {"prop.name1", test::make_int_prop_value(1)},
+                      {"prop.name2", test::make_int_prop_value(2)},
+                  }));
 
   ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
       start_time + time_delta1,
-      CreateDictionaryValue("{'prop': {'name1': 3, 'name3': 4}}")));
+      ValueMap{
+          {"prop.name1", test::make_int_prop_value(3)},
+          {"prop.name3", test::make_int_prop_value(4)},
+      }));
 
   ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
       start_time + time_delta2,
-      CreateDictionaryValue("{'prop': {'name10': 10, 'name11': 11}}")));
+      ValueMap{
+          {"prop.name10", test::make_int_prop_value(10)},
+          {"prop.name11", test::make_int_prop_value(11)},
+      }));
 
-  EXPECT_EQ(3u, queue_->GetLastStateChangeId());
+  EXPECT_EQ(3, queue_->GetLastStateChangeId());
   auto changes = queue_->GetAndClearRecordedStateChanges();
-  ASSERT_EQ(2u, changes.size());
+  ASSERT_EQ(2, changes.size());
 
-  const std::string expected1 =
-      "{'prop': {'name1': 3, 'name2': 2, 'name3': 4}}";
+  ValueMap expected1{
+      {"prop.name1", test::make_int_prop_value(3)},
+      {"prop.name2", test::make_int_prop_value(2)},
+      {"prop.name3", test::make_int_prop_value(4)},
+  };
   EXPECT_EQ(start_time + time_delta1, changes[0].timestamp);
-  EXPECT_JSON_EQ(expected1, *changes[0].changed_properties);
+  EXPECT_EQ(expected1, changes[0].changed_properties);
 
-  const std::string expected2 =
-      "{'prop': {'name10': 10, 'name11': 11}}";
+  ValueMap expected2{
+      {"prop.name10", test::make_int_prop_value(10)},
+      {"prop.name11", test::make_int_prop_value(11)},
+  };
   EXPECT_EQ(start_time + time_delta2, changes[1].timestamp);
-  EXPECT_JSON_EQ(expected2, *changes[1].changed_properties);
+  EXPECT_EQ(expected2, changes[1].changed_properties);
 }
 
 TEST_F(StateChangeQueueTest, ImmediateStateChangeNotification) {
@@ -139,8 +161,10 @@
 TEST_F(StateChangeQueueTest, DelayedStateChangeNotification) {
   // When queue is not empty, registering a new callback will not trigger it.
   ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
-      base::Time::Now(),
-      CreateDictionaryValue("{'prop': {'name1': 1, 'name3': 2}}")));
+      base::Time::Now(), ValueMap{
+                             {"prop.name1", test::make_int_prop_value(1)},
+                             {"prop.name2", test::make_int_prop_value(2)},
+                         }));
 
   auto callback = [](StateChangeQueueInterface::UpdateID id) {
     FAIL() << "This should not be called";
diff --git a/src/states/state_manager.cc b/src/states/state_manager.cc
index b170b86..fe6e669 100644
--- a/src/states/state_manager.cc
+++ b/src/states/state_manager.cc
@@ -27,17 +27,14 @@
   callback.Run();  // Force to read current state.
 }
 
-const base::DictionaryValue& StateManager::GetState() const {
-  if (!cached_dict_valid_) {
-    cached_dict_.Clear();
-    for (const auto& pair : packages_) {
-      cached_dict_.SetWithoutPathExpansion(
-          pair.first, pair.second->GetValuesAsJson().DeepCopy());
-    }
-    cached_dict_valid_ = true;
+std::unique_ptr<base::DictionaryValue> StateManager::GetState() const {
+  std::unique_ptr<base::DictionaryValue> dict{new base::DictionaryValue};
+  for (const auto& pair : packages_) {
+    auto pkg_value = pair.second->GetValuesAsJson();
+    CHECK(pkg_value);
+    dict->SetWithoutPathExpansion(pair.first, pkg_value.release());
   }
-
-  return cached_dict_;
+  return dict;
 }
 
 bool StateManager::SetProperty(const std::string& name,
@@ -49,7 +46,8 @@
   return result;
 }
 
-const base::Value* StateManager::GetProperty(const std::string& name) const {
+std::unique_ptr<base::Value> StateManager::GetProperty(
+    const std::string& name) const {
   auto parts = SplitAtFirst(name, ".", true);
   const std::string& package_name = parts.first;
   const std::string& property_name = parts.second;
@@ -95,11 +93,8 @@
   if (!package->SetPropertyValue(property_name, value, error))
     return false;
 
-  cached_dict_valid_ = false;
-
-  std::unique_ptr<base::DictionaryValue> prop_set{new base::DictionaryValue};
-  prop_set->Set(full_property_name, value.DeepCopy());
-  state_change_queue_->NotifyPropertiesUpdated(timestamp, std::move(prop_set));
+  ValueMap prop_set{{full_property_name, package->GetProperty(property_name)}};
+  state_change_queue_->NotifyPropertiesUpdated(timestamp, prop_set);
   return true;
 }
 
@@ -222,9 +217,8 @@
   StatePackage* package = FindPackage(package_name);
   if (package == nullptr) {
     std::unique_ptr<StatePackage> new_package{new StatePackage(package_name)};
-    package =
-        packages_.insert(std::make_pair(package_name, std::move(new_package)))
-            .first->second.get();
+    package = packages_.emplace(package_name, std::move(new_package))
+                  .first->second.get();
   }
   return package;
 }
diff --git a/src/states/state_manager.h b/src/states/state_manager.h
index c48a57c..55faf76 100644
--- a/src/states/state_manager.h
+++ b/src/states/state_manager.h
@@ -39,11 +39,11 @@
   bool LoadStateDefinitionFromJson(const std::string& json, ErrorPtr* error);
   bool SetProperties(const base::DictionaryValue& dict, ErrorPtr* error);
   bool SetPropertiesFromJson(const std::string& json, ErrorPtr* error);
-  const base::Value* GetProperty(const std::string& name) const;
+  std::unique_ptr<base::Value> GetProperty(const std::string& name) const;
   bool SetProperty(const std::string& name,
                    const base::Value& value,
                    ErrorPtr* error);
-  const base::DictionaryValue& GetState() const;
+  std::unique_ptr<base::DictionaryValue> GetState() const;
 
   // Returns the recorded state changes since last time this method has been
   // called.
@@ -80,9 +80,6 @@
 
   std::vector<base::Closure> on_changed_;
 
-  mutable base::DictionaryValue cached_dict_;
-  mutable bool cached_dict_valid_ = false;
-
   DISALLOW_COPY_AND_ASSIGN(StateManager);
 };
 
diff --git a/src/states/state_manager_unittest.cc b/src/states/state_manager_unittest.cc
index 6899646..3f854fe 100644
--- a/src/states/state_manager_unittest.cc
+++ b/src/states/state_manager_unittest.cc
@@ -12,9 +12,9 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <weave/provider/test/mock_config_store.h>
-#include <weave/test/unittest_utils.h>
 
 #include "src/commands/schema_constants.h"
+#include "src/commands/unittest_utils.h"
 #include "src/states/error_codes.h"
 #include "src/states/mock_state_change_queue_interface.h"
 
@@ -22,18 +22,17 @@
 
 using testing::_;
 using testing::Return;
-using testing::ReturnRef;
 using test::CreateDictionaryValue;
 
 namespace {
 
 const char kBaseDefinition[] = R"({
   "base": {
-    "manufacturer":{"type":"string"},
-    "serialNumber":{"type":"string"}
+    "manufacturer":"string",
+    "serialNumber":"string"
   },
   "device": {
-    "state_property":{"type":"string"}
+    "state_property":"string"
   }
 })";
 
@@ -52,10 +51,6 @@
   return CreateDictionaryValue(kBaseDefaults);
 }
 
-MATCHER_P(IsState, str, "") {
-  return arg.Equals(CreateDictionaryValue(str).get());
-}
-
 }  // anonymous namespace
 
 class StateManagerTest : public ::testing::Test {
@@ -63,9 +58,9 @@
   void SetUp() override {
     // Initial expectations.
     EXPECT_CALL(mock_state_change_queue_, IsEmpty()).Times(0);
-    EXPECT_CALL(mock_state_change_queue_, MockNotifyPropertiesUpdated(_, _))
+    EXPECT_CALL(mock_state_change_queue_, NotifyPropertiesUpdated(_, _))
         .WillRepeatedly(Return(true));
-    EXPECT_CALL(mock_state_change_queue_, MockGetAndClearRecordedStateChanges())
+    EXPECT_CALL(mock_state_change_queue_, GetAndClearRecordedStateChanges())
         .Times(0);
     mgr_.reset(new StateManager(&mock_state_change_queue_));
 
@@ -107,9 +102,11 @@
       'manufacturer': 'Test Factory',
       'serialNumber': 'Test Model'
     },
-    'device': {}
+    'device': {
+      'state_property': ''
+    }
   })";
-  EXPECT_JSON_EQ(expected, mgr_->GetState());
+  EXPECT_JSON_EQ(expected, *mgr_->GetState());
 }
 
 TEST_F(StateManagerTest, LoadStateDefinition) {
@@ -125,10 +122,14 @@
       'manufacturer': 'Test Factory',
       'serialNumber': 'Test Model'
     },
-    'power': {},
-    'device': {}
+    'power': {
+      'battery_level': 0
+    },
+    'device': {
+      'state_property': ''
+    }
   })";
-  EXPECT_JSON_EQ(expected, mgr_->GetState());
+  EXPECT_JSON_EQ(expected, *mgr_->GetState());
 }
 
 TEST_F(StateManagerTest, Startup) {
@@ -136,15 +137,12 @@
 
   auto state_definition = R"({
     "base": {
-      "firmwareVersion": {"type":"string"},
-      "localDiscoveryEnabled": {"type":"boolean"},
-      "localAnonymousAccessMaxRole": {
-        "type": "string",
-        "enum": ["none", "viewer", "user"]
-      },
-      "localPairingEnabled": {"type":"boolean"}
+      "firmwareVersion": "string",
+      "localDiscoveryEnabled": "boolean",
+      "localAnonymousAccessMaxRole": [ "none", "viewer", "user" ],
+      "localPairingEnabled": "boolean"
     },
-    "power": {"battery_level":{"type":"integer"}}
+    "power": {"battery_level":"integer"}
   })";
   ASSERT_TRUE(manager.LoadStateDefinitionFromJson(state_definition, nullptr));
 
@@ -170,13 +168,15 @@
       'battery_level': 44
     }
   })";
-  EXPECT_JSON_EQ(expected, manager.GetState());
+  EXPECT_JSON_EQ(expected, *manager.GetState());
 }
 
 TEST_F(StateManagerTest, SetPropertyValue) {
-  const std::string state = "{'device': {'state_property': 'Test Value'}}";
+  ValueMap expected_prop_set{
+      {"device.state_property", test::make_string_prop_value("Test Value")},
+  };
   EXPECT_CALL(mock_state_change_queue_,
-              MockNotifyPropertiesUpdated(timestamp_, IsState(state)))
+              NotifyPropertiesUpdated(timestamp_, expected_prop_set))
       .WillOnce(Return(true));
   ASSERT_TRUE(SetPropertyValue("device.state_property",
                                base::StringValue{"Test Value"}, nullptr));
@@ -189,7 +189,7 @@
       'state_property': 'Test Value'
     }
   })";
-  EXPECT_JSON_EQ(expected, mgr_->GetState());
+  EXPECT_JSON_EQ(expected, *mgr_->GetState());
 }
 
 TEST_F(StateManagerTest, SetPropertyValue_Error_NoName) {
@@ -216,36 +216,39 @@
 }
 
 TEST_F(StateManagerTest, SetPropertyValue_Error_UnknownProperty) {
-  ASSERT_TRUE(
-      SetPropertyValue("base.level", base::FundamentalValue{0}, nullptr));
+  ErrorPtr error;
+  ASSERT_FALSE(
+      SetPropertyValue("base.level", base::FundamentalValue{0}, &error));
+  EXPECT_EQ(errors::state::kDomain, error->GetDomain());
+  EXPECT_EQ(errors::state::kPropertyNotDefined, error->GetCode());
 }
 
 TEST_F(StateManagerTest, GetAndClearRecordedStateChanges) {
-  EXPECT_CALL(mock_state_change_queue_,
-              MockNotifyPropertiesUpdated(timestamp_, _))
+  EXPECT_CALL(mock_state_change_queue_, NotifyPropertiesUpdated(timestamp_, _))
       .WillOnce(Return(true));
   ASSERT_TRUE(SetPropertyValue("device.state_property",
                                base::StringValue{"Test Value"}, nullptr));
-  std::vector<StateChange> expected_state;
-  const std::string expected_val =
-      "{'device': {'state_property': 'Test Value'}}";
-  expected_state.emplace_back(
-      timestamp_,
-      CreateDictionaryValue(expected_val));
-  EXPECT_CALL(mock_state_change_queue_, MockGetAndClearRecordedStateChanges())
-      .WillOnce(ReturnRef(expected_state));
+  std::vector<StateChange> expected_val;
+  expected_val.emplace_back(
+      timestamp_, ValueMap{{"device.state_property",
+                            test::make_string_prop_value("Test Value")}});
+  EXPECT_CALL(mock_state_change_queue_, GetAndClearRecordedStateChanges())
+      .WillOnce(Return(expected_val));
   EXPECT_CALL(mock_state_change_queue_, GetLastStateChangeId())
       .WillOnce(Return(0));
   auto changes = mgr_->GetAndClearRecordedStateChanges();
-  ASSERT_EQ(1u, changes.second.size());
-  EXPECT_EQ(timestamp_, changes.second.back().timestamp);
-  EXPECT_JSON_EQ(expected_val, *changes.second.back().changed_properties);
+  ASSERT_EQ(1, changes.second.size());
+  EXPECT_EQ(expected_val.back().timestamp, changes.second.back().timestamp);
+  EXPECT_EQ(expected_val.back().changed_properties,
+            changes.second.back().changed_properties);
 }
 
 TEST_F(StateManagerTest, SetProperties) {
-  const std::string state = "{'base': {'manufacturer': 'No Name'}}";
+  ValueMap expected_prop_set{
+      {"base.manufacturer", test::make_string_prop_value("No Name")},
+  };
   EXPECT_CALL(mock_state_change_queue_,
-              MockNotifyPropertiesUpdated(_, IsState(state)))
+              NotifyPropertiesUpdated(_, expected_prop_set))
       .WillOnce(Return(true));
 
   EXPECT_CALL(*this, OnStateChanged()).Times(1);
@@ -257,14 +260,16 @@
       'manufacturer': 'No Name',
       'serialNumber': 'Test Model'
     },
-    'device': {}
+    'device': {
+      'state_property': ''
+    }
   })";
-  EXPECT_JSON_EQ(expected, mgr_->GetState());
+  EXPECT_JSON_EQ(expected, *mgr_->GetState());
 }
 
 TEST_F(StateManagerTest, GetProperty) {
   EXPECT_JSON_EQ("'Test Model'", *mgr_->GetProperty("base.serialNumber"));
-  EXPECT_EQ(nullptr, mgr_->GetProperty("device.state_property"));
+  EXPECT_JSON_EQ("''", *mgr_->GetProperty("device.state_property"));
   EXPECT_EQ(nullptr, mgr_->GetProperty("device.unknown"));
   EXPECT_EQ(nullptr, mgr_->GetProperty("unknown.state_property"));
 }
diff --git a/src/states/state_package.cc b/src/states/state_package.cc
index b0ea199..0b8e219 100644
--- a/src/states/state_package.cc
+++ b/src/states/state_package.cc
@@ -7,6 +7,9 @@
 #include <base/logging.h>
 #include <base/values.h>
 
+#include "src/commands/prop_types.h"
+#include "src/commands/prop_values.h"
+#include "src/commands/schema_utils.h"
 #include "src/states/error_codes.h"
 
 namespace weave {
@@ -15,18 +18,28 @@
 
 bool StatePackage::AddSchemaFromJson(const base::DictionaryValue* json,
                                      ErrorPtr* error) {
+  ObjectSchema schema;
+  if (!schema.FromJson(json, nullptr, error))
+    return false;
+
   // Scan first to make sure we have no property redefinitions.
-  for (base::DictionaryValue::Iterator it(*json); !it.IsAtEnd(); it.Advance()) {
-    if (types_.HasKey(it.key())) {
+  for (const auto& pair : schema.GetProps()) {
+    if (types_.GetProp(pair.first)) {
       Error::AddToPrintf(error, FROM_HERE, errors::state::kDomain,
                          errors::state::kPropertyRedefinition,
                          "State property '%s.%s' is already defined",
-                         name_.c_str(), it.key().c_str());
+                         name_.c_str(), pair.first.c_str());
       return false;
     }
   }
 
-  types_.MergeDictionary(json);
+  // Now move all the properties to |types_| object.
+  for (const auto& pair : schema.GetProps()) {
+    types_.AddProp(pair.first, pair.second->Clone());
+    // Create default value for this state property.
+    values_.emplace(pair.first, pair.second->CreateDefaultValue());
+  }
+
   return true;
 }
 
@@ -39,15 +52,21 @@
   return true;
 }
 
-const base::DictionaryValue& StatePackage::GetValuesAsJson() const {
-  return values_;
+std::unique_ptr<base::DictionaryValue> StatePackage::GetValuesAsJson() const {
+  std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+  for (const auto& pair : values_) {
+    auto value = pair.second->ToJson();
+    CHECK(value);
+    dict->SetWithoutPathExpansion(pair.first, value.release());
+  }
+  return dict;
 }
 
-const base::Value* StatePackage::GetPropertyValue(
+std::unique_ptr<base::Value> StatePackage::GetPropertyValue(
     const std::string& property_name,
     ErrorPtr* error) const {
-  const base::Value* value = nullptr;
-  if (!values_.Get(property_name, &value)) {
+  auto it = values_.find(property_name);
+  if (it == values_.end()) {
     Error::AddToPrintf(error, FROM_HERE, errors::state::kDomain,
                        errors::state::kPropertyNotDefined,
                        "State property '%s.%s' is not defined", name_.c_str(),
@@ -55,13 +74,24 @@
     return nullptr;
   }
 
-  return value;
+  return it->second->ToJson();
 }
 
 bool StatePackage::SetPropertyValue(const std::string& property_name,
                                     const base::Value& value,
                                     ErrorPtr* error) {
-  values_.Set(property_name, value.DeepCopy());
+  auto it = values_.find(property_name);
+  if (it == values_.end()) {
+    Error::AddToPrintf(error, FROM_HERE, errors::state::kDomain,
+                       errors::state::kPropertyNotDefined,
+                       "State property '%s.%s' is not defined", name_.c_str(),
+                       property_name.c_str());
+    return false;
+  }
+  auto new_value = it->second->GetPropType()->CreatePropValue(value, error);
+  if (!new_value)
+    return false;
+  it->second = std::move(new_value);
   return true;
 }
 
diff --git a/src/states/state_package.h b/src/states/state_package.h
index 4092948..e8e3964 100644
--- a/src/states/state_package.h
+++ b/src/states/state_package.h
@@ -10,9 +10,15 @@
 #include <string>
 
 #include <base/macros.h>
-#include <base/values.h>
 #include <weave/error.h>
 
+#include "src/commands/object_schema.h"
+#include "src/commands/prop_values.h"
+
+namespace base {
+class DictionaryValue;
+}  // namespace base
+
 namespace weave {
 
 // A package is a set of related state properties. GCD specification defines
@@ -43,25 +49,33 @@
   //      "message": "Printer low on cyan ink"
   //    }
   //  }
-  const base::DictionaryValue& GetValuesAsJson() const;
+  std::unique_ptr<base::DictionaryValue> GetValuesAsJson() const;
 
   // Gets the value for a specific state property. |property_name| must not
   // include the package name as part of the property name.
-  const base::Value* GetPropertyValue(const std::string& property_name,
-                                      ErrorPtr* error) const;
+  std::unique_ptr<base::Value> GetPropertyValue(
+      const std::string& property_name,
+      ErrorPtr* error) const;
   // Sets the value for a specific state property. |property_name| must not
   // include the package name as part of the property name.
   bool SetPropertyValue(const std::string& property_name,
                         const base::Value& value,
                         ErrorPtr* error);
 
+  std::shared_ptr<const PropValue> GetProperty(
+      const std::string& property_name) const {
+    auto it = values_.find(property_name);
+    return it != values_.end() ? it->second
+                               : std::shared_ptr<const PropValue>{};
+  }
+
   // Returns the name of the this package.
   const std::string& GetName() const { return name_; }
 
  private:
   std::string name_;
-  base::DictionaryValue types_;
-  base::DictionaryValue values_;
+  ObjectSchema types_;
+  ValueMap values_;
 
   friend class StatePackageTestHelper;
   DISALLOW_COPY_AND_ASSIGN(StatePackage);
diff --git a/src/states/state_package_unittest.cc b/src/states/state_package_unittest.cc
index d09625a..f958bf5 100644
--- a/src/states/state_package_unittest.cc
+++ b/src/states/state_package_unittest.cc
@@ -9,9 +9,9 @@
 
 #include <base/values.h>
 #include <gtest/gtest.h>
-#include <weave/test/unittest_utils.h>
 
 #include "src/commands/schema_constants.h"
+#include "src/commands/unittest_utils.h"
 #include "src/states/error_codes.h"
 
 namespace weave {
@@ -21,11 +21,11 @@
 class StatePackageTestHelper {
  public:
   // Returns the state property definitions (types/constraints/etc).
-  static const base::DictionaryValue& GetTypes(const StatePackage& package) {
+  static const ObjectSchema& GetTypes(const StatePackage& package) {
     return package.types_;
   }
   // Returns the all state property values in this package.
-  static const base::DictionaryValue& GetValues(const StatePackage& package) {
+  static const ValueMap& GetValues(const StatePackage& package) {
     return package.values_;
   }
 };
@@ -33,30 +33,15 @@
 namespace {
 std::unique_ptr<base::DictionaryValue> GetTestSchema() {
   return CreateDictionaryValue(R"({
-    'color': {
-      'type': 'string'
+    'light': 'boolean',
+    'color': 'string',
+    'direction':{
+      'properties':{
+        'azimuth': {'type': 'number', 'isRequired': true},
+        'altitude': {'maximum': 90.0}
+      }
     },
-    'direction': {
-      'additionalProperties': false,
-      'properties': {
-        'altitude': {
-          'maximum': 90.0,
-          'type': 'number'
-        },
-        'azimuth': {
-          'type': 'number'
-        }
-      },
-      'type': 'object',
-      'required': [ 'azimuth' ]
-    },
-    'iso': {
-      'enum': [50, 100, 200, 400],
-      'type': 'integer'
-    },
-    'light': {
-      'type': 'boolean'
-    }
+    'iso': [50, 100, 200, 400]
   })");
 }
 
@@ -69,11 +54,11 @@
   })");
 }
 
-inline const base::DictionaryValue& GetTypes(const StatePackage& package) {
+inline const ObjectSchema& GetTypes(const StatePackage& package) {
   return StatePackageTestHelper::GetTypes(package);
 }
 // Returns the all state property values in this package.
-inline const base::DictionaryValue& GetValues(const StatePackage& package) {
+inline const ValueMap& GetValues(const StatePackage& package) {
   return StatePackageTestHelper::GetValues(package);
 }
 
@@ -93,15 +78,15 @@
 TEST(StatePackage, Empty) {
   StatePackage package("test");
   EXPECT_EQ("test", package.GetName());
-  EXPECT_TRUE(GetTypes(package).empty());
+  EXPECT_TRUE(GetTypes(package).GetProps().empty());
   EXPECT_TRUE(GetValues(package).empty());
 }
 
 TEST(StatePackage, AddSchemaFromJson_OnEmpty) {
   StatePackage package("test");
   ASSERT_TRUE(package.AddSchemaFromJson(GetTestSchema().get(), nullptr));
-  EXPECT_EQ(4u, GetTypes(package).size());
-  EXPECT_EQ(0u, GetValues(package).size());
+  EXPECT_EQ(4, GetTypes(package).GetProps().size());
+  EXPECT_EQ(4, GetValues(package).size());
 
   auto expected = R"({
     'color': {
@@ -129,16 +114,22 @@
       'type': 'boolean'
     }
   })";
-  EXPECT_JSON_EQ(expected, GetTypes(package));
+  EXPECT_JSON_EQ(expected, *GetTypes(package).ToJson(true, false));
 
-  EXPECT_JSON_EQ("{}", package.GetValuesAsJson());
+  expected = R"({
+    'color': '',
+    'direction': {},
+    'iso': 0,
+    'light': false
+  })";
+  EXPECT_JSON_EQ(expected, *package.GetValuesAsJson());
 }
 
 TEST(StatePackage, AddValuesFromJson_OnEmpty) {
   StatePackage package("test");
   ASSERT_TRUE(package.AddSchemaFromJson(GetTestSchema().get(), nullptr));
   ASSERT_TRUE(package.AddValuesFromJson(GetTestValues().get(), nullptr));
-  EXPECT_EQ(4u, GetValues(package).size());
+  EXPECT_EQ(4, GetValues(package).size());
   auto expected = R"({
     'color': 'white',
     'direction': {
@@ -148,17 +139,14 @@
     'iso': 200,
     'light': true
   })";
-  EXPECT_JSON_EQ(expected, package.GetValuesAsJson());
+  EXPECT_JSON_EQ(expected, *package.GetValuesAsJson());
 }
 
 TEST_F(StatePackageTest, AddSchemaFromJson_AddMore) {
-  auto dict = CreateDictionaryValue(R"({'brightness':{
-      'enum': ['low', 'medium', 'high'],
-      'type': 'string'
-    }})");
+  auto dict = CreateDictionaryValue("{'brightness':['low', 'medium', 'high']}");
   ASSERT_TRUE(package_->AddSchemaFromJson(dict.get(), nullptr));
-  EXPECT_EQ(5u, GetTypes(*package_).size());
-  EXPECT_EQ(4u, GetValues(*package_).size());
+  EXPECT_EQ(5, GetTypes(*package_).GetProps().size());
+  EXPECT_EQ(5, GetValues(*package_).size());
   auto expected = R"({
     'brightness': {
       'enum': ['low', 'medium', 'high'],
@@ -189,9 +177,10 @@
       'type': 'boolean'
     }
   })";
-  EXPECT_JSON_EQ(expected, GetTypes(*package_));
+  EXPECT_JSON_EQ(expected, *GetTypes(*package_).ToJson(true, false));
 
   expected = R"({
+    'brightness': '',
     'color': 'white',
     'direction': {
       'altitude': 89.9,
@@ -200,18 +189,15 @@
     'iso': 200,
     'light': true
   })";
-  EXPECT_JSON_EQ(expected, package_->GetValuesAsJson());
+  EXPECT_JSON_EQ(expected, *package_->GetValuesAsJson());
 }
 
 TEST_F(StatePackageTest, AddValuesFromJson_AddMore) {
-  auto dict = CreateDictionaryValue(R"({'brightness':{
-      'enum': ['low', 'medium', 'high'],
-      'type': 'string'
-    }})");
+  auto dict = CreateDictionaryValue("{'brightness':['low', 'medium', 'high']}");
   ASSERT_TRUE(package_->AddSchemaFromJson(dict.get(), nullptr));
   dict = CreateDictionaryValue("{'brightness':'medium'}");
   ASSERT_TRUE(package_->AddValuesFromJson(dict.get(), nullptr));
-  EXPECT_EQ(5u, GetValues(*package_).size());
+  EXPECT_EQ(5, GetValues(*package_).size());
   auto expected = R"({
     'brightness': 'medium',
     'color': 'white',
@@ -222,12 +208,11 @@
     'iso': 200,
     'light': true
   })";
-  EXPECT_JSON_EQ(expected, package_->GetValuesAsJson());
+  EXPECT_JSON_EQ(expected, *package_->GetValuesAsJson());
 }
 
 TEST_F(StatePackageTest, AddSchemaFromJson_Error_Redefined) {
-  auto dict = CreateDictionaryValue(R"({'color':
-    {'type':'string', 'enum':['white', 'blue', 'red']}})");
+  auto dict = CreateDictionaryValue("{'color':['white', 'blue', 'red']}");
   ErrorPtr error;
   EXPECT_FALSE(package_->AddSchemaFromJson(dict.get(), &error));
   EXPECT_EQ(errors::state::kDomain, error->GetDomain());
@@ -236,7 +221,10 @@
 
 TEST_F(StatePackageTest, AddValuesFromJson_Error_Undefined) {
   auto dict = CreateDictionaryValue("{'brightness':'medium'}");
-  EXPECT_TRUE(package_->AddValuesFromJson(dict.get(), nullptr));
+  ErrorPtr error;
+  EXPECT_FALSE(package_->AddValuesFromJson(dict.get(), &error));
+  EXPECT_EQ(errors::state::kDomain, error->GetDomain());
+  EXPECT_EQ(errors::state::kPropertyNotDefined, error->GetCode());
 }
 
 TEST_F(StatePackageTest, GetPropertyValue) {
@@ -283,7 +271,88 @@
     'iso': 200,
     'light': true
   })";
-  EXPECT_JSON_EQ(expected, package_->GetValuesAsJson());
+  EXPECT_JSON_EQ(expected, *package_->GetValuesAsJson());
+}
+
+TEST_F(StatePackageTest, SetPropertyValue_Error_TypeMismatch) {
+  ErrorPtr error;
+  ASSERT_FALSE(
+      package_->SetPropertyValue("color", base::FundamentalValue{12}, &error));
+  EXPECT_EQ(errors::commands::kDomain, error->GetDomain());
+  EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+  error.reset();
+
+  ASSERT_FALSE(
+      package_->SetPropertyValue("iso", base::FundamentalValue{false}, &error));
+  EXPECT_EQ(errors::commands::kDomain, error->GetDomain());
+  EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+}
+
+TEST_F(StatePackageTest, SetPropertyValue_Error_OutOfRange) {
+  ErrorPtr error;
+  ASSERT_FALSE(
+      package_->SetPropertyValue("iso", base::FundamentalValue{150}, &error));
+  EXPECT_EQ(errors::commands::kDomain, error->GetDomain());
+  EXPECT_EQ(errors::commands::kOutOfRange, error->GetCode());
+}
+
+TEST_F(StatePackageTest, SetPropertyValue_Error_Object_TypeMismatch) {
+  ErrorPtr error;
+  ASSERT_FALSE(package_->SetPropertyValue(
+      "direction",
+      *CreateDictionaryValue("{'altitude': 45.0, 'azimuth': '15'}"), &error));
+  EXPECT_EQ(errors::commands::kDomain, error->GetDomain());
+  EXPECT_EQ(errors::commands::kInvalidPropValue, error->GetCode());
+  const Error* inner = error->GetInnerError();
+  EXPECT_EQ(errors::commands::kDomain, inner->GetDomain());
+  EXPECT_EQ(errors::commands::kTypeMismatch, inner->GetCode());
+}
+
+TEST_F(StatePackageTest, SetPropertyValue_Error_Object_OutOfRange) {
+  ErrorPtr error;
+  ASSERT_FALSE(package_->SetPropertyValue(
+      "direction",
+      *CreateDictionaryValue("{'altitude': 100.0, 'azimuth': 290.0}"), &error));
+  EXPECT_EQ(errors::commands::kDomain, error->GetDomain());
+  EXPECT_EQ(errors::commands::kInvalidPropValue, error->GetCode());
+  const Error* inner = error->GetInnerError();
+  EXPECT_EQ(errors::commands::kDomain, inner->GetDomain());
+  EXPECT_EQ(errors::commands::kOutOfRange, inner->GetCode());
+}
+
+TEST_F(StatePackageTest, SetPropertyValue_Error_Object_UnknownProperty) {
+  ErrorPtr error;
+  ASSERT_FALSE(package_->SetPropertyValue(
+      "direction", *CreateDictionaryValue(
+                       "{'altitude': 10.0, 'azimuth': 20.0, 'spin': 30.0}"),
+      &error));
+  EXPECT_EQ(errors::commands::kDomain, error->GetDomain());
+  EXPECT_EQ(errors::commands::kUnknownProperty, error->GetCode());
+}
+
+TEST_F(StatePackageTest, SetPropertyValue_Object_OptionalProperty) {
+  EXPECT_JSON_EQ("{'altitude': 89.9, 'azimuth': 57.2957795}",
+                 *package_->GetProperty("direction")->ToJson());
+  ASSERT_TRUE(package_->SetPropertyValue(
+      "direction", *CreateDictionaryValue("{'azimuth': 10.0}"), nullptr));
+  EXPECT_JSON_EQ("{'azimuth': 10.0}",
+                 *package_->GetProperty("direction")->ToJson());
+}
+
+TEST_F(StatePackageTest, SetPropertyValue_Error_Object_MissingProperty) {
+  ErrorPtr error;
+  ASSERT_FALSE(package_->SetPropertyValue(
+      "direction", *CreateDictionaryValue("{'altitude': 10.0}"), &error));
+  EXPECT_EQ(errors::commands::kDomain, error->GetDomain());
+  EXPECT_EQ(errors::commands::kPropertyMissing, error->GetCode());
+}
+
+TEST_F(StatePackageTest, SetPropertyValue_Error_Unknown) {
+  ErrorPtr error;
+  ASSERT_FALSE(package_->SetPropertyValue("volume", base::FundamentalValue{100},
+                                          &error));
+  EXPECT_EQ(errors::state::kDomain, error->GetDomain());
+  EXPECT_EQ(errors::state::kPropertyNotDefined, error->GetCode());
 }
 
 }  // namespace weave
diff --git a/src/string_utils_unittest.cc b/src/string_utils_unittest.cc
index 3966921..c26d7d8 100644
--- a/src/string_utils_unittest.cc
+++ b/src/string_utils_unittest.cc
@@ -17,21 +17,21 @@
   std::vector<std::string> parts;
 
   parts = Split("", ",", false, false);
-  EXPECT_EQ(0u, parts.size());
+  EXPECT_EQ(0, parts.size());
 
   parts = Split("abc", ",", false, false);
-  EXPECT_EQ(1u, parts.size());
+  EXPECT_EQ(1, parts.size());
   EXPECT_EQ("abc", parts[0]);
 
   parts = Split(",a,bc , d,  ,e, ", ",", true, true);
-  EXPECT_EQ(4u, parts.size());
+  EXPECT_EQ(4, parts.size());
   EXPECT_EQ("a", parts[0]);
   EXPECT_EQ("bc", parts[1]);
   EXPECT_EQ("d", parts[2]);
   EXPECT_EQ("e", parts[3]);
 
   parts = Split(",a,bc , d,  ,e, ", ",", false, true);
-  EXPECT_EQ(6u, parts.size());
+  EXPECT_EQ(6, parts.size());
   EXPECT_EQ("a", parts[0]);
   EXPECT_EQ("bc ", parts[1]);
   EXPECT_EQ(" d", parts[2]);
@@ -40,7 +40,7 @@
   EXPECT_EQ(" ", parts[5]);
 
   parts = Split(",a,bc , d,  ,e, ", ",", true, false);
-  EXPECT_EQ(7u, parts.size());
+  EXPECT_EQ(7, parts.size());
   EXPECT_EQ("", parts[0]);
   EXPECT_EQ("a", parts[1]);
   EXPECT_EQ("bc", parts[2]);
@@ -50,7 +50,7 @@
   EXPECT_EQ("", parts[6]);
 
   parts = Split(",a,bc , d,  ,e, ", ",", false, false);
-  EXPECT_EQ(7u, parts.size());
+  EXPECT_EQ(7, parts.size());
   EXPECT_EQ("", parts[0]);
   EXPECT_EQ("a", parts[1]);
   EXPECT_EQ("bc ", parts[2]);
@@ -60,12 +60,12 @@
   EXPECT_EQ(" ", parts[6]);
 
   parts = Split("abc:=xyz", ":=", false, false);
-  EXPECT_EQ(2u, parts.size());
+  EXPECT_EQ(2, parts.size());
   EXPECT_EQ("abc", parts[0]);
   EXPECT_EQ("xyz", parts[1]);
 
   parts = Split("abc", "", false, false);
-  EXPECT_EQ(3u, parts.size());
+  EXPECT_EQ(3, parts.size());
   EXPECT_EQ("a", parts[0]);
   EXPECT_EQ("b", parts[1]);
   EXPECT_EQ("c", parts[2]);
diff --git a/src/test/mock_command.cc b/src/test/mock_command.cc
new file mode 100644
index 0000000..a8564c4
--- /dev/null
+++ b/src/test/mock_command.cc
@@ -0,0 +1,30 @@
+// Copyright 2015 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <weave/test/mock_command.h>
+
+#include <memory>
+#include <string>
+
+#include <base/values.h>
+
+#include "src/commands/unittest_utils.h"
+
+namespace weave {
+namespace test {
+
+std::unique_ptr<base::DictionaryValue> MockCommand::GetParameters() const {
+  return CreateDictionaryValue(MockGetParameters());
+}
+
+std::unique_ptr<base::DictionaryValue> MockCommand::GetProgress() const {
+  return CreateDictionaryValue(MockGetProgress());
+}
+
+std::unique_ptr<base::DictionaryValue> MockCommand::GetResults() const {
+  return CreateDictionaryValue(MockGetResults());
+}
+
+}  // namespace test
+}  // namespace weave
diff --git a/src/test/unittest_utils.cc b/src/test/unittest_utils.cc
index df8ccc0..b9858e4 100644
--- a/src/test/unittest_utils.cc
+++ b/src/test/unittest_utils.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <weave/test/unittest_utils.h>
+#include "src/commands/unittest_utils.h"
 
 #include <base/json/json_reader.h>
 #include <base/json/json_writer.h>
diff --git a/src/weave_unittest.cc b/src/weave_unittest.cc
index 2e41852..c2f1e8f 100644
--- a/src/weave_unittest.cc
+++ b/src/weave_unittest.cc
@@ -72,7 +72,7 @@
     "base": {
       "reboot": {
         "minimalRole": "user",
-        "parameters": {"delay": {"type": "integer"}},
+        "parameters": {"delay": "integer"},
         "results": {}
       },
       "shutdown": {
diff --git a/third_party/chromium/base/build/build_config.h b/third_party/chromium/base/build/build_config.h
index 347a636..904965a 100644
--- a/third_party/chromium/base/build/build_config.h
+++ b/third_party/chromium/base/build/build_config.h
@@ -147,16 +147,6 @@
 #define ARCH_CPU_32_BITS 1
 #define ARCH_CPU_LITTLE_ENDIAN 1
 #endif
-#elif defined(__mips__)
-#if defined(__LP64__)
-#define ARCH_CPU_MIPS64_FAMILY 1
-#define ARCH_CPU_64_BITS 1
-#define ARCH_CPU_BIG_ENDIAN 1
-#else
-#define ARCH_CPU_MIPS_FAMILY 1
-#define ARCH_CPU_32_BITS 1
-#define ARCH_CPU_BIG_ENDIAN 1
-#endif
 #else
 #error Please add support for your architecture in build/build_config.h
 #endif
diff --git a/third_party/chromium/base/time/time_posix.cc b/third_party/chromium/base/time/time_posix.cc
index 2825ce5..b625af6 100644
--- a/third_party/chromium/base/time/time_posix.cc
+++ b/third_party/chromium/base/time/time_posix.cc
@@ -117,7 +117,7 @@
 //   => Thu Jan 01 00:00:00 UTC 1970
 //   irb(main):011:0> Time.at(-11644473600).getutc()
 //   => Mon Jan 01 00:00:00 UTC 1601
-static const int64 kWindowsEpochDeltaSeconds = 11644473600ll;
+static const int64 kWindowsEpochDeltaSeconds = INT64_C(11644473600);
 
 // static
 const int64 Time::kWindowsEpochDeltaMicroseconds =
diff --git a/third_party/chromium/base/time/time_unittest.cc b/third_party/chromium/base/time/time_unittest.cc
index 508244a..43373e7 100644
--- a/third_party/chromium/base/time/time_unittest.cc
+++ b/third_party/chromium/base/time/time_unittest.cc
@@ -584,7 +584,7 @@
   exploded.millisecond = 0;
   Time t = Time::FromUTCExploded(exploded);
   // Unix 1970 epoch.
-  EXPECT_EQ(11644473600000000ll, t.ToInternalValue());
+  EXPECT_EQ(INT64_C(11644473600000000), t.ToInternalValue());
 
   // We can't test 1601 epoch, since the system time functions on Linux
   // only compute years starting from 1900.
diff --git a/third_party/modp_b64/modp_b64.cc b/third_party/modp_b64/modp_b64.cc
index 8db2ed1..fdb8a40 100644
--- a/third_party/modp_b64/modp_b64.cc
+++ b/third_party/modp_b64/modp_b64.cc
@@ -45,12 +45,16 @@
 /* public header */
 #include "modp_b64.h"
 
-#include <base/build/build_config.h>
+/*
+ * If you are ripping this out of the library, comment out the next
+ * line and uncomment the next lines as approrpiate
+ */
+//#include "config.h"
 
-#undef WORDS_BIGENDIAN
-#if !defined(ARCH_CPU_LITTLE_ENDIAN)
-#define WORDS_BIGENDIAN 1
-#endif
+/* if on motoral, sun, ibm; uncomment this */
+/* #define WORDS_BIGENDIAN 1 */
+/* else for Intel, Amd; uncomment this */
+/* #undef WORDS_BIGENDIAN */
 
 #include "modp_b64_data.h"
 
@@ -114,7 +118,7 @@
 }
 
 #ifdef WORDS_BIGENDIAN   /* BIG ENDIAN -- SUN / IBM / MOTOROLA */
-size_t modp_b64_decode(char* dest, const char* src, size_t len)
+int modp_b64_decode(char* dest, const char* src, int len)
 {
     if (len == 0) return 0;