Add JSON to CLI commands

- Drivers
- Groups

Change-Id: Ib47dc75d9db839329e6cf8fc4439150848f604f5
diff --git a/cli/src/main/java/org/onosproject/cli/net/DriversListCommand.java b/cli/src/main/java/org/onosproject/cli/net/DriversListCommand.java
index 2450390..05d9e95 100644
--- a/cli/src/main/java/org/onosproject/cli/net/DriversListCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/DriversListCommand.java
@@ -15,12 +15,16 @@
  */
 package org.onosproject.cli.net;
 
+import java.util.Set;
+
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
 import org.onosproject.cli.AbstractShellCommand;
 import org.onosproject.net.driver.Driver;
 import org.onosproject.net.driver.DriverAdminService;
 
+import com.fasterxml.jackson.databind.node.ArrayNode;
+
 /**
  * Lists device drivers.
  */
@@ -43,17 +47,35 @@
         if (driverName != null) {
             printDriver(service.getDriver(driverName));
         } else {
-            service.getDrivers().forEach(this::printDriver);
+            if (outputJson()) {
+                json(service.getDrivers());
+            } else {
+                service.getDrivers().forEach(this::printDriver);
+            }
         }
     }
 
+    private void json(Driver driver) {
+        print("%s", jsonForEntity(driver, Driver.class));
+    }
+
+    private void json(Set<Driver> drivers) {
+        ArrayNode result = mapper().createArrayNode();
+        drivers.forEach(driver -> result.add(jsonForEntity(driver, Driver.class)));
+        print("%s", result.toString());
+    }
+
     private void printDriver(Driver driver) {
-        Driver parent = driver.parent();
-        print(FMT, driver.name(), parent != null ? parent.name() : "none",
-              driver.manufacturer(), driver.hwVersion(), driver.swVersion());
-        driver.behaviours().forEach(b -> print(FMT_B, b.getCanonicalName(),
-                                               driver.implementation(b).getCanonicalName()));
-        driver.properties().forEach((k, v) -> print(FMT_P, k, v));
+        if (outputJson()) {
+            json(driver);
+        } else {
+            Driver parent = driver.parent();
+            print(FMT, driver.name(), parent != null ? parent.name() : "none",
+                    driver.manufacturer(), driver.hwVersion(), driver.swVersion());
+            driver.behaviours().forEach(b -> print(FMT_B, b.getCanonicalName(),
+                    driver.implementation(b).getCanonicalName()));
+            driver.properties().forEach((k, v) -> print(FMT_P, k, v));
+        }
     }
 
 }
diff --git a/cli/src/main/java/org/onosproject/cli/net/GroupsListCommand.java b/cli/src/main/java/org/onosproject/cli/net/GroupsListCommand.java
index 70fa2a7..afbcab8 100644
--- a/cli/src/main/java/org/onosproject/cli/net/GroupsListCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/GroupsListCommand.java
@@ -15,10 +15,9 @@
  */
 package org.onosproject.cli.net;
 
-import static com.google.common.collect.Lists.newArrayList;
-
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.SortedMap;
 import java.util.TreeMap;
 
@@ -34,6 +33,11 @@
 import org.onosproject.net.group.GroupBucket;
 import org.onosproject.net.group.GroupService;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+
+import static com.google.common.collect.Lists.newArrayList;
+
 /**
  * Lists all groups in the system.
  */
@@ -54,6 +58,16 @@
             required = false, multiValued = false)
     String state;
 
