You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metron.apache.org by ni...@apache.org on 2018/04/23 13:38:22 UTC

metron git commit: METRON-1511 Unable to Serialize Profiler Configuration (nickwallen) closes apache/metron#982

Repository: metron
Updated Branches:
  refs/heads/master a41611b1a -> b5bf9a987


METRON-1511 Unable to Serialize Profiler Configuration (nickwallen) closes apache/metron#982


Project: http://git-wip-us.apache.org/repos/asf/metron/repo
Commit: http://git-wip-us.apache.org/repos/asf/metron/commit/b5bf9a98
Tree: http://git-wip-us.apache.org/repos/asf/metron/tree/b5bf9a98
Diff: http://git-wip-us.apache.org/repos/asf/metron/diff/b5bf9a98

Branch: refs/heads/master
Commit: b5bf9a98725f866a7fee6470a8e763d17cc69ffd
Parents: a41611b
Author: nickwallen <ni...@nickallen.org>
Authored: Mon Apr 23 09:36:06 2018 -0400
Committer: nickallen <ni...@apache.org>
Committed: Mon Apr 23 09:36:06 2018 -0400

----------------------------------------------------------------------
 .../configuration/profiler/ProfileConfig.java   |  57 ++++++++--
 .../profiler/ProfileResultExpressions.java      |   4 +-
 .../profiler/ProfileTriageExpressions.java      |   8 ++
 .../configuration/profiler/ProfilerConfig.java  |  81 ++++++++++++--
 .../profiler/ProfileConfigTest.java             | 102 ++++++++++++++---
 .../profiler/ProfilerConfigTest.java            | 109 +++++++++++++++++--
 6 files changed, 310 insertions(+), 51 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metron/blob/b5bf9a98/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/profiler/ProfileConfig.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/profiler/ProfileConfig.java b/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/profiler/ProfileConfig.java
index f5b46e6..f2272c3 100644
--- a/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/profiler/ProfileConfig.java
+++ b/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/profiler/ProfileConfig.java
@@ -18,12 +18,15 @@
 package org.apache.metron.common.configuration.profiler;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonProcessingException;
 import org.apache.commons.lang.builder.EqualsBuilder;
 import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.metron.common.utils.JSONUtils;
 