+    private JsonNode json(Map<Device, List<Group>> sortedGroups) {
+        ArrayNode result = mapper().createArrayNode();
+
+        sortedGroups.forEach((device, groups) ->
+                groups.forEach(group ->
+                        result.add(jsonForEntity(group, Group.class))));
+
+        return result;
+    }
+
     @Override
     protected void execute() {
         DeviceService deviceService = get(DeviceService.class);
@@ -61,7 +75,11 @@
         SortedMap<Device, List<Group>> sortedGroups =
                 getSortedGroups(deviceService, groupService);
 
-        sortedGroups.forEach((device, groups) -> printGroups(device.id(), groups));
+        if (outputJson()) {
+            print("%s", json(sortedGroups));
+        } else {
+            sortedGroups.forEach((device, groups) -> printGroups(device.id(), groups));
+        }
     }
 
     /**
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java b/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
index 4e7bde9..50a35c3 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
@@ -33,11 +33,14 @@
 import org.onosproject.net.Link;
 import org.onosproject.net.Path;
 import org.onosproject.net.Port;
+import org.onosproject.net.driver.Driver;
 import org.onosproject.net.flow.FlowEntry;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.flow.criteria.Criterion;
 import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
 import org.onosproject.net.intent.ConnectivityIntent;
 import org.onosproject.net.intent.Constraint;
 import org.onosproject.net.intent.HostToHostIntent;
@@ -89,6 +92,9 @@
         registerCodec(Topology.class, new TopologyCodec());
         registerCodec(TopologyCluster.class, new TopologyClusterCodec());
         registerCodec(Path.class, new PathCodec());
+        registerCodec(Group.class, new GroupCodec());
+        registerCodec(Driver.class, new DriverCodec());
+        registerCodec(GroupBucket.class, new GroupBucketCodec());
         log.info("Started");
     }
 
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/DriverCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/DriverCodec.java
new file mode 100644
index 0000000..4935d99
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/DriverCodec.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.driver.Driver;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * JSON codec for the Driver class.
+ */
+public final class DriverCodec extends JsonCodec<Driver> {
+    private static final String PARENT = "parent";
+    private static final String NAME = "name";
+    private static final String MANUFACTURER = "manufacturer";
+    private static final String HW_VERSION = "hwVersion";
+    private static final String SW_VERSION = "swVersion";
+    private static final String BEHAVIOURS = "behaviours";
+    private static final String BEHAVIORS_NAME = "name";
+    private static final String BEHAVIORS_IMPLEMENTATION_NAME = "implementationName";
+    private static final String PROPERTIES = "properties";
+
+    @Override
+    public ObjectNode encode(Driver driver, CodecContext context) {
+        checkNotNull(driver, "Driver cannot be null");
+
+        ObjectNode result = context.mapper().createObjectNode()
+                .put(NAME, driver.name())
+                .put(MANUFACTURER, driver.manufacturer())
+                .put(HW_VERSION, driver.hwVersion())
+                .put(SW_VERSION, driver.swVersion());
+
+        if (driver.parent() != null) {
+            result.put(PARENT, driver.parent().name());
+        }
+
+        ArrayNode behaviours = context.mapper().createArrayNode();
+        driver.behaviours().forEach(behaviour -> {
+            ObjectNode entry = context.mapper().createObjectNode()
+                    .put(BEHAVIORS_NAME, behaviour.getCanonicalName())
+                    .put(BEHAVIORS_IMPLEMENTATION_NAME,
+                            driver.implementation(behaviour).getCanonicalName());
+
+            behaviours.add(entry);
+        });
+        result.set(BEHAVIOURS, behaviours);
+
+        ArrayNode properties = context.mapper().createArrayNode();
+        driver.properties().forEach((name, value) -> {
+            ObjectNode entry = context.mapper().createObjectNode()
+                    .put("name", name)
+                    .put("value", value);
+
+            properties.add(entry);
+        });
+        result.set(PROPERTIES, properties);
+
+        return result;
+    }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/GroupBucketCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/GroupBucketCodec.java
new file mode 100644
index 0000000..c710514
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/GroupBucketCodec.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.group.GroupBucket;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Group bucket JSON codec.
+ */
+public class GroupBucketCodec extends JsonCodec<GroupBucket> {
+
+    private static final String TYPE = "type";
+    private static final String TREATMENT = "treatment";
+    private static final String WEIGHT = "weight";
+    private static final String WATCH_PORT = "watchPort";
+    private static final String WATCH_GROUP = "watchGroup";
+    private static final String PACKETS = "packets";
+    private static final String BYTES = "bytes";
+
+    @Override
+    public ObjectNode encode(GroupBucket bucket, CodecContext context) {
+        checkNotNull(bucket, "Driver cannot be null");
+
+        ObjectNode result = context.mapper().createObjectNode()
+                .put(TYPE, bucket.type().toString())
+                .put(WEIGHT, bucket.weight())
+                .put(PACKETS, bucket.packets())
+                .put(BYTES, bucket.bytes());
+
+        if (bucket.watchPort() != null) {
+            result.put(WATCH_PORT, bucket.watchPort().toString());
+        }
+
+        if (bucket.watchGroup() != null) {
+            result.put(WATCH_GROUP, bucket.watchGroup().toString());
+        }
+
+        if (bucket.treatment() != null) {
+            result.set(TREATMENT, context.codec(TrafficTreatment.class).encode(bucket.treatment(), context));
+        }
+
+        return result;
+    }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/GroupCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/GroupCodec.java
new file mode 100644
index 0000000..a2f33ce
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/GroupCodec.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Group JSON codec.
+ */
+public final class GroupCodec extends JsonCodec<Group> {
+    // JSON field names
+    private static final String ID = "id";
+    private static final String STATE = "state";
+    private static final String LIFE = "life";
+    private static final String PACKETS = "packets";
+    private static final String BYTES = "bytes";
+    private static final String REFERENCE_COUNT = "referenceCount";
+    private static final String TYPE = "type";
+    private static final String DEVICE_ID = "deviceId";
+    private static final String APP_ID = "appId";
+    private static final String APP_COOKIE  = "appCookie";
+    private static final String GIVEN_GROUP_ID  = "givenGroupId";
+    private static final String BUCKETS = "buckets";
+
+    @Override
+    public ObjectNode encode(Group group, CodecContext context) {
+        checkNotNull(group, "Group cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put(ID, group.id().toString())
+                .put(STATE, group.state().toString())
+                .put(LIFE, group.life())
+                .put(PACKETS, group.packets())
+                .put(BYTES, group.bytes())
+                .put(REFERENCE_COUNT, group.referenceCount())
+                .put(TYPE, group.type().toString())
+                .put(DEVICE_ID, group.deviceId().toString());
+
+        if (group.appId() != null) {
+            result.put(APP_ID, group.appId().toString());
+        }
+
+        if (group.appCookie() != null) {
+            result.put(APP_COOKIE, group.appCookie().toString());
+        }
+
+        if (group.givenGroupId() != null) {
+            result.put(GIVEN_GROUP_ID, group.givenGroupId());
+        }
+
+        ArrayNode buckets = context.mapper().createArrayNode();
+        group.buckets().buckets().forEach(bucket -> {
+                    ObjectNode bucketJson = context.codec(GroupBucket.class).encode(bucket, context);
+                    buckets.add(bucketJson);
+                });
+        result.set(BUCKETS, buckets);
+        return result;
+    }
+}
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/DriverCodecTest.java b/core/common/src/test/java/org/onosproject/codec/impl/DriverCodecTest.java
new file mode 100644
index 0000000..a1c9517
--- /dev/null
+++ b/core/common/src/test/java/org/onosproject/codec/impl/DriverCodecTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.codec.impl;
+
+
+import java.util.Map;
+
+import org.junit.Test;
+import org.onosproject.net.driver.Behaviour;
+import org.onosproject.net.driver.DefaultDriver;
+import org.onosproject.net.driver.Driver;
+import org.onosproject.net.driver.TestBehaviour;
+import org.onosproject.net.driver.TestBehaviourImpl;
+import org.onosproject.net.driver.TestBehaviourTwo;
+import org.onosproject.net.driver.TestBehaviourTwoImpl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableMap;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.onosproject.codec.impl.DriverJsonMatcher.matchesDriver;
+
+/**
+ * Unit tests for the driver codec.
+ */
+public class DriverCodecTest {
+
+    @Test
+    public void codecTest() {
+        Map<Class<? extends Behaviour>, Class<? extends Behaviour>> behaviours =
+            ImmutableMap.of(TestBehaviour.class,
+                    TestBehaviourImpl.class,
+                    TestBehaviourTwo.class,
+                    TestBehaviourTwoImpl.class);
+        Map<String, String> properties =
+                ImmutableMap.of("key1", "value1", "key2", "value2");
+
+        DefaultDriver parent = new DefaultDriver("parent", null, "Acme",
+                "HW1.2.3", "SW1.2.3",
+                behaviours,
+                properties);
+        DefaultDriver child = new DefaultDriver("child", parent, "Acme",
+                "HW1.2.3.1", "SW1.2.3.1",
+                behaviours,
+                properties);
+
+        MockCodecContext context = new MockCodecContext();
+        ObjectNode driverJson = context.codec(Driver.class).encode(child, context);
+
+        assertThat(driverJson, matchesDriver(child));
+    }
+}
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/DriverJsonMatcher.java b/core/common/src/test/java/org/onosproject/codec/impl/DriverJsonMatcher.java
new file mode 100644
index 0000000..6f0070e
--- /dev/null
+++ b/core/common/src/test/java/org/onosproject/codec/impl/DriverJsonMatcher.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.codec.impl;
+
+import java.util.Map;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.onosproject.net.driver.Driver;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Hamcrest matcher for drivers.
+ */
+public final class DriverJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
+    private final Driver driver;
+
+    private DriverJsonMatcher(Driver driver) {
+        this.driver = driver;
+    }
+
+    @Override
+    public boolean matchesSafely(JsonNode jsonDriver, Description description) {
+        // check id
+        String jsonDriverName = jsonDriver.get("name").asText();
+        String driverName = driver.name();
+        if (!jsonDriverName.equals(driverName)) {
+            description.appendText("name was " + jsonDriverName);
+            return false;
+        }
+
+
+        // check parent
+        String jsonParent = jsonDriver.get("parent").asText();
+        String parent = driver.parent().name();
+        if (!jsonParent.equals(parent)) {
+            description.appendText("parent was " + jsonParent);
+            return false;
+        }
+
+        // check manufacturer
+        String jsonManufacturer = jsonDriver.get("manufacturer").asText();
+        String manufacturer = driver.manufacturer();
+        if (!jsonManufacturer.equals(manufacturer)) {
+            description.appendText("manufacturer was " + jsonManufacturer);
+            return false;
+        }
+
+        // check HW version
+        String jsonHWVersion = jsonDriver.get("hwVersion").asText();
+        String hwVersion = driver.hwVersion();
+        if (!jsonHWVersion.equals(hwVersion)) {
+            description.appendText("HW version was " + jsonHWVersion);
+            return false;
+        }
+
+        // check SW version
+        String jsonSWVersion = jsonDriver.get("swVersion").asText();
+        String swVersion = driver.swVersion();
+        if (!jsonSWVersion.equals(swVersion)) {
+            description.appendText("SW version was " + jsonSWVersion);
+            return false;
+        }
+
+        // Check properties
+        JsonNode jsonProperties = jsonDriver.get("properties");
+        if (driver.properties().size() != jsonProperties.size()) {
+            description.appendText("properties map size was was " + jsonProperties.size());
+            return false;
+        }
+        for (Map.Entry<String, String> entry : driver.properties().entrySet()) {
+            boolean propertyFound = false;
+            for (int propertyIndex = 0; propertyIndex < jsonProperties.size(); propertyIndex++) {
+                String jsonName = jsonProperties.get(propertyIndex).get("name").asText();
+                String jsonValue = jsonProperties.get(propertyIndex).get("value").asText();
+                if (!jsonName.equals(entry.getKey()) ||
+                        !jsonValue.equals(entry.getValue())) {
+                    propertyFound = true;
+                    break;
+                }
+            }
+            if (!propertyFound) {
+                description.appendText("property not found " + entry.getKey());
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void describeTo(Description description) {
+        description.appendText(driver.toString());
+    }
+
+    /**
+     * Factory to allocate a driver matcher.
+     *
+     * @param driver driver object we are looking for
+     * @return matcher
+     */
+    public static DriverJsonMatcher matchesDriver(Driver driver) {
+        return new DriverJsonMatcher(driver);
+    }
+}
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/GroupBucketJsonMatcher.java b/core/common/src/test/java/org/onosproject/codec/impl/GroupBucketJsonMatcher.java
new file mode 100644
index 0000000..b305621
--- /dev/null
+++ b/core/common/src/test/java/org/onosproject/codec/impl/GroupBucketJsonMatcher.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.codec.impl;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.onosproject.net.group.GroupBucket;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Hamcrest matcher for instructions.
+ */
+public final class GroupBucketJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
+
+    private final GroupBucket bucket;
+
+    private GroupBucketJsonMatcher(GroupBucket bucket) {
+        this.bucket = bucket;
+    }
+
+    /**
+     * Matches the contents of a group bucket.
+     *
+     * @param bucketJson JSON representation of bucket to match
+     * @param description Description object used for recording errors
+     * @return true if contents match, false otherwise
+     */
+    @Override
+    public boolean matchesSafely(JsonNode bucketJson, Description description) {
+
+        // check type
+        final String jsonType = bucketJson.get("type").textValue();
+        if (!bucket.type().name().equals(jsonType)) {
+            description.appendText("type was " + jsonType);
+            return false;
+        }
+
+        final long jsonWeight = bucketJson.get("weight").longValue();
+        if (bucket.weight() != jsonWeight) {
+            description.appendText("weight was " + jsonWeight);
+            return false;
+        }
+
+        final long packetsJson = bucketJson.get("packets").asLong();
+        if (bucket.packets() != packetsJson) {
+            description.appendText("packets was " + packetsJson);
+            return false;
+        }
+
+        final long bytesJson = bucketJson.get("bytes").asLong();
+        if (bucket.bytes() != bytesJson) {
+            description.appendText("bytes was " + packetsJson);
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void describeTo(Description description) {
+        description.appendText(bucket.toString());
+    }
+
+    /**
+     * Factory to allocate an bucket matcher.
+     *
+     * @param bucket bucket object we are looking for
+     * @return matcher
+     */
+    public static GroupBucketJsonMatcher matchesGroupBucket(GroupBucket bucket) {
+        return new GroupBucketJsonMatcher(bucket);
+    }
+}
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/GroupCodecTest.java b/core/common/src/test/java/org/onosproject/codec/impl/GroupCodecTest.java
new file mode 100644
index 0000000..409f8eb
--- /dev/null
+++ b/core/common/src/test/java/org/onosproject/codec/impl/GroupCodecTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.codec.impl;
+
+import org.junit.Test;
+import org.onosproject.core.DefaultGroupId;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.group.DefaultGroup;
+import org.onosproject.net.group.DefaultGroupBucket;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableList;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.onosproject.codec.impl.GroupJsonMatcher.matchesGroup;
+
+/**
+ * Group codec unit tests.
+ */
+
+public class GroupCodecTest {
+
+    @Test
+    public void codecTest() {
+        GroupBucket bucket1 = DefaultGroupBucket
+                .createSelectGroupBucket(DefaultTrafficTreatment.emptyTreatment());
+        GroupBucket bucket2 = DefaultGroupBucket
+                .createIndirectGroupBucket(DefaultTrafficTreatment.emptyTreatment());
+        GroupBuckets buckets = new GroupBuckets(ImmutableList.of(bucket1, bucket2));
+
+
+        DefaultGroup group = new DefaultGroup(
+                new DefaultGroupId(1),
+                NetTestTools.did("d1"),
+                GroupDescription.Type.INDIRECT,
+                buckets);
+
+        MockCodecContext context = new MockCodecContext();
+        GroupCodec codec = new GroupCodec();
+        ObjectNode groupJson = codec.encode(group, context);
+
+        assertThat(groupJson, matchesGroup(group));
+    }
+}
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/GroupJsonMatcher.java b/core/common/src/test/java/org/onosproject/codec/impl/GroupJsonMatcher.java
new file mode 100644
index 0000000..0e62c30
--- /dev/null
+++ b/core/common/src/test/java/org/onosproject/codec/impl/GroupJsonMatcher.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.codec.impl;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Hamcrest matcher for groups.
+ */
+
+public final class GroupJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
+
+    private final Group group;
+
+    private GroupJsonMatcher(Group group) {
+        this.group = group;
+    }
+
+    @Override
+    public boolean matchesSafely(JsonNode jsonGroup, Description description) {
+        // check id
+        String jsonGroupId = jsonGroup.get("id").asText();
+        String groupId = group.id().toString();
+        if (!jsonGroupId.equals(groupId)) {
+            description.appendText("group id was " + jsonGroupId);
+            return false;
+        }
+
+        // check state
+        String jsonState = jsonGroup.get("state").asText();
+        String state = group.state().toString();
+        if (!jsonState.equals(state)) {
+            description.appendText("state was " + jsonState);
+            return false;
+        }
+
+        // check life
+        long jsonLife = jsonGroup.get("life").asLong();
+        long life = group.life();
+        if (life != jsonLife) {
+            description.appendText("life was " + jsonLife);
+            return false;
+        }
+
+        // check bytes
+        long jsonBytes = jsonGroup.get("bytes").asLong();
+        long bytes = group.bytes();
+        if (bytes != jsonBytes) {
+            description.appendText("bytes was " + jsonBytes);
+            return false;
+        }
+
+        // check packets
+        long jsonPackets = jsonGroup.get("packets").asLong();
+        long packets = group.packets();
+        if (packets != jsonPackets) {
+            description.appendText("packets was " + jsonPackets);
+            return false;
+        }
+
+        // check size of bucket array
+        JsonNode jsonBuckets = jsonGroup.get("buckets");
+        if (jsonBuckets.size() != group.buckets().buckets().size()) {
+            description.appendText("buckets size was " + jsonBuckets.size());
+            return false;
+        }
+
+        // Check buckets
+        for (GroupBucket bucket : group.buckets().buckets()) {
+            boolean bucketFound = false;
+            for (int bucketIndex = 0; bucketIndex < jsonBuckets.size(); bucketIndex++) {
+                GroupBucketJsonMatcher bucketMatcher =
+                        GroupBucketJsonMatcher.matchesGroupBucket(bucket);
+                if (bucketMatcher.matches(jsonBuckets.get(bucketIndex))) {
+                    bucketFound = true;
+                    break;
+                }
+            }
+            if (!bucketFound) {
+                description.appendText("bucket not found " + bucket.toString());
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    public void describeTo(Description description) {
+        description.appendText(group.toString());
+    }
+
+    /**
+     * Factory to allocate a group matcher.
+     *
+     * @param group group object we are looking for
+     * @return matcher
+     */
+    public static GroupJsonMatcher matchesGroup(Group group) {
+        return new GroupJsonMatcher(group);
+    }
+}