+import java.io.IOException;
 import java.io.Serializable;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -264,15 +267,47 @@ public class ProfileConfig implements Serializable {
 
   @Override
   public String toString() {
-    return "ProfileConfig{" +
-            "profile='" + profile + '\'' +
-            ", foreach='" + foreach + '\'' +
-            ", onlyif='" + onlyif + '\'' +
-            ", init=" + init +
-            ", update=" + update +
-            ", groupBy=" + groupBy +
-            ", result=" + result +
-            ", expires=" + expires +
-            '}';
+    return new ToStringBuilder(this)
+            .append("profile", profile)
+            .append("foreach", foreach)
+            .append("onlyif", onlyif)
+            .append("init", init)
+            .append("update", update)
+            .append("groupBy", groupBy)
+            .append("result", result)
+            .append("expires", expires)
+            .toString();
+  }
+
+  /**
+   * Deserialize a {@link ProfileConfig}.
+   *
+   * @param bytes Raw bytes containing a UTF-8 JSON String.
+   * @return The Profile definition.
+   * @throws IOException
+   */
+  public static ProfileConfig fromBytes(byte[] bytes) throws IOException {
+    return JSONUtils.INSTANCE.load(new String(bytes), ProfileConfig.class);
+  }
+
+  /**
+   * Deserialize a {@link ProfileConfig}.
+   *
+   * @param json A String containing JSON.
+   * @return The Profile definition.
+   * @throws IOException
+   */
+  public static ProfileConfig fromJSON(String json) throws IOException {
+    return JSONUtils.INSTANCE.load(json, ProfileConfig.class);
+  }
+
+  /**
+   * Serialize the profile definition to a JSON string.
+   *
+   * @return The Profiler configuration serialized as a JSON string.
+   * @throws JsonProcessingException
+   */
+  public String toJSON() throws JsonProcessingException {
+    return JSONUtils.INSTANCE.toJSON(this, true);
   }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/b5bf9a98/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/profiler/ProfileResultExpressions.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/profiler/ProfileResultExpressions.java b/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/profiler/ProfileResultExpressions.java
index 82af223..5bcec72 100644
--- a/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/profiler/ProfileResultExpressions.java
+++ b/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/profiler/ProfileResultExpressions.java
@@ -18,7 +18,7 @@
 package org.apache.metron.common.configuration.profiler;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonValue;
 
 /**
  * A Stellar expression that is executed to produce a single
@@ -26,7 +26,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
  */
 public class ProfileResultExpressions {
 
-  @JsonIgnore
   private String expression;
 
   @JsonCreator
@@ -34,6 +33,7 @@ public class ProfileResultExpressions {
     this.expression = expression;
   }
 
+  @JsonValue
   public String getExpression() {
     return expression;
   }

http://git-wip-us.apache.org/repos/asf/metron/blob/b5bf9a98/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/profiler/ProfileTriageExpressions.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/profiler/ProfileTriageExpressions.java b/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/profiler/ProfileTriageExpressions.java
index fbe1706..da02cb2 100644
--- a/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/profiler/ProfileTriageExpressions.java
+++ b/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/profiler/ProfileTriageExpressions.java
@@ -17,6 +17,8 @@
  */
 package org.apache.metron.common.configuration.profiler;
 
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 
@@ -61,10 +63,16 @@ public class ProfileTriageExpressions {
     return expressions.get(name);
   }
 
+  @JsonAnyGetter
   public Map<String, String> getExpressions() {
     return expressions;
   }
 
+  @JsonAnySetter
+  public void setExpressions(Map<String, String> expressions) {
+    this.expressions = expressions;
+  }
+
   @Override
   public String toString() {
     return "ProfileTriageExpressions{" +

http://git-wip-us.apache.org/repos/asf/metron/blob/b5bf9a98/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/profiler/ProfilerConfig.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/profiler/ProfilerConfig.java b/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/profiler/ProfilerConfig.java
index 0bdb7e2..e4fa99a 100644
--- a/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/profiler/ProfilerConfig.java
+++ b/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/profiler/ProfilerConfig.java
@@ -17,6 +17,17 @@
  */
 package org.apache.metron.common.configuration.profiler;
 
+import com.fasterxml.jackson.annotation.JsonGetter;
+import com.fasterxml.jackson.annotation.JsonSetter;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.metron.common.utils.JSONUtils;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion;
+
+import java.io.IOException;
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
@@ -25,6 +36,7 @@ import java.util.Optional;
 /**
  * The configuration object for the Profiler, which may contain many Profile definitions.
  */
+@JsonSerialize(include=Inclusion.NON_NULL)
 public class ProfilerConfig implements Serializable {
 
   /**
@@ -59,10 +71,16 @@ public class ProfilerConfig implements Serializable {
     return this;
   }
 
+  @JsonGetter("timestampField")
+  public String getTimestampFieldForJson() {
+    return timestampField.orElse(null);
+  }
+
   public Optional<String> getTimestampField() {
     return timestampField;
   }
 
+  @JsonSetter("timestampField")
   public void setTimestampField(String timestampField) {
     this.timestampField = Optional.of(timestampField);
   }
@@ -78,25 +96,66 @@ public class ProfilerConfig implements Serializable {
 
   @Override
   public String toString() {
-    return "ProfilerConfig{" +
-            "profiles=" + profiles +
-            ", timestampField='" + timestampField + '\'' +
-            '}';
+    return new ToStringBuilder(this)
+            .append("profiles", profiles)
+            .append("timestampField", timestampField)
+            .toString();
   }
 
   @Override
   public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
+    if (this == o) {
+      return true;
+    }
+
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
     ProfilerConfig that = (ProfilerConfig) o;
-    if (profiles != null ? !profiles.equals(that.profiles) : that.profiles != null) return false;
-    return timestampField != null ? timestampField.equals(that.timestampField) : that.timestampField == null;
+    return new EqualsBuilder()
+            .append(profiles, that.profiles)
+            .append(timestampField, that.timestampField)
+            .isEquals();
   }
 
   @Override
   public int hashCode() {
-    int result = profiles != null ? profiles.hashCode() : 0;
-    result = 31 * result + (timestampField != null ? timestampField.hashCode() : 0);
-    return result;
+    return new HashCodeBuilder(17, 37)
+            .append(profiles)
+            .append(timestampField)
+            .toHashCode();
+  }
+
+  /**
+   * Deserialize a {@link ProfilerConfig}.
+   *
+   * @param bytes Raw bytes containing a UTF-8 JSON String.
+   * @return The Profiler configuration.
+   * @throws IOException
+   */
+  public static ProfilerConfig fromBytes(byte[] bytes) throws IOException {
+    return JSONUtils.INSTANCE.load(new String(bytes), ProfilerConfig.class);
+  }
+
+  /**
+   * Deserialize a {@link ProfilerConfig}.
+   *
+   * @param json A String containing JSON.
+   * @return The Profiler configuration.
+   * @throws IOException
+   */
+  public static ProfilerConfig fromJSON(String json) throws IOException {
+    return JSONUtils.INSTANCE.load(json, ProfilerConfig.class);
+  }
+
+  /**
+   * Serialize a {@link ProfilerConfig} to a JSON string.
+   *
+   * @return The Profiler configuration serialized as a JSON string.
+   * @throws JsonProcessingException
+   */
+  public String toJSON() throws JsonProcessingException {
+    return JSONUtils.INSTANCE.toJSON(this, true);
   }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/b5bf9a98/metron-platform/metron-common/src/test/java/org/apache/metron/common/configuration/profiler/ProfileConfigTest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-common/src/test/java/org/apache/metron/common/configuration/profiler/ProfileConfigTest.java b/metron-platform/metron-common/src/test/java/org/apache/metron/common/configuration/profiler/ProfileConfigTest.java
index e178ee0..87dbbc4 100644
--- a/metron-platform/metron-common/src/test/java/org/apache/metron/common/configuration/profiler/ProfileConfigTest.java
+++ b/metron-platform/metron-common/src/test/java/org/apache/metron/common/configuration/profiler/ProfileConfigTest.java
@@ -21,7 +21,6 @@ package org.apache.metron.common.configuration.profiler;
 
 import com.fasterxml.jackson.databind.JsonMappingException;
 import org.adrianwalker.multilinestring.Multiline;
-import org.apache.metron.common.utils.JSONUtils;
 import org.junit.Test;
 
 import java.io.IOException;
@@ -51,12 +50,29 @@ public class ProfileConfigTest {
    * The 'onlyif' field should default to 'true' when it is not specified.
    */
   @Test
-  public void testOnlyIfDefault() throws IOException {
-    ProfileConfig profile = JSONUtils.INSTANCE.load(onlyIfDefault, ProfileConfig.class);
+  public void testFromJSONWithOnlyIfDefault() throws IOException {
+    ProfileConfig profile = ProfileConfig.fromJSON(onlyIfDefault);
     assertEquals("true", profile.getOnlyif());
   }
 
   /**
+   * Tests serializing the Profiler configuration to JSON.
+   */
+  @Test
+  public void testToJSONWithOnlyIfDefault() throws Exception {
+
+    // setup a profiler config to serialize
+    ProfileConfig expected = ProfileConfig.fromJSON(onlyIfDefault);
+
+    // execute the test - serialize the config
+    String asJson = expected.toJSON();
+
+    // validate - deserialize to validate
+    ProfileConfig actual = ProfileConfig.fromJSON(asJson);
+    assertEquals(expected, actual);
+  }
+
+  /**
    * {
    *    "foreach": "ip_src_addr",
    *    "update": {},
@@ -70,8 +86,8 @@ public class ProfileConfigTest {
    * The 'name' of the profile must be defined.
    */
   @Test(expected = JsonMappingException.class)
-  public void testNameMissing() throws IOException {
-    JSONUtils.INSTANCE.load(nameMissing, ProfileConfig.class);
+  public void testFromJSONWithNameMissing() throws IOException {
+    ProfileConfig.fromJSON(nameMissing);
   }
 
   /**
@@ -88,8 +104,8 @@ public class ProfileConfigTest {
    * The 'foreach' field must be defined.
    */
   @Test(expected = JsonMappingException.class)
-  public void testForeachMissing() throws IOException {
-    JSONUtils.INSTANCE.load(foreachMissing, ProfileConfig.class);
+  public void testFromJSONWithForeachMissing() throws IOException {
+    ProfileConfig.fromJSON(foreachMissing);
   }
 
   /**
@@ -106,8 +122,8 @@ public class ProfileConfigTest {
    * The 'result' field must be defined.
    */
   @Test(expected = JsonMappingException.class)
-  public void testResultMissing() throws IOException {
-    JSONUtils.INSTANCE.load(resultMissing, ProfileConfig.class);
+  public void testFromJSONWithResultMissing() throws IOException {
+    ProfileConfig.fromJSON(resultMissing);
   }
 
   /**
@@ -125,8 +141,8 @@ public class ProfileConfigTest {
    * The 'result' field must contain the 'profile' expression used to store the profile measurement.
    */
   @Test(expected = JsonMappingException.class)
-  public void testResultMissingProfileExpression() throws IOException {
-    JSONUtils.INSTANCE.load(resultMissingProfileExpression, ProfileConfig.class);
+  public void testFromJSONWithResultMissingProfileExpression() throws IOException {
+    ProfileConfig.fromJSON(resultMissingProfileExpression);
   }
 
   /**
@@ -145,8 +161,8 @@ public class ProfileConfigTest {
    * the 'profile' expression used to store the profile measurement.
    */
   @Test
-  public void testResultWithExpression() throws IOException {
-    ProfileConfig profile = JSONUtils.INSTANCE.load(resultWithExpression, ProfileConfig.class);
+  public void testFromJSONWithResultWithExpression() throws IOException {
+    ProfileConfig profile = ProfileConfig.fromJSON(resultWithExpression);
     assertEquals("2 + 2", profile.getResult().getProfileExpressions().getExpression());
 
     // no triage expressions expected
@@ -154,6 +170,23 @@ public class ProfileConfigTest {
   }
 
   /**
+   * Tests serializing the Profiler configuration to JSON.
+   */
+  @Test
+  public void testToJSONWithResultWithExpression() throws Exception {
+
+    // setup a profiler config to serialize
+    ProfileConfig expected = ProfileConfig.fromJSON(resultWithExpression);
+
+    // execute the test - serialize the config
+    String asJson = expected.toJSON();
+
+    // validate - deserialize to validate
+    ProfileConfig actual = ProfileConfig.fromJSON(asJson);
+    assertEquals(expected, actual);
+  }
+
+  /**
    * {
    *    "profile": "test",
    *    "foreach": "ip_src_addr",
@@ -170,8 +203,8 @@ public class ProfileConfigTest {
    * The result's 'triage' field is optional.
    */
   @Test
-  public void testResultWithProfileOnly() throws IOException {
-    ProfileConfig profile = JSONUtils.INSTANCE.load(resultWithProfileOnly, ProfileConfig.class);
+  public void testFromJSONWithResultWithProfileOnly() throws IOException {
+    ProfileConfig profile = ProfileConfig.fromJSON(resultWithProfileOnly);
     assertEquals("2 + 2", profile.getResult().getProfileExpressions().getExpression());
 
     // no triage expressions expected
@@ -179,6 +212,23 @@ public class ProfileConfigTest {
   }
 
   /**
+   * Tests serializing the Profiler configuration to JSON.
+   */
+  @Test
+  public void testToJSONWithProfileOnly() throws Exception {
+
+    // setup a profiler config to serialize
+    ProfileConfig expected = ProfileConfig.fromJSON(resultWithProfileOnly);
+
+    // execute the test - serialize the config
+    String asJson = expected.toJSON();
+
+    // validate - deserialize to validate
+    ProfileConfig actual = ProfileConfig.fromJSON(asJson);
+    assertEquals(expected, actual);
+  }
+
+  /**
    * {
    *    "profile": "test",
    *    "foreach": "ip_src_addr",
@@ -199,10 +249,28 @@ public class ProfileConfigTest {
    * The result's 'triage' field can contain many named expressions.
    */
   @Test
-  public void testResultWithTriage() throws IOException {
-    ProfileConfig profile = JSONUtils.INSTANCE.load(resultWithTriage, ProfileConfig.class);
+  public void testFromJSONWithResultWithTriage() throws IOException {
+    ProfileConfig profile = ProfileConfig.fromJSON(resultWithTriage);
 
     assertEquals("4 + 4", profile.getResult().getTriageExpressions().getExpression("eight"));
     assertEquals("8 + 8", profile.getResult().getTriageExpressions().getExpression("sixteen"));
   }
+
+  /**
+   * Tests serializing the Profiler configuration to JSON.
+   */
+  @Test
+  public void testToJSONWithResultWithTriage() throws Exception {
+
+    // setup a profiler config to serialize
+    ProfileConfig expected = ProfileConfig.fromJSON(resultWithTriage);
+
+    // execute the test - serialize the config
+    String asJson = expected.toJSON();
+
+    // validate - deserialize to validate
+    ProfileConfig actual = ProfileConfig.fromJSON(asJson);
+    assertEquals(expected, actual);
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/b5bf9a98/metron-platform/metron-common/src/test/java/org/apache/metron/common/configuration/profiler/ProfilerConfigTest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-common/src/test/java/org/apache/metron/common/configuration/profiler/ProfilerConfigTest.java b/metron-platform/metron-common/src/test/java/org/apache/metron/common/configuration/profiler/ProfilerConfigTest.java
index 2e73cde..1a11811 100644
--- a/metron-platform/metron-common/src/test/java/org/apache/metron/common/configuration/profiler/ProfilerConfigTest.java
+++ b/metron-platform/metron-common/src/test/java/org/apache/metron/common/configuration/profiler/ProfilerConfigTest.java
@@ -20,7 +20,6 @@
 package org.apache.metron.common.configuration.profiler;
 
 import org.adrianwalker.multilinestring.Multiline;
-import org.apache.metron.common.utils.JSONUtils;
 import org.junit.Test;
 
 import java.io.IOException;
@@ -48,14 +47,41 @@ public class ProfilerConfigTest {
    * }
    */
   @Multiline
+  private String profile;
+
+  /**
+   * Tests deserializing the Profiler configuration using the fromJSON(...) method.
+   */
+  @Test
+  public void testFromJSON() throws IOException {
+    ProfilerConfig conf = ProfilerConfig.fromJSON(profile);
+
+    assertFalse(conf.getTimestampField().isPresent());
+    assertEquals(1, conf.getProfiles().size());
+  }
+
+  /**
+   * {
+   *   "profiles": [
+   *      {
+   *        "profile": "profile1",
+   *        "foreach": "ip_src_addr",
+   *        "init":   { "count": "0" },
+   *        "update": { "count": "count + 1" },
+   *        "result":   "count"
+   *      }
+   *   ]
+   * }
+   */
+  @Multiline
   private String noTimestampField;
 
   /**
    * If no 'timestampField' is defined, it should not be present by default.
    */
   @Test
-  public void testNoTimestampField() throws IOException {
-    ProfilerConfig conf = JSONUtils.INSTANCE.load(noTimestampField, ProfilerConfig.class);
+  public void testFromJSONWithNoTimestampField() throws IOException {
+    ProfilerConfig conf = ProfilerConfig.fromJSON(noTimestampField);
     assertFalse(conf.getTimestampField().isPresent());
   }
 
@@ -77,11 +103,12 @@ public class ProfilerConfigTest {
   private String timestampField;
 
   /**
-   * If no 'timestampField' is defined, it should not be present by default.
+   * Tests deserializing the Profiler configuration when the timestamp field is defined.
    */
   @Test
-  public void testTimestampField() throws IOException {
-    ProfilerConfig conf = JSONUtils.INSTANCE.load(timestampField, ProfilerConfig.class);
+  public void testFromJSONWithTimestampField() throws IOException {
+    ProfilerConfig conf = ProfilerConfig.fromJSON(timestampField);
+
     assertTrue(conf.getTimestampField().isPresent());
   }
 
@@ -108,13 +135,75 @@ public class ProfilerConfigTest {
   @Multiline
   private String twoProfiles;
 
+  @Test
+  public void testFromJSONTwoProfiles() throws IOException {
+    ProfilerConfig conf = ProfilerConfig.fromJSON(twoProfiles);
+
+    assertEquals(2, conf.getProfiles().size());
+    assertFalse(conf.getTimestampField().isPresent());
+  }
+
   /**
-   * The 'onlyif' field should default to 'true' when it is not specified.
+   * Tests serializing the Profiler configuration to JSON.
    */
   @Test
-  public void testTwoProfiles() throws IOException {
-    ProfilerConfig conf = JSONUtils.INSTANCE.load(twoProfiles, ProfilerConfig.class);
-    assertEquals(2, conf.getProfiles().size());
+  public void testToJSON() throws Exception {
+
+    // setup a profiler config to serialize
+    ProfilerConfig expected = ProfilerConfig.fromJSON(profile);
+
+    // execute the test - serialize the config
+    String asJson = expected.toJSON();
+
+    // validate - deserialize to validate
+    ProfilerConfig actual = ProfilerConfig.fromJSON(asJson);
+    assertEquals(expected, actual);
   }
 
+  /**
+   * {
+   *   "profiles": [
+   *      {
+   *        "profile": "profile1",
+   *        "foreach": "ip_src_addr",
+   *        "init":   { "count": "0" },
+   *        "update": { "count": "count + 1" },
+   *        "result": {
+   *          "profile": "count",
+   *          "triage" : { "count": "count" }
+   *        }
+   *      }
+   *   ]
+   * }
+   */
+  @Multiline
+  private String profileWithTriageExpression;
+
+  @Test
+  public void testToJSONWithTriageExpression() throws Exception {
+
+    // setup a profiler config to serialize
+    ProfilerConfig expected = ProfilerConfig.fromJSON(profileWithTriageExpression);
+
+    // execute the test - serialize the config
+    String asJson = expected.toJSON();
+
+    // validate - deserialize to validate
+    ProfilerConfig actual = ProfilerConfig.fromJSON(asJson);
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void testToJSONWithTwoProfiles() throws Exception {
+
+    // setup a profiler config to serialize
+    ProfilerConfig expected = ProfilerConfig.fromJSON(twoProfiles);
+
+    // execute the test - serialize the config
+    String asJson = expected.toJSON();
+
+    // validate - deserialize to validate
+    ProfilerConfig actual = ProfilerConfig.fromJSON(asJson);
+    assertEquals(expected, actual);
+  }
 }