Rename our allwpilib (which is now 2020) to not have 2019 in the name

Change-Id: I3c07f85ed32ab8b97db765a9b43f2a6ce7da964a
diff --git a/wpiutil/src/test/java/edu/wpi/first/wpiutil/CircularBufferTest.java b/wpiutil/src/test/java/edu/wpi/first/wpiutil/CircularBufferTest.java
new file mode 100644
index 0000000..f995098
--- /dev/null
+++ b/wpiutil/src/test/java/edu/wpi/first/wpiutil/CircularBufferTest.java
@@ -0,0 +1,214 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2019 FIRST. All Rights Reserved.                        */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.wpiutil;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class CircularBufferTest {
+  private final double[] m_values = {751.848, 766.366, 342.657, 234.252, 716.126,
+      132.344, 445.697, 22.727, 421.125, 799.913};
+  private final double[] m_addFirstOut = {799.913, 421.125, 22.727, 445.697, 132.344,
+      716.126, 234.252, 342.657};
+  private final double[] m_addLastOut = {342.657, 234.252, 716.126, 132.344, 445.697,
+      22.727, 421.125, 799.913};
+
+  @Test
+  void addFirstTest() {
+    CircularBuffer queue = new CircularBuffer(8);
+
+    for (double value : m_values) {
+      queue.addFirst(value);
+    }
+
+    for (int i = 0; i < m_addFirstOut.length; i++) {
+      assertEquals(m_addFirstOut[i], queue.get(i), 0.00005);
+    }
+  }
+
+  @Test
+  void addLastTest() {
+    CircularBuffer queue = new CircularBuffer(8);
+
+    for (double value : m_values) {
+      queue.addLast(value);
+    }
+
+    for (int i = 0; i < m_addLastOut.length; i++) {
+      assertEquals(m_addLastOut[i], queue.get(i), 0.00005);
+    }
+  }
+
+  @Test
+  void pushPopTest() {
+    CircularBuffer queue = new CircularBuffer(3);
+
+    // Insert three elements into the buffer
+    queue.addLast(1.0);
+    queue.addLast(2.0);
+    queue.addLast(3.0);
+
+    assertEquals(1.0, queue.get(0), 0.00005);
+    assertEquals(2.0, queue.get(1), 0.00005);
+    assertEquals(3.0, queue.get(2), 0.00005);
+
+    /*
+     * The buffer is full now, so pushing subsequent elements will overwrite the
+     * front-most elements.
+     */
+
+    queue.addLast(4.0); // Overwrite 1 with 4
+
+    // The buffer now contains 2, 3, and 4
+    assertEquals(2.0, queue.get(0), 0.00005);
+    assertEquals(3.0, queue.get(1), 0.00005);
+    assertEquals(4.0, queue.get(2), 0.00005);
+
+    queue.addLast(5.0); // Overwrite 2 with 5
+
+    // The buffer now contains 3, 4, and 5
+    assertEquals(3.0, queue.get(0), 0.00005);
+    assertEquals(4.0, queue.get(1), 0.00005);
+    assertEquals(5.0, queue.get(2), 0.00005);
+
+    assertEquals(5.0, queue.removeLast(), 0.00005); // 5 is removed
+
+    // The buffer now contains 3 and 4
+    assertEquals(3.0, queue.get(0), 0.00005);
+    assertEquals(4.0, queue.get(1), 0.00005);
+
+    assertEquals(3.0, queue.removeFirst(), 0.00005); // 3 is removed
+
+    // Leaving only one element with value == 4
+    assertEquals(4.0, queue.get(0), 0.00005);
+  }
+
+  @Test
+  void resetTest() {
+    CircularBuffer queue = new CircularBuffer(5);
+
+    for (int i = 0; i < 6; i++) {
+      queue.addLast(i);
+    }
+
+    queue.clear();
+
+    for (int i = 0; i < 5; i++) {
+      assertEquals(0.0, queue.get(i), 0.00005);
+    }
+  }
+
+  @Test
+  @SuppressWarnings("PMD.ExcessiveMethodLength")
+  void resizeTest() {
+    CircularBuffer queue = new CircularBuffer(5);
+
+    /* Buffer contains {1, 2, 3, _, _}
+     *                  ^ front
+     */
+    queue.addLast(1.0);
+    queue.addLast(2.0);
+    queue.addLast(3.0);
+
+    queue.resize(2);
+    assertEquals(1.0, queue.get(0), 0.00005);
+    assertEquals(2.0, queue.get(1), 0.00005);
+
+    queue.resize(5);
+    assertEquals(1.0, queue.get(0), 0.00005);
+    assertEquals(2.0, queue.get(1), 0.00005);
+
+    queue.clear();
+
+    /* Buffer contains {_, 1, 2, 3, _}
+     *                     ^ front
+     */
+    queue.addLast(0.0);
+    queue.addLast(1.0);
+    queue.addLast(2.0);
+    queue.addLast(3.0);
+    queue.removeFirst();
+
+    queue.resize(2);
+    assertEquals(1.0, queue.get(0), 0.00005);
+    assertEquals(2.0, queue.get(1), 0.00005);
+
+    queue.resize(5);
+    assertEquals(1.0, queue.get(0), 0.00005);
+    assertEquals(2.0, queue.get(1), 0.00005);
+
+    queue.clear();
+
+    /* Buffer contains {_, _, 1, 2, 3}
+     *                        ^ front
+     */
+    queue.addLast(0.0);
+    queue.addLast(0.0);
+    queue.addLast(1.0);
+    queue.addLast(2.0);
+    queue.addLast(3.0);
+    queue.removeFirst();
+    queue.removeFirst();
+
+    queue.resize(2);
+    assertEquals(1.0, queue.get(0), 0.00005);
+    assertEquals(2.0, queue.get(1), 0.00005);
+
+    queue.resize(5);
+    assertEquals(1.0, queue.get(0), 0.00005);
+    assertEquals(2.0, queue.get(1), 0.00005);
+
+    queue.clear();
+
+    /* Buffer contains {3, _, _, 1, 2}
+     *                           ^ front
+     */
+    queue.addLast(3.0);
+    queue.addFirst(2.0);
+    queue.addFirst(1.0);
+
+    queue.resize(2);
+    assertEquals(1.0, queue.get(0), 0.00005);
+    assertEquals(2.0, queue.get(1), 0.00005);
+
+    queue.resize(5);
+    assertEquals(1.0, queue.get(0), 0.00005);
+    assertEquals(2.0, queue.get(1), 0.00005);
+
+    queue.clear();
+
+    /* Buffer contains {2, 3, _, _, 1}
+     *                              ^ front
+     */
+    queue.addLast(2.0);
+    queue.addLast(3.0);
+    queue.addFirst(1.0);
+
+    queue.resize(2);
+    assertEquals(1.0, queue.get(0), 0.00005);
+    assertEquals(2.0, queue.get(1), 0.00005);
+
+    queue.resize(5);
+    assertEquals(1.0, queue.get(0), 0.00005);
+    assertEquals(2.0, queue.get(1), 0.00005);
+
+    // Test addLast() after resize
+    queue.addLast(3.0);
+    assertEquals(1.0, queue.get(0), 0.00005);
+    assertEquals(2.0, queue.get(1), 0.00005);
+    assertEquals(3.0, queue.get(2), 0.00005);
+
+    // Test addFirst() after resize
+    queue.addFirst(4.0);
+    assertEquals(4.0, queue.get(0), 0.00005);
+    assertEquals(1.0, queue.get(1), 0.00005);
+    assertEquals(2.0, queue.get(2), 0.00005);
+    assertEquals(3.0, queue.get(3), 0.00005);
+  }
+}
diff --git a/wpiutil/src/test/java/edu/wpi/first/wpiutil/math/MatrixTest.java b/wpiutil/src/test/java/edu/wpi/first/wpiutil/math/MatrixTest.java
new file mode 100644
index 0000000..4d6697d
--- /dev/null
+++ b/wpiutil/src/test/java/edu/wpi/first/wpiutil/math/MatrixTest.java
@@ -0,0 +1,210 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2019 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.wpiutil.math;
+
+import org.ejml.data.SingularMatrixException;
+import org.ejml.dense.row.MatrixFeatures_DDRM;
+import org.ejml.simple.SimpleMatrix;
+import org.junit.jupiter.api.Test;
+
+import edu.wpi.first.wpiutil.math.numbers.N1;
+import edu.wpi.first.wpiutil.math.numbers.N2;
+import edu.wpi.first.wpiutil.math.numbers.N3;
+import edu.wpi.first.wpiutil.math.numbers.N4;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class MatrixTest {
+  @Test
+  void testMatrixMultiplication() {
+    var mat1 = MatrixUtils.mat(Nat.N2(), Nat.N2())
+        .fill(2.0, 1.0,
+            0.0, 1.0);
+    var mat2 = MatrixUtils.mat(Nat.N2(), Nat.N2())
+        .fill(3.0, 0.0,
+            0.0, 2.5);
+
+    Matrix<N2, N2> result = mat1.times(mat2);
+
+    assertTrue(MatrixFeatures_DDRM.isEquals(
+        MatrixUtils.mat(Nat.N2(), Nat.N2())
+        .fill(6.0, 2.5,
+            0.0, 2.5).getStorage().getDDRM(),
+        result.getStorage().getDDRM()
+    ));
+
+    var mat3 = MatrixUtils.mat(Nat.N2(), Nat.N3())
+        .fill(1.0, 3.0, 0.5,
+            2.0, 4.3, 1.2);
+    var mat4 = MatrixUtils.mat(Nat.N3(), Nat.N4())
+        .fill(3.0, 1.5, 2.0, 4.5,
+            2.3, 1.0, 1.6, 3.1,
+            5.2, 2.1, 2.0, 1.0);
+
+    Matrix<N2, N4> result2 = mat3.times(mat4);
+
+    assertTrue(MatrixFeatures_DDRM.isIdentical(
+        MatrixUtils.mat(Nat.N2(), Nat.N4())
+        .fill(12.5, 5.55, 7.8, 14.3,
+            22.13, 9.82, 13.28, 23.53).getStorage().getDDRM(),
+        result2.getStorage().getDDRM(),
+        1E-9
+    ));
+  }
+
+  @Test
+  void testMatrixVectorMultiplication() {
+    var mat = MatrixUtils.mat(Nat.N2(), Nat.N2())
+        .fill(1.0, 1.0,
+            0.0, 1.0);
+
+    var vec = MatrixUtils.vec(Nat.N2())
+        .fill(3.0,
+            2.0);
+
+    Matrix<N2, N1> result = mat.times(vec);
+    assertTrue(MatrixFeatures_DDRM.isEquals(
+        MatrixUtils.vec(Nat.N2())
+        .fill(5.0,
+            2.0).getStorage().getDDRM(),
+        result.getStorage().getDDRM()
+    ));
+  }
+
+  @Test
+  void testTranspose() {
+    Matrix<N3, N1> vec = MatrixUtils.vec(Nat.N3())
+        .fill(1.0,
+            2.0,
+            3.0);
+
+    Matrix<N1, N3> transpose = vec.transpose();
+
+    assertTrue(MatrixFeatures_DDRM.isEquals(
+        MatrixUtils.mat(Nat.N1(), Nat.N3()).fill(1.0, 2.0, 3.0).getStorage()
+        .getDDRM(),
+        transpose.getStorage().getDDRM()
+    ));
+  }
+
+  @Test
+  void testInverse() {
+    var mat = MatrixUtils.mat(Nat.N3(), Nat.N3())
+        .fill(1.0, 3.0, 2.0,
+            5.0, 2.0, 1.5,
+            0.0, 1.3, 2.5);
+
+    var inv = mat.inv();
+
+    assertTrue(MatrixFeatures_DDRM.isIdentical(
+        MatrixUtils.eye(Nat.N3()).getStorage().getDDRM(),
+        mat.times(inv).getStorage().getDDRM(),
+        1E-9
+    ));
+
+    assertTrue(MatrixFeatures_DDRM.isIdentical(
+        MatrixUtils.eye(Nat.N3()).getStorage().getDDRM(),
+        inv.times(mat).getStorage().getDDRM(),
+        1E-9
+    ));
+  }
+
+  @Test
+  void testUninvertableMatrix() {
+    var singularMatrix = MatrixUtils.mat(Nat.N2(), Nat.N2())
+        .fill(2.0, 1.0,
+            2.0, 1.0);
+
+    assertThrows(SingularMatrixException.class, singularMatrix::inv);
+  }
+
+  @Test
+  void testMatrixScalarArithmetic() {
+    var mat = MatrixUtils.mat(Nat.N2(), Nat.N2())
+        .fill(1.0, 2.0,
+            3.0, 4.0);
+
+
+    assertTrue(MatrixFeatures_DDRM.isEquals(
+        MatrixUtils.mat(Nat.N2(), Nat.N2())
+        .fill(3.0, 4.0,
+            5.0, 6.0).getStorage().getDDRM(),
+        mat.plus(2.0).getStorage().getDDRM()
+    ));
+
+    assertTrue(MatrixFeatures_DDRM.isEquals(
+        MatrixUtils.mat(Nat.N2(), Nat.N2())
+        .fill(0.0, 1.0,
+            2.0, 3.0).getStorage().getDDRM(),
+        mat.minus(1.0).getStorage().getDDRM()
+    ));
+
+    assertTrue(MatrixFeatures_DDRM.isEquals(
+        MatrixUtils.mat(Nat.N2(), Nat.N2())
+        .fill(2.0, 4.0,
+            6.0, 8.0).getStorage().getDDRM(),
+        mat.times(2.0).getStorage().getDDRM()
+    ));
+
+    assertTrue(MatrixFeatures_DDRM.isIdentical(
+        MatrixUtils.mat(Nat.N2(), Nat.N2())
+        .fill(0.5, 1.0,
+            1.5, 2.0).getStorage().getDDRM(),
+        mat.div(2.0).getStorage().getDDRM(),
+        1E-3
+    ));
+  }
+
+  @Test
+  void testMatrixMatrixArithmetic() {
+    var mat1 = MatrixUtils.mat(Nat.N2(), Nat.N2())
+        .fill(1.0, 2.0,
+            3.0, 4.0);
+
+    var mat2 = MatrixUtils.mat(Nat.N2(), Nat.N2())
+        .fill(5.0, 6.0,
+            7.0, 8.0);
+
+    assertTrue(MatrixFeatures_DDRM.isEquals(
+        MatrixUtils.mat(Nat.N2(), Nat.N2())
+        .fill(-4.0, -4.0,
+            -4.0, -4.0).getStorage().getDDRM(),
+        mat1.minus(mat2).getStorage().getDDRM()
+    ));
+
+    assertTrue(MatrixFeatures_DDRM.isEquals(
+        MatrixUtils.mat(Nat.N2(), Nat.N2())
+        .fill(6.0, 8.0,
+            10.0, 12.0).getStorage().getDDRM(),
+        mat1.plus(mat2).getStorage().getDDRM()
+    ));
+  }
+
+  @Test
+  void testMatrixExponential() {
+    SimpleMatrix matrix = MatrixUtils.eye(Nat.N2()).getStorage();
+    var result = SimpleMatrixUtils.expm(matrix);
+
+    assertTrue(MatrixFeatures_DDRM.isIdentical(
+        result.getDDRM(),
+        new SimpleMatrix(2, 2, true, new double[]{Math.E, 0, 0, Math.E}).getDDRM(),
+        1E-9
+    ));
+
+    matrix = new SimpleMatrix(2, 2, true, new double[]{1, 2, 3, 4});
+    result = SimpleMatrixUtils.expm(matrix.scale(0.01));
+
+    assertTrue(MatrixFeatures_DDRM.isIdentical(
+        result.getDDRM(),
+        new SimpleMatrix(2, 2, true, new double[]{1.01035625, 0.02050912,
+            0.03076368, 1.04111993}).getDDRM(),
+        1E-8
+    ));
+  }
+}
diff --git a/wpiutil/src/test/native/ManagedStaticTest.cpp b/wpiutil/src/test/native/ManagedStaticTest.cpp
new file mode 100644
index 0000000..81a4b9c
--- /dev/null
+++ b/wpiutil/src/test/native/ManagedStaticTest.cpp
@@ -0,0 +1,60 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2019 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include "wpi/ManagedStatic.h"  // NOLINT(build/include_order)
+
+#include "gtest/gtest.h"
+
+static int refCount = 0;
+
+struct StaticTestClass {
+  StaticTestClass() { refCount++; }
+  ~StaticTestClass() { refCount--; }
+
+  void Func() {}
+};
+
+namespace wpi {
+TEST(ManagedStaticTest, LazyDoesNotInitialize) {
+  {
+    refCount = 0;
+    wpi::ManagedStatic<StaticTestClass> managedStatic;
+    ASSERT_EQ(refCount, 0);
+  }
+  ASSERT_EQ(refCount, 0);
+  wpi_shutdown();
+}
+
+TEST(ManagedStaticTest, LazyInitDoesntDestruct) {
+  {
+    refCount = 0;
+    wpi::ManagedStatic<StaticTestClass> managedStatic;
+    ASSERT_EQ(refCount, 0);
+    managedStatic->Func();
+    ASSERT_EQ(refCount, 1);
+  }
+  ASSERT_EQ(refCount, 1);
+  wpi_shutdown();
+  ASSERT_EQ(refCount, 0);
+}
+
+TEST(ManagedStaticTest, EagerInit) {
+  {
+    refCount = 0;
+    StaticTestClass* test = new StaticTestClass{};
+    ASSERT_EQ(refCount, 1);
+    wpi::ManagedStatic<StaticTestClass> managedStatic(
+        test, [](void* val) { delete static_cast<StaticTestClass*>(val); });
+    ASSERT_EQ(refCount, 1);
+    managedStatic->Func();
+    ASSERT_EQ(refCount, 1);
+  }
+  ASSERT_EQ(refCount, 1);
+  wpi_shutdown();
+  ASSERT_EQ(refCount, 0);
+}
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/Base64Test.cpp b/wpiutil/src/test/native/cpp/Base64Test.cpp
new file mode 100644
index 0000000..caa35aa
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/Base64Test.cpp
@@ -0,0 +1,104 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2019 FIRST. All Rights Reserved.                        */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include "gtest/gtest.h"
+#include "wpi/Base64.h"
+#include "wpi/SmallString.h"
+
+namespace wpi {
+
+struct Base64TestParam {
+  int plain_len;
+  const char* plain;
+  const char* encoded;
+};
+
+std::ostream& operator<<(std::ostream& os, const Base64TestParam& param) {
+  os << "Base64TestParam(Len: " << param.plain_len << ", "
+     << "Plain: \"" << param.plain << "\", "
+     << "Encoded: \"" << param.encoded << "\")";
+  return os;
+}
+
+class Base64Test : public ::testing::TestWithParam<Base64TestParam> {
+ protected:
+  StringRef GetPlain() {
+    if (GetParam().plain_len < 0)
+      return StringRef(GetParam().plain);
+    else
+      return StringRef(GetParam().plain, GetParam().plain_len);
+  }
+};
+
+TEST_P(Base64Test, EncodeStdString) {
+  std::string s;
+  Base64Encode(GetPlain(), &s);
+  ASSERT_EQ(GetParam().encoded, s);
+
+  // text already in s
+  Base64Encode(GetPlain(), &s);
+  ASSERT_EQ(GetParam().encoded, s);
+}
+
+TEST_P(Base64Test, EncodeSmallString) {
+  SmallString<128> buf;
+  ASSERT_EQ(GetParam().encoded, Base64Encode(GetPlain(), buf));
+  // reuse buf
+  ASSERT_EQ(GetParam().encoded, Base64Encode(GetPlain(), buf));
+}
+
+TEST_P(Base64Test, DecodeStdString) {
+  std::string s;
+  StringRef encoded = GetParam().encoded;
+  EXPECT_EQ(encoded.size(), Base64Decode(encoded, &s));
+  ASSERT_EQ(GetPlain(), s);
+
+  // text already in s
+  Base64Decode(encoded, &s);
+  ASSERT_EQ(GetPlain(), s);
+}
+
+TEST_P(Base64Test, DecodeSmallString) {
+  SmallString<128> buf;
+  StringRef encoded = GetParam().encoded;
+  size_t len;
+  StringRef plain = Base64Decode(encoded, &len, buf);
+  EXPECT_EQ(encoded.size(), len);
+  ASSERT_EQ(GetPlain(), plain);
+
+  // reuse buf
+  plain = Base64Decode(encoded, &len, buf);
+  ASSERT_EQ(GetPlain(), plain);
+}
+
+static Base64TestParam sample[] = {
+    {-1, "Send reinforcements", "U2VuZCByZWluZm9yY2VtZW50cw=="},
+    {-1, "Now is the time for all good coders\n to learn C++",
+     "Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKIHRvIGxlYXJuIEMrKw=="},
+    {-1,
+     "This is line one\nThis is line two\nThis is line three\nAnd so on...\n",
+     "VGhpcyBpcyBsaW5lIG9uZQpUaGlzIGlzIGxpbmUgdHdvClRoaXMgaXMgbGluZSB0aHJlZQpBb"
+     "mQgc28gb24uLi4K"},
+};
+
+INSTANTIATE_TEST_SUITE_P(Base64Sample, Base64Test, ::testing::ValuesIn(sample));
+
+static Base64TestParam standard[] = {
+    {0, "", ""},
+    {1, "\0", "AA=="},
+    {2, "\0\0", "AAA="},
+    {3, "\0\0\0", "AAAA"},
+    {1, "\377", "/w=="},
+    {2, "\377\377", "//8="},
+    {3, "\377\377\377", "////"},
+    {2, "\xff\xef", "/+8="},
+};
+
+INSTANTIATE_TEST_SUITE_P(Base64Standard, Base64Test,
+                         ::testing::ValuesIn(standard));
+
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/CircularBufferTest.cpp b/wpiutil/src/test/native/cpp/CircularBufferTest.cpp
new file mode 100644
index 0000000..7fe9e03
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/CircularBufferTest.cpp
@@ -0,0 +1,209 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2019 FIRST. All Rights Reserved.                        */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include "wpi/circular_buffer.h"  // NOLINT(build/include_order)
+
+#include <array>
+
+#include "gtest/gtest.h"
+
+static const std::array<double, 10> values = {
+    {751.848, 766.366, 342.657, 234.252, 716.126, 132.344, 445.697, 22.727,
+     421.125, 799.913}};
+
+static const std::array<double, 8> pushFrontOut = {
+    {799.913, 421.125, 22.727, 445.697, 132.344, 716.126, 234.252, 342.657}};
+
+static const std::array<double, 8> pushBackOut = {
+    {342.657, 234.252, 716.126, 132.344, 445.697, 22.727, 421.125, 799.913}};
+
+TEST(CircularBufferTest, PushFrontTest) {
+  wpi::circular_buffer<double> queue(8);
+
+  for (auto& value : values) {
+    queue.push_front(value);
+  }
+
+  for (size_t i = 0; i < pushFrontOut.size(); i++) {
+    EXPECT_EQ(pushFrontOut[i], queue[i]);
+  }
+}
+
+TEST(CircularBufferTest, PushBackTest) {
+  wpi::circular_buffer<double> queue(8);
+
+  for (auto& value : values) {
+    queue.push_back(value);
+  }
+
+  for (size_t i = 0; i < pushBackOut.size(); i++) {
+    EXPECT_EQ(pushBackOut[i], queue[i]);
+  }
+}
+
+TEST(CircularBufferTest, PushPopTest) {
+  wpi::circular_buffer<double> queue(3);
+
+  // Insert three elements into the buffer
+  queue.push_back(1.0);
+  queue.push_back(2.0);
+  queue.push_back(3.0);
+
+  EXPECT_EQ(1.0, queue[0]);
+  EXPECT_EQ(2.0, queue[1]);
+  EXPECT_EQ(3.0, queue[2]);
+
+  /*
+   * The buffer is full now, so pushing subsequent elements will overwrite the
+   * front-most elements.
+   */
+
+  queue.push_back(4.0);  // Overwrite 1 with 4
+
+  // The buffer now contains 2, 3 and 4
+  EXPECT_EQ(2.0, queue[0]);
+  EXPECT_EQ(3.0, queue[1]);
+  EXPECT_EQ(4.0, queue[2]);
+
+  queue.push_back(5.0);  // Overwrite 2 with 5
+
+  // The buffer now contains 3, 4 and 5
+  EXPECT_EQ(3.0, queue[0]);
+  EXPECT_EQ(4.0, queue[1]);
+  EXPECT_EQ(5.0, queue[2]);
+
+  EXPECT_EQ(5.0, queue.pop_back());  // 5 is removed
+
+  // The buffer now contains 3 and 4
+  EXPECT_EQ(3.0, queue[0]);
+  EXPECT_EQ(4.0, queue[1]);
+
+  EXPECT_EQ(3.0, queue.pop_front());  // 3 is removed
+
+  // Leaving only one element with value == 4
+  EXPECT_EQ(4.0, queue[0]);
+}
+
+TEST(CircularBufferTest, ResetTest) {
+  wpi::circular_buffer<double> queue(5);
+
+  for (size_t i = 1; i < 6; i++) {
+    queue.push_back(i);
+  }
+
+  queue.reset();
+
+  for (size_t i = 0; i < 5; i++) {
+    EXPECT_EQ(0.0, queue[i]);
+  }
+}
+
+TEST(CircularBufferTest, ResizeTest) {
+  wpi::circular_buffer<double> queue(5);
+
+  /* Buffer contains {1, 2, 3, _, _}
+   *                  ^ front
+   */
+  queue.push_back(1.0);
+  queue.push_back(2.0);
+  queue.push_back(3.0);
+
+  queue.resize(2);
+  EXPECT_EQ(1.0, queue[0]);
+  EXPECT_EQ(2.0, queue[1]);
+
+  queue.resize(5);
+  EXPECT_EQ(1.0, queue[0]);
+  EXPECT_EQ(2.0, queue[1]);
+
+  queue.reset();
+
+  /* Buffer contains {_, 1, 2, 3, _}
+   *                     ^ front
+   */
+  queue.push_back(0.0);
+  queue.push_back(1.0);
+  queue.push_back(2.0);
+  queue.push_back(3.0);
+  queue.pop_front();
+
+  queue.resize(2);
+  EXPECT_EQ(1.0, queue[0]);
+  EXPECT_EQ(2.0, queue[1]);
+
+  queue.resize(5);
+  EXPECT_EQ(1.0, queue[0]);
+  EXPECT_EQ(2.0, queue[1]);
+
+  queue.reset();
+
+  /* Buffer contains {_, _, 1, 2, 3}
+   *                        ^ front
+   */
+  queue.push_back(0.0);
+  queue.push_back(0.0);
+  queue.push_back(1.0);
+  queue.push_back(2.0);
+  queue.push_back(3.0);
+  queue.pop_front();
+  queue.pop_front();
+
+  queue.resize(2);
+  EXPECT_EQ(1.0, queue[0]);
+  EXPECT_EQ(2.0, queue[1]);
+
+  queue.resize(5);
+  EXPECT_EQ(1.0, queue[0]);
+  EXPECT_EQ(2.0, queue[1]);
+
+  queue.reset();
+
+  /* Buffer contains {3, _, _, 1, 2}
+   *                           ^ front
+   */
+  queue.push_back(3.0);
+  queue.push_front(2.0);
+  queue.push_front(1.0);
+
+  queue.resize(2);
+  EXPECT_EQ(1.0, queue[0]);
+  EXPECT_EQ(2.0, queue[1]);
+
+  queue.resize(5);
+  EXPECT_EQ(1.0, queue[0]);
+  EXPECT_EQ(2.0, queue[1]);
+
+  queue.reset();
+
+  /* Buffer contains {2, 3, _, _, 1}
+   *                              ^ front
+   */
+  queue.push_back(2.0);
+  queue.push_back(3.0);
+  queue.push_front(1.0);
+
+  queue.resize(2);
+  EXPECT_EQ(1.0, queue[0]);
+  EXPECT_EQ(2.0, queue[1]);
+
+  queue.resize(5);
+  EXPECT_EQ(1.0, queue[0]);
+  EXPECT_EQ(2.0, queue[1]);
+
+  // Test push_back() after resize
+  queue.push_back(3.0);
+  EXPECT_EQ(1.0, queue[0]);
+  EXPECT_EQ(2.0, queue[1]);
+  EXPECT_EQ(3.0, queue[2]);
+
+  // Test push_front() after resize
+  queue.push_front(4.0);
+  EXPECT_EQ(4.0, queue[0]);
+  EXPECT_EQ(1.0, queue[1]);
+  EXPECT_EQ(2.0, queue[2]);
+  EXPECT_EQ(3.0, queue[3]);
+}
diff --git a/wpiutil/src/test/native/cpp/EigenTest.cpp b/wpiutil/src/test/native/cpp/EigenTest.cpp
new file mode 100644
index 0000000..04a0bc2
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/EigenTest.cpp
@@ -0,0 +1,61 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2019 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include <Eigen/Core>
+#include <Eigen/LU>
+
+#include "gtest/gtest.h"
+
+TEST(EigenTest, MultiplicationTest) {
+  Eigen::Matrix<double, 2, 2> m1;
+  m1 << 2, 1, 0, 1;
+
+  Eigen::Matrix<double, 2, 2> m2;
+  m2 << 3, 0, 0, 2.5;
+
+  const auto result = m1 * m2;
+
+  Eigen::Matrix<double, 2, 2> expectedResult;
+  expectedResult << 6.0, 2.5, 0.0, 2.5;
+
+  EXPECT_TRUE(expectedResult.isApprox(result));
+
+  Eigen::Matrix<double, 2, 3> m3;
+  m3 << 1.0, 3.0, 0.5, 2.0, 4.3, 1.2;
+
+  Eigen::Matrix<double, 3, 4> m4;
+  m4 << 3.0, 1.5, 2.0, 4.5, 2.3, 1.0, 1.6, 3.1, 5.2, 2.1, 2.0, 1.0;
+
+  const auto result2 = m3 * m4;
+
+  Eigen::Matrix<double, 2, 4> expectedResult2;
+  expectedResult2 << 12.5, 5.55, 7.8, 14.3, 22.13, 9.82, 13.28, 23.53;
+
+  EXPECT_TRUE(expectedResult2.isApprox(result2));
+}
+
+TEST(EigenTest, TransposeTest) {
+  Eigen::Matrix<double, 3, 1> vec;
+  vec << 1, 2, 3;
+
+  const auto transpose = vec.transpose();
+
+  Eigen::Matrix<double, 1, 3> expectedTranspose;
+  expectedTranspose << 1, 2, 3;
+
+  EXPECT_TRUE(expectedTranspose.isApprox(transpose));
+}
+
+TEST(EigenTest, InverseTest) {
+  Eigen::Matrix<double, 3, 3> mat;
+  mat << 1.0, 3.0, 2.0, 5.0, 2.0, 1.5, 0.0, 1.3, 2.5;
+
+  const auto inverse = mat.inverse();
+  const auto identity = Eigen::MatrixXd::Identity(3, 3);
+
+  EXPECT_TRUE(identity.isApprox(mat * inverse));
+}
diff --git a/wpiutil/src/test/native/cpp/HttpParserTest.cpp b/wpiutil/src/test/native/cpp/HttpParserTest.cpp
new file mode 100644
index 0000000..8de2bc6
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/HttpParserTest.cpp
@@ -0,0 +1,209 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include "wpi/HttpParser.h"  // NOLINT(build/include_order)
+
+#include "gtest/gtest.h"
+
+namespace wpi {
+
+TEST(HttpParserTest, UrlMethodHeadersComplete) {
+  HttpParser p{HttpParser::kRequest};
+  int callbacks = 0;
+  p.url.connect([&](StringRef path) {
+    ASSERT_EQ(path, "/foo/bar");
+    ASSERT_EQ(p.GetUrl(), "/foo/bar");
+    ++callbacks;
+  });
+  p.Execute("GET /foo");
+  p.Execute("/bar");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute(" HTTP/1.1\r\n\r\n");
+  ASSERT_EQ(callbacks, 1);
+  ASSERT_EQ(p.GetUrl(), "/foo/bar");
+  ASSERT_EQ(p.GetMethod(), HTTP_GET);
+  ASSERT_FALSE(p.HasError());
+}
+
+TEST(HttpParserTest, UrlMethodHeader) {
+  HttpParser p{HttpParser::kRequest};
+  int callbacks = 0;
+  p.url.connect([&](StringRef path) {
+    ASSERT_EQ(path, "/foo/bar");
+    ASSERT_EQ(p.GetUrl(), "/foo/bar");
+    ++callbacks;
+  });
+  p.Execute("GET /foo");
+  p.Execute("/bar");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute(" HTTP/1.1\r\n");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute("F");
+  ASSERT_EQ(callbacks, 1);
+  ASSERT_EQ(p.GetUrl(), "/foo/bar");
+  ASSERT_EQ(p.GetMethod(), HTTP_GET);
+  ASSERT_FALSE(p.HasError());
+}
+
+TEST(HttpParserTest, StatusHeadersComplete) {
+  HttpParser p{HttpParser::kResponse};
+  int callbacks = 0;
+  p.status.connect([&](StringRef status) {
+    ASSERT_EQ(status, "OK");
+    ASSERT_EQ(p.GetStatusCode(), 200u);
+    ++callbacks;
+  });
+  p.Execute("HTTP/1.1 200");
+  p.Execute(" OK");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute("\r\n\r\n");
+  ASSERT_EQ(callbacks, 1);
+  ASSERT_EQ(p.GetStatusCode(), 200u);
+  ASSERT_FALSE(p.HasError());
+}
+
+TEST(HttpParserTest, StatusHeader) {
+  HttpParser p{HttpParser::kResponse};
+  int callbacks = 0;
+  p.status.connect([&](StringRef status) {
+    ASSERT_EQ(status, "OK");
+    ASSERT_EQ(p.GetStatusCode(), 200u);
+    ++callbacks;
+  });
+  p.Execute("HTTP/1.1 200");
+  p.Execute(" OK\r\n");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute("F");
+  ASSERT_EQ(callbacks, 1);
+  ASSERT_EQ(p.GetStatusCode(), 200u);
+  ASSERT_FALSE(p.HasError());
+}
+
+TEST(HttpParserTest, HeaderFieldComplete) {
+  HttpParser p{HttpParser::kRequest};
+  int callbacks = 0;
+  p.header.connect([&](StringRef name, StringRef value) {
+    ASSERT_EQ(name, "Foo");
+    ASSERT_EQ(value, "Bar");
+    ++callbacks;
+  });
+  p.Execute("GET / HTTP/1.1\r\n");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute("Fo");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute("o: ");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute("Bar");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute("\r\n");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute("\r\n");
+  ASSERT_EQ(callbacks, 1);
+  ASSERT_FALSE(p.HasError());
+}
+
+TEST(HttpParserTest, HeaderFieldNext) {
+  HttpParser p{HttpParser::kRequest};
+  int callbacks = 0;
+  p.header.connect([&](StringRef name, StringRef value) {
+    ASSERT_EQ(name, "Foo");
+    ASSERT_EQ(value, "Bar");
+    ++callbacks;
+  });
+  p.Execute("GET / HTTP/1.1\r\n");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute("Fo");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute("o: ");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute("Bar");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute("\r\n");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute("F");
+  ASSERT_EQ(callbacks, 1);
+  ASSERT_FALSE(p.HasError());
+}
+
+TEST(HttpParserTest, HeadersComplete) {
+  HttpParser p{HttpParser::kRequest};
+  int callbacks = 0;
+  p.headersComplete.connect([&](bool keepAlive) {
+    ASSERT_EQ(keepAlive, false);
+    ++callbacks;
+  });
+  p.Execute("GET / HTTP/1.0\r\n");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute("\r\n");
+  ASSERT_EQ(callbacks, 1);
+  ASSERT_FALSE(p.HasError());
+}
+
+TEST(HttpParserTest, HeadersCompleteHTTP11) {
+  HttpParser p{HttpParser::kRequest};
+  int callbacks = 0;
+  p.headersComplete.connect([&](bool keepAlive) {
+    ASSERT_EQ(keepAlive, true);
+    ++callbacks;
+  });
+  p.Execute("GET / HTTP/1.1\r\n");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute("\r\n");
+  ASSERT_EQ(callbacks, 1);
+  ASSERT_FALSE(p.HasError());
+}
+
+TEST(HttpParserTest, HeadersCompleteKeepAlive) {
+  HttpParser p{HttpParser::kRequest};
+  int callbacks = 0;
+  p.headersComplete.connect([&](bool keepAlive) {
+    ASSERT_EQ(keepAlive, true);
+    ++callbacks;
+  });
+  p.Execute("GET / HTTP/1.0\r\n");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute("Connection: Keep-Alive\r\n");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute("\r\n");
+  ASSERT_EQ(callbacks, 1);
+  ASSERT_FALSE(p.HasError());
+}
+
+TEST(HttpParserTest, HeadersCompleteUpgrade) {
+  HttpParser p{HttpParser::kRequest};
+  int callbacks = 0;
+  p.headersComplete.connect([&](bool) {
+    ASSERT_TRUE(p.IsUpgrade());
+    ++callbacks;
+  });
+  p.Execute("GET / HTTP/1.0\r\n");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute("Connection: Upgrade\r\n");
+  p.Execute("Upgrade: websocket\r\n");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute("\r\n");
+  ASSERT_EQ(callbacks, 1);
+  ASSERT_FALSE(p.HasError());
+}
+
+TEST(HttpParserTest, Reset) {
+  HttpParser p{HttpParser::kRequest};
+  int callbacks = 0;
+  p.headersComplete.connect([&](bool) { ++callbacks; });
+  p.Execute("GET / HTTP/1.1\r\n");
+  ASSERT_EQ(callbacks, 0);
+  p.Execute("\r\n");
+  ASSERT_EQ(callbacks, 1);
+  p.Reset(HttpParser::kRequest);
+  p.Execute("GET / HTTP/1.1\r\n");
+  ASSERT_EQ(callbacks, 1);
+  p.Execute("\r\n");
+  ASSERT_EQ(callbacks, 2);
+  ASSERT_FALSE(p.HasError());
+}
+
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/HttpUtilTest.cpp b/wpiutil/src/test/native/cpp/HttpUtilTest.cpp
new file mode 100644
index 0000000..a83214d
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/HttpUtilTest.cpp
@@ -0,0 +1,105 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include "wpi/HttpUtil.h"  // NOLINT(build/include_order)
+
+#include "gtest/gtest.h"
+
+namespace wpi {
+
+TEST(HttpMultipartScannerTest, ExecuteExact) {
+  HttpMultipartScanner scanner("foo");
+  EXPECT_TRUE(scanner.Execute("abcdefg---\r\n--foo\r\n").empty());
+  EXPECT_TRUE(scanner.IsDone());
+  EXPECT_TRUE(scanner.GetSkipped().empty());
+}
+
+TEST(HttpMultipartScannerTest, ExecutePartial) {
+  HttpMultipartScanner scanner("foo");
+  EXPECT_TRUE(scanner.Execute("abcdefg--").empty());
+  EXPECT_FALSE(scanner.IsDone());
+  EXPECT_TRUE(scanner.Execute("-\r\n").empty());
+  EXPECT_FALSE(scanner.IsDone());
+  EXPECT_TRUE(scanner.Execute("--foo\r").empty());
+  EXPECT_FALSE(scanner.IsDone());
+  EXPECT_TRUE(scanner.Execute("\n").empty());
+  EXPECT_TRUE(scanner.IsDone());
+}
+
+TEST(HttpMultipartScannerTest, ExecuteTrailing) {
+  HttpMultipartScanner scanner("foo");
+  EXPECT_EQ(scanner.Execute("abcdefg---\r\n--foo\r\nxyz"), "xyz");
+}
+
+TEST(HttpMultipartScannerTest, ExecutePadding) {
+  HttpMultipartScanner scanner("foo");
+  EXPECT_EQ(scanner.Execute("abcdefg---\r\n--foo    \r\nxyz"), "xyz");
+  EXPECT_TRUE(scanner.IsDone());
+}
+
+TEST(HttpMultipartScannerTest, SaveSkipped) {
+  HttpMultipartScanner scanner("foo", true);
+  scanner.Execute("abcdefg---\r\n--foo\r\n");
+  EXPECT_EQ(scanner.GetSkipped(), "abcdefg---\r\n--foo\r\n");
+}
+
+TEST(HttpMultipartScannerTest, Reset) {
+  HttpMultipartScanner scanner("foo", true);
+
+  scanner.Execute("abcdefg---\r\n--foo\r\n");
+  EXPECT_TRUE(scanner.IsDone());
+  EXPECT_EQ(scanner.GetSkipped(), "abcdefg---\r\n--foo\r\n");
+
+  scanner.Reset(true);
+  EXPECT_FALSE(scanner.IsDone());
+  scanner.SetBoundary("bar");
+
+  scanner.Execute("--foo\r\n--bar\r\n");
+  EXPECT_TRUE(scanner.IsDone());
+  EXPECT_EQ(scanner.GetSkipped(), "--foo\r\n--bar\r\n");
+}
+
+TEST(HttpMultipartScannerTest, WithoutDashes) {
+  HttpMultipartScanner scanner("foo", true);
+
+  EXPECT_TRUE(scanner.Execute("--\r\nfoo\r\n").empty());
+  EXPECT_TRUE(scanner.IsDone());
+}
+
+TEST(HttpMultipartScannerTest, SeqDashesDashes) {
+  HttpMultipartScanner scanner("foo", true);
+  EXPECT_TRUE(scanner.Execute("\r\n--foo\r\n").empty());
+  EXPECT_TRUE(scanner.IsDone());
+  EXPECT_TRUE(scanner.Execute("\r\n--foo\r\n").empty());
+  EXPECT_TRUE(scanner.IsDone());
+}
+
+TEST(HttpMultipartScannerTest, SeqDashesNoDashes) {
+  HttpMultipartScanner scanner("foo", true);
+  EXPECT_TRUE(scanner.Execute("\r\n--foo\r\n").empty());
+  EXPECT_TRUE(scanner.IsDone());
+  EXPECT_TRUE(scanner.Execute("\r\nfoo\r\n").empty());
+  EXPECT_FALSE(scanner.IsDone());
+}
+
+TEST(HttpMultipartScannerTest, SeqNoDashesDashes) {
+  HttpMultipartScanner scanner("foo", true);
+  EXPECT_TRUE(scanner.Execute("\r\nfoo\r\n").empty());
+  EXPECT_TRUE(scanner.IsDone());
+  EXPECT_TRUE(scanner.Execute("\r\n--foo\r\n").empty());
+  EXPECT_FALSE(scanner.IsDone());
+}
+
+TEST(HttpMultipartScannerTest, SeqNoDashesNoDashes) {
+  HttpMultipartScanner scanner("foo", true);
+  EXPECT_TRUE(scanner.Execute("\r\nfoo\r\n").empty());
+  EXPECT_TRUE(scanner.IsDone());
+  EXPECT_TRUE(scanner.Execute("\r\nfoo\r\n").empty());
+  EXPECT_TRUE(scanner.IsDone());
+}
+
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/UidVectorTest.cpp b/wpiutil/src/test/native/cpp/UidVectorTest.cpp
new file mode 100644
index 0000000..e11c7b2
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/UidVectorTest.cpp
@@ -0,0 +1,48 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include "wpi/UidVector.h"  // NOLINT(build/include_order)
+
+#include "gtest/gtest.h"
+
+namespace wpi {
+
+TEST(UidVectorTest, Empty) {
+  UidVector<int, 4> v;
+  ASSERT_TRUE(v.empty());
+
+  v.emplace_back(1);
+  ASSERT_FALSE(v.empty());
+}
+
+TEST(UidVectorTest, Erase) {
+  UidVector<int, 4> v;
+  size_t uid = v.emplace_back(1);
+  v.erase(uid);
+  ASSERT_TRUE(v.empty());
+}
+
+TEST(UidVectorTest, Clear) {
+  UidVector<int, 4> v;
+  v.emplace_back(1);
+  v.emplace_back(2);
+  v.clear();
+  ASSERT_TRUE(v.empty());
+}
+
+TEST(UidVectorTest, Iterate) {
+  UidVector<int, 4> v;
+  v.emplace_back(2);
+  v.emplace_back(1);
+  std::vector<int> out;
+  for (auto&& val : v) out.push_back(val);
+  ASSERT_EQ(out.size(), 2u);
+  EXPECT_EQ(out[0], 2);
+  EXPECT_EQ(out[1], 1);
+}
+
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/UnitsTest.cpp b/wpiutil/src/test/native/cpp/UnitsTest.cpp
new file mode 100644
index 0000000..8e0823a
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/UnitsTest.cpp
@@ -0,0 +1,3379 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2019 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include <array>
+#include <chrono>
+#include <string>
+#include <type_traits>
+
+#include "gtest/gtest.h"
+#include "units/units.h"
+
+using namespace units;
+using namespace units::dimensionless;
+using namespace units::length;
+using namespace units::mass;
+using namespace units::angle;
+using namespace units::time;
+using namespace units::frequency;
+using namespace units::area;
+using namespace units::velocity;
+using namespace units::angular_velocity;
+using namespace units::temperature;
+using namespace units::luminous_intensity;
+using namespace units::solid_angle;
+using namespace units::frequency;
+using namespace units::acceleration;
+using namespace units::pressure;
+using namespace units::charge;
+using namespace units::energy;
+using namespace units::power;
+using namespace units::voltage;
+using namespace units::capacitance;
+using namespace units::impedance;
+using namespace units::conductance;
+using namespace units::magnetic_flux;
+using namespace units::magnetic_field_strength;
+using namespace units::inductance;
+using namespace units::luminous_flux;
+using namespace units::illuminance;
+using namespace units::radiation;
+using namespace units::torque;
+using namespace units::volume;
+using namespace units::density;
+using namespace units::concentration;
+using namespace units::data;
+using namespace units::data_transfer_rate;
+using namespace units::math;
+
+#if !defined(_MSC_VER) || _MSC_VER > 1800
+using namespace units::literals;
+#endif
+
+namespace {
+
+class TypeTraits : public ::testing::Test {
+ protected:
+  TypeTraits() {}
+  virtual ~TypeTraits() {}
+  virtual void SetUp() {}
+  virtual void TearDown() {}
+};
+
+class UnitManipulators : public ::testing::Test {
+ protected:
+  UnitManipulators() {}
+  virtual ~UnitManipulators() {}
+  virtual void SetUp() {}
+  virtual void TearDown() {}
+};
+
+class UnitContainer : public ::testing::Test {
+ protected:
+  UnitContainer() {}
+  virtual ~UnitContainer() {}
+  virtual void SetUp() {}
+  virtual void TearDown() {}
+};
+
+class UnitConversion : public ::testing::Test {
+ protected:
+  UnitConversion() {}
+  virtual ~UnitConversion() {}
+  virtual void SetUp() {}
+  virtual void TearDown() {}
+};
+
+class UnitMath : public ::testing::Test {
+ protected:
+  UnitMath() {}
+  virtual ~UnitMath() {}
+  virtual void SetUp() {}
+  virtual void TearDown() {}
+};
+
+class CompileTimeArithmetic : public ::testing::Test {
+ protected:
+  CompileTimeArithmetic() {}
+  virtual ~CompileTimeArithmetic() {}
+  virtual void SetUp() {}
+  virtual void TearDown() {}
+};
+
+class Constexpr : public ::testing::Test {
+ protected:
+  Constexpr() {}
+  virtual ~Constexpr() {}
+  virtual void SetUp() {}
+  virtual void TearDown() {}
+};
+
+class CaseStudies : public ::testing::Test {
+ protected:
+  CaseStudies() {}
+  virtual ~CaseStudies() {}
+  virtual void SetUp() {}
+  virtual void TearDown() {}
+
+  struct RightTriangle {
+    using a = unit_value_t<meters, 3>;
+    using b = unit_value_t<meters, 4>;
+    using c = unit_value_sqrt<
+        unit_value_add<unit_value_power<a, 2>, unit_value_power<b, 2>>>;
+  };
+};
+}  // namespace
+
+TEST_F(TypeTraits, isRatio) {
+  EXPECT_TRUE(traits::is_ratio<std::ratio<1>>::value);
+  EXPECT_FALSE(traits::is_ratio<double>::value);
+}
+
+TEST_F(TypeTraits, ratio_sqrt) {
+  using rt2 = ratio_sqrt<std::ratio<2>>;
+  EXPECT_LT(std::abs(std::sqrt(2 / static_cast<double>(1)) -
+                     rt2::num / static_cast<double>(rt2::den)),
+            5e-9);
+
+  using rt4 = ratio_sqrt<std::ratio<4>>;
+  EXPECT_LT(std::abs(std::sqrt(4 / static_cast<double>(1)) -
+                     rt4::num / static_cast<double>(rt4::den)),
+            5e-9);
+
+  using rt10 = ratio_sqrt<std::ratio<10>>;
+  EXPECT_LT(std::abs(std::sqrt(10 / static_cast<double>(1)) -
+                     rt10::num / static_cast<double>(rt10::den)),
+            5e-9);
+
+  using rt30 = ratio_sqrt<std::ratio<30>>;
+  EXPECT_LT(std::abs(std::sqrt(30 / static_cast<double>(1)) -
+                     rt30::num / static_cast<double>(rt30::den)),
+            5e-9);
+
+  using rt61 = ratio_sqrt<std::ratio<61>>;
+  EXPECT_LT(std::abs(std::sqrt(61 / static_cast<double>(1)) -
+                     rt61::num / static_cast<double>(rt61::den)),
+            5e-9);
+
+  using rt100 = ratio_sqrt<std::ratio<100>>;
+  EXPECT_LT(std::abs(std::sqrt(100 / static_cast<double>(1)) -
+                     rt100::num / static_cast<double>(rt100::den)),
+            5e-9);
+
+  using rt1000 = ratio_sqrt<std::ratio<1000>>;
+  EXPECT_LT(std::abs(std::sqrt(1000 / static_cast<double>(1)) -
+                     rt1000::num / static_cast<double>(rt1000::den)),
+            5e-9);
+
+  using rt10000 = ratio_sqrt<std::ratio<10000>>;
+  EXPECT_LT(std::abs(std::sqrt(10000 / static_cast<double>(1)) -
+                     rt10000::num / static_cast<double>(rt10000::den)),
+            5e-9);
+}
+
+TEST_F(TypeTraits, is_unit) {
+  EXPECT_FALSE(traits::is_unit<std::ratio<1>>::value);
+  EXPECT_FALSE(traits::is_unit<double>::value);
+  EXPECT_TRUE(traits::is_unit<meters>::value);
+  EXPECT_TRUE(traits::is_unit<feet>::value);
+  EXPECT_TRUE(traits::is_unit<degrees_squared>::value);
+  EXPECT_FALSE(traits::is_unit<meter_t>::value);
+}
+
+TEST_F(TypeTraits, is_unit_t) {
+  EXPECT_FALSE(traits::is_unit_t<std::ratio<1>>::value);
+  EXPECT_FALSE(traits::is_unit_t<double>::value);
+  EXPECT_FALSE(traits::is_unit_t<meters>::value);
+  EXPECT_FALSE(traits::is_unit_t<feet>::value);
+  EXPECT_FALSE(traits::is_unit_t<degrees_squared>::value);
+  EXPECT_TRUE(traits::is_unit_t<meter_t>::value);
+}
+
+TEST_F(TypeTraits, unit_traits) {
+  EXPECT_TRUE(
+      (std::is_same<void,
+                    traits::unit_traits<double>::conversion_ratio>::value));
+  EXPECT_FALSE(
+      (std::is_same<void,
+                    traits::unit_traits<meters>::conversion_ratio>::value));
+}
+
+TEST_F(TypeTraits, unit_t_traits) {
+  EXPECT_TRUE(
+      (std::is_same<void,
+                    traits::unit_t_traits<double>::underlying_type>::value));
+  EXPECT_TRUE(
+      (std::is_same<UNIT_LIB_DEFAULT_TYPE,
+                    traits::unit_t_traits<meter_t>::underlying_type>::value));
+  EXPECT_TRUE(
+      (std::is_same<void, traits::unit_t_traits<double>::value_type>::value));
+  EXPECT_TRUE(
+      (std::is_same<UNIT_LIB_DEFAULT_TYPE,
+                    traits::unit_t_traits<meter_t>::value_type>::value));
+}
+
+TEST_F(TypeTraits, all_true) {
+  EXPECT_TRUE(all_true<true>::type::value);
+  EXPECT_TRUE((all_true<true, true>::type::value));
+  EXPECT_TRUE((all_true<true, true, true>::type::value));
+  EXPECT_FALSE(all_true<false>::type::value);
+  EXPECT_FALSE((all_true<true, false>::type::value));
+  EXPECT_FALSE((all_true<true, true, false>::type::value));
+  EXPECT_FALSE((all_true<false, false, false>::type::value));
+}
+
+TEST_F(TypeTraits, is_convertible_unit) {
+  EXPECT_TRUE((traits::is_convertible_unit<meters, meters>::value));
+  EXPECT_TRUE((traits::is_convertible_unit<meters, astronicalUnits>::value));
+  EXPECT_TRUE((traits::is_convertible_unit<meters, parsecs>::value));
+
+  EXPECT_TRUE((traits::is_convertible_unit<meters, meters>::value));
+  EXPECT_TRUE((traits::is_convertible_unit<astronicalUnits, meters>::value));
+  EXPECT_TRUE((traits::is_convertible_unit<parsecs, meters>::value));
+  EXPECT_TRUE((traits::is_convertible_unit<years, weeks>::value));
+
+  EXPECT_FALSE((traits::is_convertible_unit<meters, seconds>::value));
+  EXPECT_FALSE((traits::is_convertible_unit<seconds, meters>::value));
+  EXPECT_FALSE((traits::is_convertible_unit<years, meters>::value));
+}
+
+TEST_F(TypeTraits, inverse) {
+  double test;
+
+  using htz = inverse<seconds>;
+  bool shouldBeTrue = std::is_same<htz, hertz>::value;
+  EXPECT_TRUE(shouldBeTrue);
+
+  test = convert<inverse<celsius>, inverse<fahrenheit>>(1.0);
+  EXPECT_NEAR(5.0 / 9.0, test, 5.0e-5);
+
+  test = convert<inverse<kelvin>, inverse<fahrenheit>>(6.0);
+  EXPECT_NEAR(10.0 / 3.0, test, 5.0e-5);
+}
+
+TEST_F(TypeTraits, base_unit_of) {
+  using base = traits::base_unit_of<years>;
+  bool shouldBeTrue = std::is_same<base, category::time_unit>::value;
+
+  EXPECT_TRUE(shouldBeTrue);
+}
+
+TEST_F(TypeTraits, has_linear_scale) {
+  EXPECT_TRUE((traits::has_linear_scale<scalar_t>::value));
+  EXPECT_TRUE((traits::has_linear_scale<meter_t>::value));
+  EXPECT_TRUE((traits::has_linear_scale<foot_t>::value));
+  EXPECT_TRUE((traits::has_linear_scale<watt_t, scalar_t>::value));
+  EXPECT_TRUE((traits::has_linear_scale<scalar_t, meter_t>::value));
+  EXPECT_TRUE((traits::has_linear_scale<meters_per_second_t>::value));
+  EXPECT_FALSE((traits::has_linear_scale<dB_t>::value));
+  EXPECT_FALSE((traits::has_linear_scale<dB_t, meters_per_second_t>::value));
+}
+
+TEST_F(TypeTraits, has_decibel_scale) {
+  EXPECT_FALSE((traits::has_decibel_scale<scalar_t>::value));
+  EXPECT_FALSE((traits::has_decibel_scale<meter_t>::value));
+  EXPECT_FALSE((traits::has_decibel_scale<foot_t>::value));
+  EXPECT_TRUE((traits::has_decibel_scale<dB_t>::value));
+  EXPECT_TRUE((traits::has_decibel_scale<dBW_t>::value));
+
+  EXPECT_TRUE((traits::has_decibel_scale<dBW_t, dB_t>::value));
+  EXPECT_TRUE((traits::has_decibel_scale<dBW_t, dBm_t>::value));
+  EXPECT_TRUE((traits::has_decibel_scale<dB_t, dB_t>::value));
+  EXPECT_TRUE((traits::has_decibel_scale<dB_t, dB_t, dB_t>::value));
+  EXPECT_FALSE((traits::has_decibel_scale<dB_t, dB_t, meter_t>::value));
+  EXPECT_FALSE((traits::has_decibel_scale<meter_t, dB_t>::value));
+}
+
+TEST_F(TypeTraits, is_same_scale) {
+  EXPECT_TRUE((traits::is_same_scale<scalar_t, dimensionless_t>::value));
+  EXPECT_TRUE((traits::is_same_scale<dB_t, dBW_t>::value));
+  EXPECT_FALSE((traits::is_same_scale<dB_t, scalar_t>::value));
+}
+
+TEST_F(TypeTraits, is_dimensionless_unit) {
+  EXPECT_TRUE((traits::is_dimensionless_unit<scalar_t>::value));
+  EXPECT_TRUE((traits::is_dimensionless_unit<const scalar_t>::value));
+  EXPECT_TRUE((traits::is_dimensionless_unit<const scalar_t&>::value));
+  EXPECT_TRUE((traits::is_dimensionless_unit<dimensionless_t>::value));
+  EXPECT_TRUE((traits::is_dimensionless_unit<dB_t>::value));
+  EXPECT_TRUE((traits::is_dimensionless_unit<dB_t, scalar_t>::value));
+  EXPECT_TRUE((traits::is_dimensionless_unit<ppm_t>::value));
+  EXPECT_FALSE((traits::is_dimensionless_unit<meter_t>::value));
+  EXPECT_FALSE((traits::is_dimensionless_unit<dBW_t>::value));
+  EXPECT_FALSE((traits::is_dimensionless_unit<dBW_t, scalar_t>::value));
+}
+
+TEST_F(TypeTraits, is_length_unit) {
+  EXPECT_TRUE((traits::is_length_unit<meter>::value));
+  EXPECT_TRUE((traits::is_length_unit<cubit>::value));
+  EXPECT_FALSE((traits::is_length_unit<year>::value));
+  EXPECT_FALSE((traits::is_length_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_length_unit<meter_t>::value));
+  EXPECT_TRUE((traits::is_length_unit<const meter_t>::value));
+  EXPECT_TRUE((traits::is_length_unit<const meter_t&>::value));
+  EXPECT_TRUE((traits::is_length_unit<cubit_t>::value));
+  EXPECT_FALSE((traits::is_length_unit<year_t>::value));
+  EXPECT_TRUE((traits::is_length_unit<meter_t, cubit_t>::value));
+  EXPECT_FALSE((traits::is_length_unit<meter_t, year_t>::value));
+}
+
+TEST_F(TypeTraits, is_mass_unit) {
+  EXPECT_TRUE((traits::is_mass_unit<kilogram>::value));
+  EXPECT_TRUE((traits::is_mass_unit<stone>::value));
+  EXPECT_FALSE((traits::is_mass_unit<meter>::value));
+  EXPECT_FALSE((traits::is_mass_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_mass_unit<kilogram_t>::value));
+  EXPECT_TRUE((traits::is_mass_unit<const kilogram_t>::value));
+  EXPECT_TRUE((traits::is_mass_unit<const kilogram_t&>::value));
+  EXPECT_TRUE((traits::is_mass_unit<stone_t>::value));
+  EXPECT_FALSE((traits::is_mass_unit<meter_t>::value));
+  EXPECT_TRUE((traits::is_mass_unit<kilogram_t, stone_t>::value));
+  EXPECT_FALSE((traits::is_mass_unit<kilogram_t, meter_t>::value));
+}
+
+TEST_F(TypeTraits, is_time_unit) {
+  EXPECT_TRUE((traits::is_time_unit<second>::value));
+  EXPECT_TRUE((traits::is_time_unit<year>::value));
+  EXPECT_FALSE((traits::is_time_unit<meter>::value));
+  EXPECT_FALSE((traits::is_time_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_time_unit<second_t>::value));
+  EXPECT_TRUE((traits::is_time_unit<const second_t>::value));
+  EXPECT_TRUE((traits::is_time_unit<const second_t&>::value));
+  EXPECT_TRUE((traits::is_time_unit<year_t>::value));
+  EXPECT_FALSE((traits::is_time_unit<meter_t>::value));
+  EXPECT_TRUE((traits::is_time_unit<second_t, year_t>::value));
+  EXPECT_FALSE((traits::is_time_unit<second_t, meter_t>::value));
+}
+
+TEST_F(TypeTraits, is_angle_unit) {
+  EXPECT_TRUE((traits::is_angle_unit<angle::radian>::value));
+  EXPECT_TRUE((traits::is_angle_unit<angle::degree>::value));
+  EXPECT_FALSE((traits::is_angle_unit<watt>::value));
+  EXPECT_FALSE((traits::is_angle_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_angle_unit<angle::radian_t>::value));
+  EXPECT_TRUE((traits::is_angle_unit<const angle::radian_t>::value));
+  EXPECT_TRUE((traits::is_angle_unit<const angle::radian_t&>::value));
+  EXPECT_TRUE((traits::is_angle_unit<angle::degree_t>::value));
+  EXPECT_FALSE((traits::is_angle_unit<watt_t>::value));
+  EXPECT_TRUE((traits::is_angle_unit<angle::radian_t, angle::degree_t>::value));
+  EXPECT_FALSE((traits::is_angle_unit<angle::radian_t, watt_t>::value));
+}
+
+TEST_F(TypeTraits, is_current_unit) {
+  EXPECT_TRUE((traits::is_current_unit<current::ampere>::value));
+  EXPECT_FALSE((traits::is_current_unit<volt>::value));
+  EXPECT_FALSE((traits::is_current_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_current_unit<current::ampere_t>::value));
+  EXPECT_TRUE((traits::is_current_unit<const current::ampere_t>::value));
+  EXPECT_TRUE((traits::is_current_unit<const current::ampere_t&>::value));
+  EXPECT_FALSE((traits::is_current_unit<volt_t>::value));
+  EXPECT_TRUE((traits::is_current_unit<current::ampere_t,
+                                       current::milliampere_t>::value));
+  EXPECT_FALSE((traits::is_current_unit<current::ampere_t, volt_t>::value));
+}
+
+TEST_F(TypeTraits, is_temperature_unit) {
+  EXPECT_TRUE((traits::is_temperature_unit<fahrenheit>::value));
+  EXPECT_TRUE((traits::is_temperature_unit<kelvin>::value));
+  EXPECT_FALSE((traits::is_temperature_unit<cubit>::value));
+  EXPECT_FALSE((traits::is_temperature_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_temperature_unit<fahrenheit_t>::value));
+  EXPECT_TRUE((traits::is_temperature_unit<const fahrenheit_t>::value));
+  EXPECT_TRUE((traits::is_temperature_unit<const fahrenheit_t&>::value));
+  EXPECT_TRUE((traits::is_temperature_unit<kelvin_t>::value));
+  EXPECT_FALSE((traits::is_temperature_unit<cubit_t>::value));
+  EXPECT_TRUE((traits::is_temperature_unit<fahrenheit_t, kelvin_t>::value));
+  EXPECT_FALSE((traits::is_temperature_unit<cubit_t, fahrenheit_t>::value));
+}
+
+TEST_F(TypeTraits, is_substance_unit) {
+  EXPECT_TRUE((traits::is_substance_unit<substance::mol>::value));
+  EXPECT_FALSE((traits::is_substance_unit<year>::value));
+  EXPECT_FALSE((traits::is_substance_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_substance_unit<substance::mole_t>::value));
+  EXPECT_TRUE((traits::is_substance_unit<const substance::mole_t>::value));
+  EXPECT_TRUE((traits::is_substance_unit<const substance::mole_t&>::value));
+  EXPECT_FALSE((traits::is_substance_unit<year_t>::value));
+  EXPECT_TRUE(
+      (traits::is_substance_unit<substance::mole_t, substance::mole_t>::value));
+  EXPECT_FALSE((traits::is_substance_unit<year_t, substance::mole_t>::value));
+}
+
+TEST_F(TypeTraits, is_luminous_intensity_unit) {
+  EXPECT_TRUE((traits::is_luminous_intensity_unit<candela>::value));
+  EXPECT_FALSE(
+      (traits::is_luminous_intensity_unit<units::radiation::rad>::value));
+  EXPECT_FALSE((traits::is_luminous_intensity_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_luminous_intensity_unit<candela_t>::value));
+  EXPECT_TRUE((traits::is_luminous_intensity_unit<const candela_t>::value));
+  EXPECT_TRUE((traits::is_luminous_intensity_unit<const candela_t&>::value));
+  EXPECT_FALSE((traits::is_luminous_intensity_unit<rad_t>::value));
+  EXPECT_TRUE(
+      (traits::is_luminous_intensity_unit<candela_t, candela_t>::value));
+  EXPECT_FALSE((traits::is_luminous_intensity_unit<rad_t, candela_t>::value));
+}
+
+TEST_F(TypeTraits, is_solid_angle_unit) {
+  EXPECT_TRUE((traits::is_solid_angle_unit<steradian>::value));
+  EXPECT_TRUE((traits::is_solid_angle_unit<degree_squared>::value));
+  EXPECT_FALSE((traits::is_solid_angle_unit<angle::degree>::value));
+  EXPECT_FALSE((traits::is_solid_angle_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_solid_angle_unit<steradian_t>::value));
+  EXPECT_TRUE((traits::is_solid_angle_unit<const steradian_t>::value));
+  EXPECT_TRUE((traits::is_solid_angle_unit<const degree_squared_t&>::value));
+  EXPECT_FALSE((traits::is_solid_angle_unit<angle::degree_t>::value));
+  EXPECT_TRUE(
+      (traits::is_solid_angle_unit<degree_squared_t, steradian_t>::value));
+  EXPECT_FALSE(
+      (traits::is_solid_angle_unit<angle::degree_t, steradian_t>::value));
+}
+
+TEST_F(TypeTraits, is_frequency_unit) {
+  EXPECT_TRUE((traits::is_frequency_unit<hertz>::value));
+  EXPECT_FALSE((traits::is_frequency_unit<second>::value));
+  EXPECT_FALSE((traits::is_frequency_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_frequency_unit<hertz_t>::value));
+  EXPECT_TRUE((traits::is_frequency_unit<const hertz_t>::value));
+  EXPECT_TRUE((traits::is_frequency_unit<const hertz_t&>::value));
+  EXPECT_FALSE((traits::is_frequency_unit<second_t>::value));
+  EXPECT_TRUE((traits::is_frequency_unit<const hertz_t&, gigahertz_t>::value));
+  EXPECT_FALSE((traits::is_frequency_unit<second_t, hertz_t>::value));
+}
+
+TEST_F(TypeTraits, is_velocity_unit) {
+  EXPECT_TRUE((traits::is_velocity_unit<meters_per_second>::value));
+  EXPECT_TRUE((traits::is_velocity_unit<miles_per_hour>::value));
+  EXPECT_FALSE((traits::is_velocity_unit<meters_per_second_squared>::value));
+  EXPECT_FALSE((traits::is_velocity_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_velocity_unit<meters_per_second_t>::value));
+  EXPECT_TRUE((traits::is_velocity_unit<const meters_per_second_t>::value));
+  EXPECT_TRUE((traits::is_velocity_unit<const meters_per_second_t&>::value));
+  EXPECT_TRUE((traits::is_velocity_unit<miles_per_hour_t>::value));
+  EXPECT_FALSE((traits::is_velocity_unit<meters_per_second_squared_t>::value));
+  EXPECT_TRUE(
+      (traits::is_velocity_unit<miles_per_hour_t, meters_per_second_t>::value));
+  EXPECT_FALSE((traits::is_velocity_unit<meters_per_second_squared_t,
+                                         meters_per_second_t>::value));
+}
+
+TEST_F(TypeTraits, is_acceleration_unit) {
+  EXPECT_TRUE((traits::is_acceleration_unit<meters_per_second_squared>::value));
+  EXPECT_TRUE(
+      (traits::is_acceleration_unit<acceleration::standard_gravity>::value));
+  EXPECT_FALSE((traits::is_acceleration_unit<inch>::value));
+  EXPECT_FALSE((traits::is_acceleration_unit<double>::value));
+
+  EXPECT_TRUE(
+      (traits::is_acceleration_unit<meters_per_second_squared_t>::value));
+  EXPECT_TRUE(
+      (traits::is_acceleration_unit<const meters_per_second_squared_t>::value));
+  EXPECT_TRUE((
+      traits::is_acceleration_unit<const meters_per_second_squared_t&>::value));
+  EXPECT_TRUE((traits::is_acceleration_unit<standard_gravity_t>::value));
+  EXPECT_FALSE((traits::is_acceleration_unit<inch_t>::value));
+  EXPECT_TRUE(
+      (traits::is_acceleration_unit<standard_gravity_t,
+                                    meters_per_second_squared_t>::value));
+  EXPECT_FALSE(
+      (traits::is_acceleration_unit<inch_t,
+                                    meters_per_second_squared_t>::value));
+}
+
+TEST_F(TypeTraits, is_force_unit) {
+  EXPECT_TRUE((traits::is_force_unit<units::force::newton>::value));
+  EXPECT_TRUE((traits::is_force_unit<units::force::dynes>::value));
+  EXPECT_FALSE((traits::is_force_unit<meter>::value));
+  EXPECT_FALSE((traits::is_force_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_force_unit<units::force::newton_t>::value));
+  EXPECT_TRUE((traits::is_force_unit<const units::force::newton_t>::value));
+  EXPECT_TRUE((traits::is_force_unit<const units::force::newton_t&>::value));
+  EXPECT_TRUE((traits::is_force_unit<units::force::dyne_t>::value));
+  EXPECT_FALSE((traits::is_force_unit<watt_t>::value));
+  EXPECT_TRUE((traits::is_force_unit<units::force::dyne_t,
+                                     units::force::newton_t>::value));
+  EXPECT_FALSE((traits::is_force_unit<watt_t, units::force::newton_t>::value));
+}
+
+TEST_F(TypeTraits, is_pressure_unit) {
+  EXPECT_TRUE((traits::is_pressure_unit<pressure::pascals>::value));
+  EXPECT_TRUE((traits::is_pressure_unit<atmosphere>::value));
+  EXPECT_FALSE((traits::is_pressure_unit<year>::value));
+  EXPECT_FALSE((traits::is_pressure_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_pressure_unit<pascal_t>::value));
+  EXPECT_TRUE((traits::is_pressure_unit<const pascal_t>::value));
+  EXPECT_TRUE((traits::is_pressure_unit<const pascal_t&>::value));
+  EXPECT_TRUE((traits::is_pressure_unit<atmosphere_t>::value));
+  EXPECT_FALSE((traits::is_pressure_unit<year_t>::value));
+  EXPECT_TRUE(
+      (traits::is_pressure_unit<atmosphere_t, pressure::pascal_t>::value));
+  EXPECT_FALSE((traits::is_pressure_unit<year_t, pressure::pascal_t>::value));
+}
+
+TEST_F(TypeTraits, is_charge_unit) {
+  EXPECT_TRUE((traits::is_charge_unit<coulomb>::value));
+  EXPECT_FALSE((traits::is_charge_unit<watt>::value));
+  EXPECT_FALSE((traits::is_charge_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_charge_unit<coulomb_t>::value));
+  EXPECT_TRUE((traits::is_charge_unit<const coulomb_t>::value));
+  EXPECT_TRUE((traits::is_charge_unit<const coulomb_t&>::value));
+  EXPECT_FALSE((traits::is_charge_unit<watt_t>::value));
+  EXPECT_TRUE((traits::is_charge_unit<const coulomb_t&, coulomb_t>::value));
+  EXPECT_FALSE((traits::is_charge_unit<watt_t, coulomb_t>::value));
+}
+
+TEST_F(TypeTraits, is_energy_unit) {
+  EXPECT_TRUE((traits::is_energy_unit<joule>::value));
+  EXPECT_TRUE((traits::is_energy_unit<calorie>::value));
+  EXPECT_FALSE((traits::is_energy_unit<watt>::value));
+  EXPECT_FALSE((traits::is_energy_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_energy_unit<joule_t>::value));
+  EXPECT_TRUE((traits::is_energy_unit<const joule_t>::value));
+  EXPECT_TRUE((traits::is_energy_unit<const joule_t&>::value));
+  EXPECT_TRUE((traits::is_energy_unit<calorie_t>::value));
+  EXPECT_FALSE((traits::is_energy_unit<watt_t>::value));
+  EXPECT_TRUE((traits::is_energy_unit<calorie_t, joule_t>::value));
+  EXPECT_FALSE((traits::is_energy_unit<watt_t, joule_t>::value));
+}
+
+TEST_F(TypeTraits, is_power_unit) {
+  EXPECT_TRUE((traits::is_power_unit<watt>::value));
+  EXPECT_FALSE((traits::is_power_unit<henry>::value));
+  EXPECT_FALSE((traits::is_power_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_power_unit<watt_t>::value));
+  EXPECT_TRUE((traits::is_power_unit<const watt_t>::value));
+  EXPECT_TRUE((traits::is_power_unit<const watt_t&>::value));
+  EXPECT_FALSE((traits::is_power_unit<henry_t>::value));
+  EXPECT_TRUE((traits::is_power_unit<const watt_t&, watt_t>::value));
+  EXPECT_FALSE((traits::is_power_unit<henry_t, watt_t>::value));
+}
+
+TEST_F(TypeTraits, is_voltage_unit) {
+  EXPECT_TRUE((traits::is_voltage_unit<volt>::value));
+  EXPECT_FALSE((traits::is_voltage_unit<henry>::value));
+  EXPECT_FALSE((traits::is_voltage_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_voltage_unit<volt_t>::value));
+  EXPECT_TRUE((traits::is_voltage_unit<const volt_t>::value));
+  EXPECT_TRUE((traits::is_voltage_unit<const volt_t&>::value));
+  EXPECT_FALSE((traits::is_voltage_unit<henry_t>::value));
+  EXPECT_TRUE((traits::is_voltage_unit<const volt_t&, volt_t>::value));
+  EXPECT_FALSE((traits::is_voltage_unit<henry_t, volt_t>::value));
+}
+
+TEST_F(TypeTraits, is_capacitance_unit) {
+  EXPECT_TRUE((traits::is_capacitance_unit<farad>::value));
+  EXPECT_FALSE((traits::is_capacitance_unit<ohm>::value));
+  EXPECT_FALSE((traits::is_capacitance_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_capacitance_unit<farad_t>::value));
+  EXPECT_TRUE((traits::is_capacitance_unit<const farad_t>::value));
+  EXPECT_TRUE((traits::is_capacitance_unit<const farad_t&>::value));
+  EXPECT_FALSE((traits::is_capacitance_unit<ohm_t>::value));
+  EXPECT_TRUE(
+      (traits::is_capacitance_unit<const farad_t&, millifarad_t>::value));
+  EXPECT_FALSE((traits::is_capacitance_unit<ohm_t, farad_t>::value));
+}
+
+TEST_F(TypeTraits, is_impedance_unit) {
+  EXPECT_TRUE((traits::is_impedance_unit<ohm>::value));
+  EXPECT_FALSE((traits::is_impedance_unit<farad>::value));
+  EXPECT_FALSE((traits::is_impedance_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_impedance_unit<ohm_t>::value));
+  EXPECT_TRUE((traits::is_impedance_unit<const ohm_t>::value));
+  EXPECT_TRUE((traits::is_impedance_unit<const ohm_t&>::value));
+  EXPECT_FALSE((traits::is_impedance_unit<farad_t>::value));
+  EXPECT_TRUE((traits::is_impedance_unit<const ohm_t&, milliohm_t>::value));
+  EXPECT_FALSE((traits::is_impedance_unit<farad_t, ohm_t>::value));
+}
+
+TEST_F(TypeTraits, is_conductance_unit) {
+  EXPECT_TRUE((traits::is_conductance_unit<siemens>::value));
+  EXPECT_FALSE((traits::is_conductance_unit<volt>::value));
+  EXPECT_FALSE((traits::is_conductance_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_conductance_unit<siemens_t>::value));
+  EXPECT_TRUE((traits::is_conductance_unit<const siemens_t>::value));
+  EXPECT_TRUE((traits::is_conductance_unit<const siemens_t&>::value));
+  EXPECT_FALSE((traits::is_conductance_unit<volt_t>::value));
+  EXPECT_TRUE(
+      (traits::is_conductance_unit<const siemens_t&, millisiemens_t>::value));
+  EXPECT_FALSE((traits::is_conductance_unit<volt_t, siemens_t>::value));
+}
+
+TEST_F(TypeTraits, is_magnetic_flux_unit) {
+  EXPECT_TRUE((traits::is_magnetic_flux_unit<weber>::value));
+  EXPECT_TRUE((traits::is_magnetic_flux_unit<maxwell>::value));
+  EXPECT_FALSE((traits::is_magnetic_flux_unit<inch>::value));
+  EXPECT_FALSE((traits::is_magnetic_flux_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_magnetic_flux_unit<weber_t>::value));
+  EXPECT_TRUE((traits::is_magnetic_flux_unit<const weber_t>::value));
+  EXPECT_TRUE((traits::is_magnetic_flux_unit<const weber_t&>::value));
+  EXPECT_TRUE((traits::is_magnetic_flux_unit<maxwell_t>::value));
+  EXPECT_FALSE((traits::is_magnetic_flux_unit<inch_t>::value));
+  EXPECT_TRUE((traits::is_magnetic_flux_unit<maxwell_t, weber_t>::value));
+  EXPECT_FALSE((traits::is_magnetic_flux_unit<inch_t, weber_t>::value));
+}
+
+TEST_F(TypeTraits, is_magnetic_field_strength_unit) {
+  EXPECT_TRUE((traits::is_magnetic_field_strength_unit<
+               units::magnetic_field_strength::tesla>::value));
+  EXPECT_TRUE((traits::is_magnetic_field_strength_unit<gauss>::value));
+  EXPECT_FALSE((traits::is_magnetic_field_strength_unit<volt>::value));
+  EXPECT_FALSE((traits::is_magnetic_field_strength_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_magnetic_field_strength_unit<tesla_t>::value));
+  EXPECT_TRUE((traits::is_magnetic_field_strength_unit<const tesla_t>::value));
+  EXPECT_TRUE((traits::is_magnetic_field_strength_unit<const tesla_t&>::value));
+  EXPECT_TRUE((traits::is_magnetic_field_strength_unit<gauss_t>::value));
+  EXPECT_FALSE((traits::is_magnetic_field_strength_unit<volt_t>::value));
+  EXPECT_TRUE(
+      (traits::is_magnetic_field_strength_unit<gauss_t, tesla_t>::value));
+  EXPECT_FALSE(
+      (traits::is_magnetic_field_strength_unit<volt_t, tesla_t>::value));
+}
+
+TEST_F(TypeTraits, is_inductance_unit) {
+  EXPECT_TRUE((traits::is_inductance_unit<henry>::value));
+  EXPECT_FALSE((traits::is_inductance_unit<farad>::value));
+  EXPECT_FALSE((traits::is_inductance_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_inductance_unit<henry_t>::value));
+  EXPECT_TRUE((traits::is_inductance_unit<const henry_t>::value));
+  EXPECT_TRUE((traits::is_inductance_unit<const henry_t&>::value));
+  EXPECT_FALSE((traits::is_inductance_unit<farad_t>::value));
+  EXPECT_TRUE(
+      (traits::is_inductance_unit<const henry_t&, millihenry_t>::value));
+  EXPECT_FALSE((traits::is_inductance_unit<farad_t, henry_t>::value));
+}
+
+TEST_F(TypeTraits, is_luminous_flux_unit) {
+  EXPECT_TRUE((traits::is_luminous_flux_unit<lumen>::value));
+  EXPECT_FALSE((traits::is_luminous_flux_unit<pound>::value));
+  EXPECT_FALSE((traits::is_luminous_flux_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_luminous_flux_unit<lumen_t>::value));
+  EXPECT_TRUE((traits::is_luminous_flux_unit<const lumen_t>::value));
+  EXPECT_TRUE((traits::is_luminous_flux_unit<const lumen_t&>::value));
+  EXPECT_FALSE((traits::is_luminous_flux_unit<pound_t>::value));
+  EXPECT_TRUE(
+      (traits::is_luminous_flux_unit<const lumen_t&, millilumen_t>::value));
+  EXPECT_FALSE((traits::is_luminous_flux_unit<pound_t, lumen_t>::value));
+}
+
+TEST_F(TypeTraits, is_illuminance_unit) {
+  EXPECT_TRUE((traits::is_illuminance_unit<illuminance::footcandle>::value));
+  EXPECT_TRUE((traits::is_illuminance_unit<illuminance::lux>::value));
+  EXPECT_FALSE((traits::is_illuminance_unit<meter>::value));
+  EXPECT_FALSE((traits::is_illuminance_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_illuminance_unit<footcandle_t>::value));
+  EXPECT_TRUE((traits::is_illuminance_unit<const footcandle_t>::value));
+  EXPECT_TRUE((traits::is_illuminance_unit<const footcandle_t&>::value));
+  EXPECT_TRUE((traits::is_illuminance_unit<lux_t>::value));
+  EXPECT_FALSE((traits::is_illuminance_unit<meter_t>::value));
+  EXPECT_TRUE((traits::is_illuminance_unit<lux_t, footcandle_t>::value));
+  EXPECT_FALSE((traits::is_illuminance_unit<meter_t, footcandle_t>::value));
+}
+
+TEST_F(TypeTraits, is_radioactivity_unit) {
+  EXPECT_TRUE((traits::is_radioactivity_unit<becquerel>::value));
+  EXPECT_FALSE((traits::is_radioactivity_unit<year>::value));
+  EXPECT_FALSE((traits::is_radioactivity_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_radioactivity_unit<becquerel_t>::value));
+  EXPECT_TRUE((traits::is_radioactivity_unit<const becquerel_t>::value));
+  EXPECT_TRUE((traits::is_radioactivity_unit<const becquerel_t&>::value));
+  EXPECT_FALSE((traits::is_radioactivity_unit<year_t>::value));
+  EXPECT_TRUE((traits::is_radioactivity_unit<const becquerel_t&,
+                                             millibecquerel_t>::value));
+  EXPECT_FALSE((traits::is_radioactivity_unit<year_t, becquerel_t>::value));
+}
+
+TEST_F(TypeTraits, is_torque_unit) {
+  EXPECT_TRUE((traits::is_torque_unit<torque::newton_meter>::value));
+  EXPECT_TRUE((traits::is_torque_unit<torque::foot_pound>::value));
+  EXPECT_FALSE((traits::is_torque_unit<volume::cubic_meter>::value));
+  EXPECT_FALSE((traits::is_torque_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_torque_unit<torque::newton_meter_t>::value));
+  EXPECT_TRUE((traits::is_torque_unit<const torque::newton_meter_t>::value));
+  EXPECT_TRUE((traits::is_torque_unit<const torque::newton_meter_t&>::value));
+  EXPECT_TRUE((traits::is_torque_unit<torque::foot_pound_t>::value));
+  EXPECT_FALSE((traits::is_torque_unit<volume::cubic_meter_t>::value));
+  EXPECT_TRUE((traits::is_torque_unit<torque::foot_pound_t,
+                                      torque::newton_meter_t>::value));
+  EXPECT_FALSE((traits::is_torque_unit<volume::cubic_meter_t,
+                                       torque::newton_meter_t>::value));
+}
+
+TEST_F(TypeTraits, is_area_unit) {
+  EXPECT_TRUE((traits::is_area_unit<square_meter>::value));
+  EXPECT_TRUE((traits::is_area_unit<hectare>::value));
+  EXPECT_FALSE((traits::is_area_unit<astronicalUnit>::value));
+  EXPECT_FALSE((traits::is_area_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_area_unit<square_meter_t>::value));
+  EXPECT_TRUE((traits::is_area_unit<const square_meter_t>::value));
+  EXPECT_TRUE((traits::is_area_unit<const square_meter_t&>::value));
+  EXPECT_TRUE((traits::is_area_unit<hectare_t>::value));
+  EXPECT_FALSE((traits::is_area_unit<astronicalUnit_t>::value));
+  EXPECT_TRUE((traits::is_area_unit<hectare_t, square_meter_t>::value));
+  EXPECT_FALSE((traits::is_area_unit<astronicalUnit_t, square_meter_t>::value));
+}
+
+TEST_F(TypeTraits, is_volume_unit) {
+  EXPECT_TRUE((traits::is_volume_unit<cubic_meter>::value));
+  EXPECT_TRUE((traits::is_volume_unit<cubic_foot>::value));
+  EXPECT_FALSE((traits::is_volume_unit<square_feet>::value));
+  EXPECT_FALSE((traits::is_volume_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_volume_unit<cubic_meter_t>::value));
+  EXPECT_TRUE((traits::is_volume_unit<const cubic_meter_t>::value));
+  EXPECT_TRUE((traits::is_volume_unit<const cubic_meter_t&>::value));
+  EXPECT_TRUE((traits::is_volume_unit<cubic_inch_t>::value));
+  EXPECT_FALSE((traits::is_volume_unit<foot_t>::value));
+  EXPECT_TRUE((traits::is_volume_unit<cubic_inch_t, cubic_meter_t>::value));
+  EXPECT_FALSE((traits::is_volume_unit<foot_t, cubic_meter_t>::value));
+}
+
+TEST_F(TypeTraits, is_density_unit) {
+  EXPECT_TRUE((traits::is_density_unit<kilograms_per_cubic_meter>::value));
+  EXPECT_TRUE((traits::is_density_unit<ounces_per_cubic_foot>::value));
+  EXPECT_FALSE((traits::is_density_unit<year>::value));
+  EXPECT_FALSE((traits::is_density_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_density_unit<kilograms_per_cubic_meter_t>::value));
+  EXPECT_TRUE(
+      (traits::is_density_unit<const kilograms_per_cubic_meter_t>::value));
+  EXPECT_TRUE(
+      (traits::is_density_unit<const kilograms_per_cubic_meter_t&>::value));
+  EXPECT_TRUE((traits::is_density_unit<ounces_per_cubic_foot_t>::value));
+  EXPECT_FALSE((traits::is_density_unit<year_t>::value));
+  EXPECT_TRUE((traits::is_density_unit<ounces_per_cubic_foot_t,
+                                       kilograms_per_cubic_meter_t>::value));
+  EXPECT_FALSE(
+      (traits::is_density_unit<year_t, kilograms_per_cubic_meter_t>::value));
+}
+
+TEST_F(TypeTraits, is_data_unit) {
+  EXPECT_TRUE((traits::is_data_unit<bit>::value));
+  EXPECT_TRUE((traits::is_data_unit<byte>::value));
+  EXPECT_TRUE((traits::is_data_unit<exabit>::value));
+  EXPECT_TRUE((traits::is_data_unit<exabyte>::value));
+  EXPECT_FALSE((traits::is_data_unit<year>::value));
+  EXPECT_FALSE((traits::is_data_unit<double>::value));
+
+  EXPECT_TRUE((traits::is_data_unit<bit_t>::value));
+  EXPECT_TRUE((traits::is_data_unit<const bit_t>::value));
+  EXPECT_TRUE((traits::is_data_unit<const bit_t&>::value));
+  EXPECT_TRUE((traits::is_data_unit<byte_t>::value));
+  EXPECT_FALSE((traits::is_data_unit<year_t>::value));
+  EXPECT_TRUE((traits::is_data_unit<bit_t, byte_t>::value));
+  EXPECT_FALSE((traits::is_data_unit<year_t, byte_t>::value));
+}
+
+TEST_F(TypeTraits, is_data_transfer_rate_unit) {
+  EXPECT_TRUE((traits::is_data_transfer_rate_unit<Gbps>::value));
+  EXPECT_TRUE((traits::is_data_transfer_rate_unit<GBps>::value));
+  EXPECT_FALSE((traits::is_data_transfer_rate_unit<year>::value));
+  EXPECT_FALSE((traits::is_data_transfer_rate_unit<double>::value));
+
+  EXPECT_TRUE(
+      (traits::is_data_transfer_rate_unit<gigabits_per_second_t>::value));
+  EXPECT_TRUE((
+      traits::is_data_transfer_rate_unit<const gigabytes_per_second_t>::value));
+  EXPECT_TRUE((traits::is_data_transfer_rate_unit<
+               const gigabytes_per_second_t&>::value));
+  EXPECT_TRUE(
+      (traits::is_data_transfer_rate_unit<gigabytes_per_second_t>::value));
+  EXPECT_FALSE((traits::is_data_transfer_rate_unit<year_t>::value));
+  EXPECT_TRUE(
+      (traits::is_data_transfer_rate_unit<gigabits_per_second_t,
+                                          gigabytes_per_second_t>::value));
+  EXPECT_FALSE(
+      (traits::is_data_transfer_rate_unit<year_t,
+                                          gigabytes_per_second_t>::value));
+}
+
+TEST_F(UnitManipulators, squared) {
+  double test;
+
+  test = convert<squared<meters>, square_feet>(0.092903);
+  EXPECT_NEAR(0.99999956944, test, 5.0e-12);
+
+  using scalar_2 = squared<scalar>;  // this is actually nonsensical, and should
+                                     // also result in a scalar.
+  bool isSame =
+      std::is_same<typename std::decay<scalar_t>::type,
+                   typename std::decay<unit_t<scalar_2>>::type>::value;
+  EXPECT_TRUE(isSame);
+}
+
+TEST_F(UnitManipulators, cubed) {
+  double test;
+
+  test = convert<cubed<meters>, cubic_feet>(0.0283168);
+  EXPECT_NEAR(0.999998354619, test, 5.0e-13);
+}
+
+TEST_F(UnitManipulators, square_root) {
+  double test;
+
+  test = convert<square_root<square_kilometer>, meter>(1.0);
+  EXPECT_TRUE((traits::is_convertible_unit<
+               typename std::decay<square_root<square_kilometer>>::type,
+               kilometer>::value));
+  EXPECT_NEAR(1000.0, test, 5.0e-13);
+}
+
+TEST_F(UnitManipulators, compound_unit) {
+  using acceleration1 = unit<std::ratio<1>, category::acceleration_unit>;
+  using acceleration2 =
+      compound_unit<meters, inverse<seconds>, inverse<seconds>>;
+  using acceleration3 =
+      unit<std::ratio<1>,
+           base_unit<std::ratio<1>, std::ratio<0>, std::ratio<-2>>>;
+  using acceleration4 = compound_unit<meters, inverse<squared<seconds>>>;
+  using acceleration5 = compound_unit<meters, squared<inverse<seconds>>>;
+
+  bool areSame12 = std::is_same<acceleration1, acceleration2>::value;
+  bool areSame23 = std::is_same<acceleration2, acceleration3>::value;
+  bool areSame34 = std::is_same<acceleration3, acceleration4>::value;
+  bool areSame45 = std::is_same<acceleration4, acceleration5>::value;
+
+  EXPECT_TRUE(areSame12);
+  EXPECT_TRUE(areSame23);
+  EXPECT_TRUE(areSame34);
+  EXPECT_TRUE(areSame45);
+
+  // test that thing with translations still compile
+  using arbitrary1 = compound_unit<meters, inverse<celsius>>;
+  using arbitrary2 = compound_unit<meters, celsius>;
+  using arbitrary3 = compound_unit<arbitrary1, arbitrary2>;
+  EXPECT_TRUE((std::is_same<square_meters, arbitrary3>::value));
+}
+
+TEST_F(UnitManipulators, dimensionalAnalysis) {
+  // these look like 'compound units', but the dimensional analysis can be
+  // REALLY handy if the unit types aren't know (i.e. they themselves are
+  // template parameters), as you can get the resulting unit of the operation.
+
+  using velocity = units::detail::unit_divide<meters, second>;
+  bool shouldBeTrue = std::is_same<meters_per_second, velocity>::value;
+  EXPECT_TRUE(shouldBeTrue);
+
+  using acceleration1 = unit<std::ratio<1>, category::acceleration_unit>;
+  using acceleration2 = units::detail::unit_divide<
+      meters, units::detail::unit_multiply<seconds, seconds>>;
+  shouldBeTrue = std::is_same<acceleration1, acceleration2>::value;
+  EXPECT_TRUE(shouldBeTrue);
+}
+
+#ifdef _MSC_VER
+#if (_MSC_VER >= 1900)
+TEST_F(UnitContainer, trivial) {
+  EXPECT_TRUE((std::is_trivial<meter_t>::value));
+  EXPECT_TRUE((std::is_trivially_assignable<meter_t, meter_t>::value));
+  EXPECT_TRUE((std::is_trivially_constructible<meter_t>::value));
+  EXPECT_TRUE((std::is_trivially_copy_assignable<meter_t>::value));
+  EXPECT_TRUE((std::is_trivially_copy_constructible<meter_t>::value));
+  EXPECT_TRUE((std::is_trivially_copyable<meter_t>::value));
+  EXPECT_TRUE((std::is_trivially_default_constructible<meter_t>::value));
+  EXPECT_TRUE((std::is_trivially_destructible<meter_t>::value));
+  EXPECT_TRUE((std::is_trivially_move_assignable<meter_t>::value));
+  EXPECT_TRUE((std::is_trivially_move_constructible<meter_t>::value));
+
+  EXPECT_TRUE((std::is_trivial<dB_t>::value));
+  EXPECT_TRUE((std::is_trivially_assignable<dB_t, dB_t>::value));
+  EXPECT_TRUE((std::is_trivially_constructible<dB_t>::value));
+  EXPECT_TRUE((std::is_trivially_copy_assignable<dB_t>::value));
+  EXPECT_TRUE((std::is_trivially_copy_constructible<dB_t>::value));
+  EXPECT_TRUE((std::is_trivially_copyable<dB_t>::value));
+  EXPECT_TRUE((std::is_trivially_default_constructible<dB_t>::value));
+  EXPECT_TRUE((std::is_trivially_destructible<dB_t>::value));
+  EXPECT_TRUE((std::is_trivially_move_assignable<dB_t>::value));
+  EXPECT_TRUE((std::is_trivially_move_constructible<dB_t>::value));
+}
+#endif
+#endif
+
+TEST_F(UnitContainer, has_value_member) {
+  EXPECT_TRUE((traits::has_value_member<linear_scale<double>, double>::value));
+  EXPECT_FALSE((traits::has_value_member<meter, double>::value));
+}
+
+TEST_F(UnitContainer, make_unit) {
+  auto dist = units::make_unit<meter_t>(5);
+  EXPECT_EQ(meter_t(5), dist);
+}
+
+TEST_F(UnitContainer, unitTypeAddition) {
+  // units
+  meter_t a_m(1.0), c_m;
+  foot_t b_ft(3.28084);
+
+  double d = convert<feet, meters>(b_ft());
+  EXPECT_NEAR(1.0, d, 5.0e-5);
+
+  c_m = a_m + b_ft;
+  EXPECT_NEAR(2.0, c_m(), 5.0e-5);
+
+  c_m = b_ft + meter_t(3);
+  EXPECT_NEAR(4.0, c_m(), 5.0e-5);
+
+  auto e_ft = b_ft + meter_t(3);
+  EXPECT_NEAR(13.12336, e_ft(), 5.0e-6);
+
+  // scalar
+  scalar_t sresult = scalar_t(1.0) + scalar_t(1.0);
+  EXPECT_NEAR(2.0, sresult, 5.0e-6);
+
+  sresult = scalar_t(1.0) + 1.0;
+  EXPECT_NEAR(2.0, sresult, 5.0e-6);
+
+  sresult = 1.0 + scalar_t(1.0);
+  EXPECT_NEAR(2.0, sresult, 5.0e-6);
+
+  d = scalar_t(1.0) + scalar_t(1.0);
+  EXPECT_NEAR(2.0, d, 5.0e-6);
+
+  d = scalar_t(1.0) + 1.0;
+  EXPECT_NEAR(2.0, d, 5.0e-6);
+
+  d = 1.0 + scalar_t(1.0);
+  EXPECT_NEAR(2.0, d, 5.0e-6);
+}
+
+TEST_F(UnitContainer, unitTypeUnaryAddition) {
+  meter_t a_m(1.0);
+
+  EXPECT_EQ(++a_m, meter_t(2));
+  EXPECT_EQ(a_m++, meter_t(2));
+  EXPECT_EQ(a_m, meter_t(3));
+  EXPECT_EQ(+a_m, meter_t(3));
+  EXPECT_EQ(a_m, meter_t(3));
+
+  dBW_t b_dBW(1.0);
+
+  EXPECT_EQ(++b_dBW, dBW_t(2));
+  EXPECT_EQ(b_dBW++, dBW_t(2));
+  EXPECT_EQ(b_dBW, dBW_t(3));
+  EXPECT_EQ(+b_dBW, dBW_t(3));
+  EXPECT_EQ(b_dBW, dBW_t(3));
+}
+
+TEST_F(UnitContainer, unitTypeSubtraction) {
+  meter_t a_m(1.0), c_m;
+  foot_t b_ft(3.28084);
+
+  c_m = a_m - b_ft;
+  EXPECT_NEAR(0.0, c_m(), 5.0e-5);
+
+  c_m = b_ft - meter_t(1);
+  EXPECT_NEAR(0.0, c_m(), 5.0e-5);
+
+  auto e_ft = b_ft - meter_t(1);
+  EXPECT_NEAR(0.0, e_ft(), 5.0e-6);
+
+  scalar_t sresult = scalar_t(1.0) - scalar_t(1.0);
+  EXPECT_NEAR(0.0, sresult, 5.0e-6);
+
+  sresult = scalar_t(1.0) - 1.0;
+  EXPECT_NEAR(0.0, sresult, 5.0e-6);
+
+  sresult = 1.0 - scalar_t(1.0);
+  EXPECT_NEAR(0.0, sresult, 5.0e-6);
+
+  double d = scalar_t(1.0) - scalar_t(1.0);
+  EXPECT_NEAR(0.0, d, 5.0e-6);
+
+  d = scalar_t(1.0) - 1.0;
+  EXPECT_NEAR(0.0, d, 5.0e-6);
+
+  d = 1.0 - scalar_t(1.0);
+  EXPECT_NEAR(0.0, d, 5.0e-6);
+}
+
+TEST_F(UnitContainer, unitTypeUnarySubtraction) {
+  meter_t a_m(4.0);
+
+  EXPECT_EQ(--a_m, meter_t(3));
+  EXPECT_EQ(a_m--, meter_t(3));
+  EXPECT_EQ(a_m, meter_t(2));
+  EXPECT_EQ(-a_m, meter_t(-2));
+  EXPECT_EQ(a_m, meter_t(2));
+
+  dBW_t b_dBW(4.0);
+
+  EXPECT_EQ(--b_dBW, dBW_t(3));
+  EXPECT_EQ(b_dBW--, dBW_t(3));
+  EXPECT_EQ(b_dBW, dBW_t(2));
+  EXPECT_EQ(-b_dBW, dBW_t(-2));
+  EXPECT_EQ(b_dBW, dBW_t(2));
+}
+
+TEST_F(UnitContainer, unitTypeMultiplication) {
+  meter_t a_m(1.0), b_m(2.0);
+  foot_t a_ft(3.28084);
+
+  auto c_m2 = a_m * b_m;
+  EXPECT_NEAR(2.0, c_m2(), 5.0e-5);
+
+  c_m2 = b_m * meter_t(2);
+  EXPECT_NEAR(4.0, c_m2(), 5.0e-5);
+
+  c_m2 = b_m * a_ft;
+  EXPECT_NEAR(2.0, c_m2(), 5.0e-5);
+
+  auto c_m = b_m * 2.0;
+  EXPECT_NEAR(4.0, c_m(), 5.0e-5);
+
+  c_m = 2.0 * b_m;
+  EXPECT_NEAR(4.0, c_m(), 5.0e-5);
+
+  double convert = scalar_t(3.14);
+  EXPECT_NEAR(3.14, convert, 5.0e-5);
+
+  scalar_t sresult = scalar_t(5.0) * scalar_t(4.0);
+  EXPECT_NEAR(20.0, sresult(), 5.0e-5);
+
+  sresult = scalar_t(5.0) * 4.0;
+  EXPECT_NEAR(20.0, sresult(), 5.0e-5);
+
+  sresult = 4.0 * scalar_t(5.0);
+  EXPECT_NEAR(20.0, sresult(), 5.0e-5);
+
+  double result = scalar_t(5.0) * scalar_t(4.0);
+  EXPECT_NEAR(20.0, result, 5.0e-5);
+
+  result = scalar_t(5.0) * 4.0;
+  EXPECT_NEAR(20.0, result, 5.0e-5);
+
+  result = 4.0 * scalar_t(5.0);
+  EXPECT_NEAR(20.0, result, 5.0e-5);
+}
+
+TEST_F(UnitContainer, unitTypeMixedUnitMultiplication) {
+  meter_t a_m(1.0);
+  foot_t b_ft(3.28084);
+  unit_t<inverse<meter>> i_m(2.0);
+
+  // resultant unit is square of leftmost unit
+  auto c_m2 = a_m * b_ft;
+  EXPECT_NEAR(1.0, c_m2(), 5.0e-5);
+
+  auto c_ft2 = b_ft * a_m;
+  EXPECT_NEAR(10.7639111056, c_ft2(), 5.0e-7);
+
+  // you can get whatever (compatible) type you want if you ask explicitly
+  square_meter_t d_m2 = b_ft * a_m;
+  EXPECT_NEAR(1.0, d_m2(), 5.0e-5);
+
+  // a unit times a sclar ends up with the same units.
+  meter_t e_m = a_m * scalar_t(3.0);
+  EXPECT_NEAR(3.0, e_m(), 5.0e-5);
+
+  e_m = scalar_t(4.0) * a_m;
+  EXPECT_NEAR(4.0, e_m(), 5.0e-5);
+
+  // unit times its inverse results in a scalar
+  scalar_t s = a_m * i_m;
+  EXPECT_NEAR(2.0, s, 5.0e-5);
+
+  c_m2 = b_ft * meter_t(2);
+  EXPECT_NEAR(2.0, c_m2(), 5.0e-5);
+
+  auto e_ft2 = b_ft * meter_t(3);
+  EXPECT_NEAR(32.2917333168, e_ft2(), 5.0e-6);
+
+  auto mps = meter_t(10.0) * unit_t<inverse<seconds>>(1.0);
+  EXPECT_EQ(mps, meters_per_second_t(10));
+}
+
+TEST_F(UnitContainer, unitTypeScalarMultiplication) {
+  meter_t a_m(1.0);
+
+  auto result_m = scalar_t(3.0) * a_m;
+  EXPECT_NEAR(3.0, result_m(), 5.0e-5);
+
+  result_m = a_m * scalar_t(4.0);
+  EXPECT_NEAR(4.0, result_m(), 5.0e-5);
+
+  result_m = 3.0 * a_m;
+  EXPECT_NEAR(3.0, result_m(), 5.0e-5);
+
+  result_m = a_m * 4.0;
+  EXPECT_NEAR(4.0, result_m(), 5.0e-5);
+
+  bool isSame = std::is_same<decltype(result_m), meter_t>::value;
+  EXPECT_TRUE(isSame);
+}
+
+TEST_F(UnitContainer, unitTypeDivision) {
+  meter_t a_m(1.0), b_m(2.0);
+  foot_t a_ft(3.28084);
+  second_t a_sec(10.0);
+  bool isSame;
+
+  auto c = a_m / a_ft;
+  EXPECT_NEAR(1.0, c, 5.0e-5);
+  isSame = std::is_same<decltype(c), scalar_t>::value;
+  EXPECT_TRUE(isSame);
+
+  c = a_m / b_m;
+  EXPECT_NEAR(0.5, c, 5.0e-5);
+  isSame = std::is_same<decltype(c), scalar_t>::value;
+  EXPECT_TRUE(isSame);
+
+  c = a_ft / a_m;
+  EXPECT_NEAR(1.0, c, 5.0e-5);
+  isSame = std::is_same<decltype(c), scalar_t>::value;
+  EXPECT_TRUE(isSame);
+
+  c = scalar_t(1.0) / 2.0;
+  EXPECT_NEAR(0.5, c, 5.0e-5);
+  isSame = std::is_same<decltype(c), scalar_t>::value;
+  EXPECT_TRUE(isSame);
+
+  c = 1.0 / scalar_t(2.0);
+  EXPECT_NEAR(0.5, c, 5.0e-5);
+  isSame = std::is_same<decltype(c), scalar_t>::value;
+  EXPECT_TRUE(isSame);
+
+  double d = scalar_t(1.0) / 2.0;
+  EXPECT_NEAR(0.5, d, 5.0e-5);
+
+  auto e = a_m / a_sec;
+  EXPECT_NEAR(0.1, e(), 5.0e-5);
+  isSame = std::is_same<decltype(e), meters_per_second_t>::value;
+  EXPECT_TRUE(isSame);
+
+  auto f = a_m / 8.0;
+  EXPECT_NEAR(0.125, f(), 5.0e-5);
+  isSame = std::is_same<decltype(f), meter_t>::value;
+  EXPECT_TRUE(isSame);
+
+  auto g = 4.0 / b_m;
+  EXPECT_NEAR(2.0, g(), 5.0e-5);
+  isSame = std::is_same<decltype(g), unit_t<inverse<meters>>>::value;
+  EXPECT_TRUE(isSame);
+
+  auto mph = mile_t(60.0) / hour_t(1.0);
+  meters_per_second_t mps = mph;
+  EXPECT_NEAR(26.8224, mps(), 5.0e-5);
+}
+
+TEST_F(UnitContainer, compoundAssignmentAddition) {
+  // units
+  meter_t a(0.0);
+  a += meter_t(1.0);
+
+  EXPECT_EQ(meter_t(1.0), a);
+
+  a += foot_t(meter_t(1));
+
+  EXPECT_EQ(meter_t(2.0), a);
+
+  // scalars
+  scalar_t b(0);
+  b += scalar_t(1.0);
+
+  EXPECT_EQ(scalar_t(1.0), b);
+
+  b += 1;
+
+  EXPECT_EQ(scalar_t(2.0), b);
+}
+
+TEST_F(UnitContainer, compoundAssignmentSubtraction) {
+  // units
+  meter_t a(2.0);
+  a -= meter_t(1.0);
+
+  EXPECT_EQ(meter_t(1.0), a);
+
+  a -= foot_t(meter_t(1));
+
+  EXPECT_EQ(meter_t(0.0), a);
+
+  // scalars
+  scalar_t b(2);
+  b -= scalar_t(1.0);
+
+  EXPECT_EQ(scalar_t(1.0), b);
+
+  b -= 1;
+
+  EXPECT_EQ(scalar_t(0), b);
+}
+
+TEST_F(UnitContainer, compoundAssignmentMultiplication) {
+  // units
+  meter_t a(2.0);
+  a *= scalar_t(2.0);
+
+  EXPECT_EQ(meter_t(4.0), a);
+
+  a *= 2.0;
+
+  EXPECT_EQ(meter_t(8.0), a);
+
+  // scalars
+  scalar_t b(2);
+  b *= scalar_t(2.0);
+
+  EXPECT_EQ(scalar_t(4.0), b);
+
+  b *= 2;
+
+  EXPECT_EQ(scalar_t(8.0), b);
+}
+
+TEST_F(UnitContainer, compoundAssignmentDivision) {
+  // units
+  meter_t a(8.0);
+  a /= scalar_t(2.0);
+
+  EXPECT_EQ(meter_t(4.0), a);
+
+  a /= 2.0;
+
+  EXPECT_EQ(meter_t(2.0), a);
+
+  // scalars
+  scalar_t b(8);
+  b /= scalar_t(2.0);
+
+  EXPECT_EQ(scalar_t(4.0), b);
+
+  b /= 2;
+
+  EXPECT_EQ(scalar_t(2.0), b);
+}
+
+TEST_F(UnitContainer, scalarTypeImplicitConversion) {
+  double test = scalar_t(3.0);
+  EXPECT_DOUBLE_EQ(3.0, test);
+
+  scalar_t testS = 3.0;
+  EXPECT_DOUBLE_EQ(3.0, testS);
+
+  scalar_t test3(ppm_t(10));
+  EXPECT_DOUBLE_EQ(0.00001, test3);
+
+  scalar_t test4;
+  test4 = ppm_t(1);
+  EXPECT_DOUBLE_EQ(0.000001, test4);
+}
+
+TEST_F(UnitContainer, valueMethod) {
+  double test = meter_t(3.0).to<double>();
+  EXPECT_DOUBLE_EQ(3.0, test);
+
+  auto test2 = meter_t(4.0).value();
+  EXPECT_DOUBLE_EQ(4.0, test2);
+  EXPECT_TRUE((std::is_same<decltype(test2), double>::value));
+}
+
+TEST_F(UnitContainer, convertMethod) {
+  double test = meter_t(3.0).convert<feet>().to<double>();
+  EXPECT_NEAR(9.84252, test, 5.0e-6);
+}
+
+#ifndef UNIT_LIB_DISABLE_IOSTREAM
+TEST_F(UnitContainer, cout) {
+  testing::internal::CaptureStdout();
+  std::cout << degree_t(349.87);
+  std::string output = testing::internal::GetCapturedStdout();
+  EXPECT_STREQ("349.87 deg", output.c_str());
+
+  testing::internal::CaptureStdout();
+  std::cout << meter_t(1.0);
+  output = testing::internal::GetCapturedStdout();
+  EXPECT_STREQ("1 m", output.c_str());
+
+  testing::internal::CaptureStdout();
+  std::cout << dB_t(31.0);
+  output = testing::internal::GetCapturedStdout();
+  EXPECT_STREQ("31 dB", output.c_str());
+
+  testing::internal::CaptureStdout();
+  std::cout << volt_t(21.79);
+  output = testing::internal::GetCapturedStdout();
+  EXPECT_STREQ("21.79 V", output.c_str());
+
+  testing::internal::CaptureStdout();
+  std::cout << dBW_t(12.0);
+  output = testing::internal::GetCapturedStdout();
+  EXPECT_STREQ("12 dBW", output.c_str());
+
+  testing::internal::CaptureStdout();
+  std::cout << dBm_t(120.0);
+  output = testing::internal::GetCapturedStdout();
+  EXPECT_STREQ("120 dBm", output.c_str());
+
+  testing::internal::CaptureStdout();
+  std::cout << miles_per_hour_t(72.1);
+  output = testing::internal::GetCapturedStdout();
+  EXPECT_STREQ("72.1 mph", output.c_str());
+
+  // undefined unit
+  testing::internal::CaptureStdout();
+  std::cout << units::math::cpow<4>(meter_t(2));
+  output = testing::internal::GetCapturedStdout();
+  EXPECT_STREQ("16 m^4", output.c_str());
+
+  testing::internal::CaptureStdout();
+  std::cout << units::math::cpow<3>(foot_t(2));
+  output = testing::internal::GetCapturedStdout();
+  EXPECT_STREQ("8 cu_ft", output.c_str());
+
+  testing::internal::CaptureStdout();
+  std::cout << std::setprecision(9) << units::math::cpow<4>(foot_t(2));
+  output = testing::internal::GetCapturedStdout();
+  EXPECT_STREQ("0.138095597 m^4", output.c_str());
+
+  // constants
+  testing::internal::CaptureStdout();
+  std::cout << std::setprecision(8) << constants::k_B;
+  output = testing::internal::GetCapturedStdout();
+#if defined(_MSC_VER) && (_MSC_VER <= 1800)
+  EXPECT_STREQ("1.3806485e-023 m^2 kg s^-2 K^-1", output.c_str());
+#else
+  EXPECT_STREQ("1.3806485e-23 m^2 kg s^-2 K^-1", output.c_str());
+#endif
+
+  testing::internal::CaptureStdout();
+  std::cout << std::setprecision(9) << constants::mu_B;
+  output = testing::internal::GetCapturedStdout();
+#if defined(_MSC_VER) && (_MSC_VER <= 1800)
+  EXPECT_STREQ("9.27400999e-024 m^2 A", output.c_str());
+#else
+  EXPECT_STREQ("9.27400999e-24 m^2 A", output.c_str());
+#endif
+
+  testing::internal::CaptureStdout();
+  std::cout << std::setprecision(7) << constants::sigma;
+  output = testing::internal::GetCapturedStdout();
+#if defined(_MSC_VER) && (_MSC_VER <= 1800)
+  EXPECT_STREQ("5.670367e-008 kg s^-3 K^-4", output.c_str());
+#else
+  EXPECT_STREQ("5.670367e-08 kg s^-3 K^-4", output.c_str());
+#endif
+}
+
+TEST_F(UnitContainer, to_string) {
+  foot_t a(3.5);
+  EXPECT_STREQ("3.5 ft", units::length::to_string(a).c_str());
+
+  meter_t b(8);
+  EXPECT_STREQ("8 m", units::length::to_string(b).c_str());
+}
+
+TEST_F(UnitContainer, DISABLED_to_string_locale) {
+  struct lconv* lc;
+
+  // German locale
+#if defined(_MSC_VER)
+  setlocale(LC_ALL, "de-DE");
+#else
+  EXPECT_STREQ("de_DE.utf8", setlocale(LC_ALL, "de_DE.utf8"));
+#endif
+
+  lc = localeconv();
+  char point_de = *lc->decimal_point;
+  EXPECT_EQ(point_de, ',');
+
+  kilometer_t de = 2_km;
+  EXPECT_STREQ("2 km", units::length::to_string(de).c_str());
+
+  de = 2.5_km;
+  EXPECT_STREQ("2,5 km", units::length::to_string(de).c_str());
+
+  // US locale
+#if defined(_MSC_VER)
+  setlocale(LC_ALL, "en-US");
+#else
+  EXPECT_STREQ("en_US.utf8", setlocale(LC_ALL, "en_US.utf8"));
+#endif
+
+  lc = localeconv();
+  char point_us = *lc->decimal_point;
+  EXPECT_EQ(point_us, '.');
+
+  mile_t us = 2_mi;
+  EXPECT_STREQ("2 mi", units::length::to_string(us).c_str());
+
+  us = 2.5_mi;
+  EXPECT_STREQ("2.5 mi", units::length::to_string(us).c_str());
+}
+
+TEST_F(UnitContainer, nameAndAbbreviation) {
+  foot_t a(3.5);
+  EXPECT_STREQ("ft", units::abbreviation(a));
+  EXPECT_STREQ("ft", a.abbreviation());
+  EXPECT_STREQ("foot", a.name());
+
+  meter_t b(8);
+  EXPECT_STREQ("m", units::abbreviation(b));
+  EXPECT_STREQ("m", b.abbreviation());
+  EXPECT_STREQ("meter", b.name());
+}
+#endif
+
+TEST_F(UnitContainer, negative) {
+  meter_t a(5.3);
+  meter_t b(-5.3);
+  EXPECT_NEAR(a.to<double>(), -b.to<double>(), 5.0e-320);
+  EXPECT_NEAR(b.to<double>(), -a.to<double>(), 5.0e-320);
+
+  dB_t c(2.87);
+  dB_t d(-2.87);
+  EXPECT_NEAR(c.to<double>(), -d.to<double>(), 5.0e-320);
+  EXPECT_NEAR(d.to<double>(), -c.to<double>(), 5.0e-320);
+
+  ppm_t e = -1 * ppm_t(10);
+  EXPECT_EQ(e, -ppm_t(10));
+  EXPECT_NEAR(-0.00001, e, 5.0e-10);
+}
+
+TEST_F(UnitContainer, concentration) {
+  ppb_t a(ppm_t(1));
+  EXPECT_EQ(ppb_t(1000), a);
+  EXPECT_EQ(0.000001, a);
+  EXPECT_EQ(0.000001, a.to<double>());
+
+  scalar_t b(ppm_t(1));
+  EXPECT_EQ(0.000001, b);
+
+  scalar_t c = ppb_t(1);
+  EXPECT_EQ(0.000000001, c);
+}
+
+TEST_F(UnitContainer, dBConversion) {
+  dBW_t a_dbw(23.1);
+  watt_t a_w = a_dbw;
+  dBm_t a_dbm = a_dbw;
+
+  EXPECT_NEAR(204.173794, a_w(), 5.0e-7);
+  EXPECT_NEAR(53.1, a_dbm(), 5.0e-7);
+
+  milliwatt_t b_mw(100000.0);
+  watt_t b_w = b_mw;
+  dBm_t b_dbm = b_mw;
+  dBW_t b_dbw = b_mw;
+
+  EXPECT_NEAR(100.0, b_w(), 5.0e-7);
+  EXPECT_NEAR(50.0, b_dbm(), 5.0e-7);
+  EXPECT_NEAR(20.0, b_dbw(), 5.0e-7);
+}
+
+TEST_F(UnitContainer, dBAddition) {
+  bool isSame;
+
+  auto result_dbw = dBW_t(10.0) + dB_t(30.0);
+  EXPECT_NEAR(40.0, result_dbw(), 5.0e-5);
+  result_dbw = dB_t(12.0) + dBW_t(30.0);
+  EXPECT_NEAR(42.0, result_dbw(), 5.0e-5);
+  isSame = std::is_same<decltype(result_dbw), dBW_t>::value;
+  EXPECT_TRUE(isSame);
+
+  auto result_dbm = dB_t(30.0) + dBm_t(20.0);
+  EXPECT_NEAR(50.0, result_dbm(), 5.0e-5);
+
+  // adding dBW to dBW is something you probably shouldn't do, but let's see if
+  // it works...
+  auto result_dBW2 = dBW_t(10.0) + dBm_t(40.0);
+  EXPECT_NEAR(20.0, result_dBW2(), 5.0e-5);
+  isSame = std::is_same<decltype(result_dBW2),
+                        unit_t<squared<watts>, double, decibel_scale>>::value;
+  EXPECT_TRUE(isSame);
+}
+
+TEST_F(UnitContainer, dBSubtraction) {
+  bool isSame;
+
+  auto result_dbw = dBW_t(10.0) - dB_t(30.0);
+  EXPECT_NEAR(-20.0, result_dbw(), 5.0e-5);
+  isSame = std::is_same<decltype(result_dbw), dBW_t>::value;
+  EXPECT_TRUE(isSame);
+
+  auto result_dbm = dBm_t(100.0) - dB_t(30.0);
+  EXPECT_NEAR(70.0, result_dbm(), 5.0e-5);
+  isSame = std::is_same<decltype(result_dbm), dBm_t>::value;
+  EXPECT_TRUE(isSame);
+
+  auto result_db = dBW_t(100.0) - dBW_t(80.0);
+  EXPECT_NEAR(20.0, result_db(), 5.0e-5);
+  isSame = std::is_same<decltype(result_db), dB_t>::value;
+  EXPECT_TRUE(isSame);
+
+  result_db = dB_t(100.0) - dB_t(80.0);
+  EXPECT_NEAR(20.0, result_db(), 5.0e-5);
+  isSame = std::is_same<decltype(result_db), dB_t>::value;
+  EXPECT_TRUE(isSame);
+}
+
+TEST_F(UnitContainer, unit_cast) {
+  meter_t test1(5.7);
+  hectare_t test2(16);
+
+  double dResult1 = 5.7;
+
+  double dResult2 = 16;
+  int iResult2 = 16;
+
+  EXPECT_EQ(dResult1, unit_cast<double>(test1));
+  EXPECT_EQ(dResult2, unit_cast<double>(test2));
+  EXPECT_EQ(iResult2, unit_cast<int>(test2));
+
+  EXPECT_TRUE(
+      (std::is_same<double, decltype(unit_cast<double>(test1))>::value));
+  EXPECT_TRUE((std::is_same<int, decltype(unit_cast<int>(test2))>::value));
+}
+
+// literal syntax is only supported in GCC 4.7+ and MSVC2015+
+#if !defined(_MSC_VER) || _MSC_VER > 1800
+TEST_F(UnitContainer, literals) {
+  // basic functionality testing
+  EXPECT_TRUE((std::is_same<decltype(16.2_m), meter_t>::value));
+  EXPECT_TRUE(meter_t(16.2) == 16.2_m);
+  EXPECT_TRUE(meter_t(16) == 16_m);
+
+  EXPECT_TRUE((std::is_same<decltype(11.2_ft), foot_t>::value));
+  EXPECT_TRUE(foot_t(11.2) == 11.2_ft);
+  EXPECT_TRUE(foot_t(11) == 11_ft);
+
+  // auto using literal syntax
+  auto x = 10.0_m;
+  EXPECT_TRUE((std::is_same<decltype(x), meter_t>::value));
+  EXPECT_TRUE(meter_t(10) == x);
+
+  // conversion using literal syntax
+  foot_t y = 0.3048_m;
+  EXPECT_TRUE(1_ft == y);
+
+  // Pythagorean theorem
+  meter_t a = 3_m;
+  meter_t b = 4_m;
+  meter_t c = sqrt(pow<2>(a) + pow<2>(b));
+  EXPECT_TRUE(c == 5_m);
+}
+#endif
+
+TEST_F(UnitConversion, length) {
+  double test;
+  test = convert<meters, nanometers>(0.000000001);
+  EXPECT_NEAR(1.0, test, 5.0e-20);
+  test = convert<meters, micrometers>(0.000001);
+  EXPECT_NEAR(1.0, test, 5.0e-20);
+  test = convert<meters, millimeters>(0.001);
+  EXPECT_NEAR(1.0, test, 5.0e-20);
+  test = convert<meters, centimeters>(0.01);
+  EXPECT_NEAR(1.0, test, 5.0e-20);
+  test = convert<meters, kilometers>(1000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-20);
+  test = convert<meters, meters>(1.0);
+  EXPECT_NEAR(1.0, test, 5.0e-20);
+  test = convert<meters, feet>(0.3048);
+  EXPECT_NEAR(1.0, test, 5.0e-20);
+  test = convert<meters, miles>(1609.344);
+  EXPECT_NEAR(1.0, test, 5.0e-20);
+  test = convert<meters, inches>(0.0254);
+  EXPECT_NEAR(1.0, test, 5.0e-20);
+  test = convert<meters, nauticalMiles>(1852.0);
+  EXPECT_NEAR(1.0, test, 5.0e-20);
+  test = convert<meters, astronicalUnits>(149597870700.0);
+  EXPECT_NEAR(1.0, test, 5.0e-20);
+  test = convert<meters, lightyears>(9460730472580800.0);
+  EXPECT_NEAR(1.0, test, 5.0e-20);
+  test = convert<meters, parsec>(3.08567758e16);
+  EXPECT_NEAR(1.0, test, 5.0e7);
+
+  test = convert<feet, feet>(6.3);
+  EXPECT_NEAR(6.3, test, 5.0e-5);
+  test = convert<feet, inches>(6.0);
+  EXPECT_NEAR(72.0, test, 5.0e-5);
+  test = convert<inches, feet>(6.0);
+  EXPECT_NEAR(0.5, test, 5.0e-5);
+  test = convert<meter, feet>(1.0);
+  EXPECT_NEAR(3.28084, test, 5.0e-5);
+  test = convert<miles, nauticalMiles>(6.3);
+  EXPECT_NEAR(5.47455, test, 5.0e-6);
+  test = convert<miles, meters>(11.0);
+  EXPECT_NEAR(17702.8, test, 5.0e-2);
+  test = convert<meters, chains>(1.0);
+  EXPECT_NEAR(0.0497097, test, 5.0e-7);
+}
+
+TEST_F(UnitConversion, mass) {
+  double test;
+
+  test = convert<kilograms, grams>(1.0e-3);
+  EXPECT_NEAR(1.0, test, 5.0e-6);
+  test = convert<kilograms, micrograms>(1.0e-9);
+  EXPECT_NEAR(1.0, test, 5.0e-6);
+  test = convert<kilograms, milligrams>(1.0e-6);
+  EXPECT_NEAR(1.0, test, 5.0e-6);
+  test = convert<kilograms, kilograms>(1.0);
+  EXPECT_NEAR(1.0, test, 5.0e-6);
+  test = convert<kilograms, metric_tons>(1000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-6);
+  test = convert<kilograms, pounds>(0.453592);
+  EXPECT_NEAR(1.0, test, 5.0e-6);
+  test = convert<kilograms, long_tons>(1016.05);
+  EXPECT_NEAR(1.0, test, 5.0e-6);
+  test = convert<kilograms, short_tons>(907.185);
+  EXPECT_NEAR(1.0, test, 5.0e-6);
+  test = convert<kilograms, mass::ounces>(0.0283495);
+  EXPECT_NEAR(1.0, test, 5.0e-6);
+  test = convert<kilograms, carats>(0.0002);
+  EXPECT_NEAR(1.0, test, 5.0e-6);
+  test = convert<slugs, kilograms>(1.0);
+  EXPECT_NEAR(14.593903, test, 5.0e-7);
+
+  test = convert<pounds, carats>(6.3);
+  EXPECT_NEAR(14288.2, test, 5.0e-2);
+}
+
+TEST_F(UnitConversion, time) {
+  double result = 0;
+  double daysPerYear = 365;
+  double hoursPerDay = 24;
+  double minsPerHour = 60;
+  double secsPerMin = 60;
+  double daysPerWeek = 7;
+
+  result = 2 * daysPerYear * hoursPerDay * minsPerHour * secsPerMin *
+           (1 / minsPerHour) * (1 / secsPerMin) * (1 / hoursPerDay) *
+           (1 / daysPerWeek);
+  EXPECT_NEAR(104.286, result, 5.0e-4);
+
+  year_t twoYears(2.0);
+  week_t twoYearsInWeeks = twoYears;
+  EXPECT_NEAR(week_t(104.286).to<double>(), twoYearsInWeeks.to<double>(),
+              5.0e-4);
+
+  double test;
+
+  test = convert<seconds, seconds>(1.0);
+  EXPECT_NEAR(1.0, test, 5.0e-20);
+  test = convert<seconds, nanoseconds>(1.0e-9);
+  EXPECT_NEAR(1.0, test, 5.0e-20);
+  test = convert<seconds, microseconds>(1.0e-6);
+  EXPECT_NEAR(1.0, test, 5.0e-20);
+  test = convert<seconds, milliseconds>(1.0e-3);
+  EXPECT_NEAR(1.0, test, 5.0e-20);
+  test = convert<seconds, minutes>(60.0);
+  EXPECT_NEAR(1.0, test, 5.0e-20);
+  test = convert<seconds, hours>(3600.0);
+  EXPECT_NEAR(1.0, test, 5.0e-20);
+  test = convert<seconds, days>(86400.0);
+  EXPECT_NEAR(1.0, test, 5.0e-20);
+  test = convert<seconds, weeks>(604800.0);
+  EXPECT_NEAR(1.0, test, 5.0e-20);
+  test = convert<seconds, years>(3.154e7);
+  EXPECT_NEAR(1.0, test, 5.0e3);
+
+  test = convert<years, weeks>(2.0);
+  EXPECT_NEAR(104.2857142857143, test, 5.0e-14);
+  test = convert<hours, minutes>(4.0);
+  EXPECT_NEAR(240.0, test, 5.0e-14);
+  test = convert<julian_years, days>(1.0);
+  EXPECT_NEAR(365.25, test, 5.0e-14);
+  test = convert<gregorian_years, days>(1.0);
+  EXPECT_NEAR(365.2425, test, 5.0e-14);
+}
+
+TEST_F(UnitConversion, angle) {
+  angle::degree_t quarterCircleDeg(90.0);
+  angle::radian_t quarterCircleRad = quarterCircleDeg;
+  EXPECT_NEAR(angle::radian_t(constants::detail::PI_VAL / 2.0).to<double>(),
+              quarterCircleRad.to<double>(), 5.0e-12);
+
+  double test;
+
+  test = convert<angle::radians, angle::radians>(1.0);
+  EXPECT_NEAR(1.0, test, 5.0e-20);
+  test = convert<angle::radians, angle::milliradians>(0.001);
+  EXPECT_NEAR(1.0, test, 5.0e-4);
+  test = convert<angle::radians, angle::degrees>(0.0174533);
+  EXPECT_NEAR(1.0, test, 5.0e-7);
+  test = convert<angle::radians, angle::arcminutes>(0.000290888);
+  EXPECT_NEAR(0.99999928265913, test, 5.0e-8);
+  test = convert<angle::radians, angle::arcseconds>(4.8481e-6);
+  EXPECT_NEAR(0.999992407, test, 5.0e-10);
+  test = convert<angle::radians, angle::turns>(6.28319);
+  EXPECT_NEAR(1.0, test, 5.0e-6);
+  test = convert<angle::radians, angle::gradians>(0.015708);
+  EXPECT_NEAR(1.0, test, 5.0e-6);
+
+  test = convert<angle::radians, angle::radians>(2.1);
+  EXPECT_NEAR(2.1, test, 5.0e-6);
+  test = convert<angle::arcseconds, angle::gradians>(2.1);
+  EXPECT_NEAR(0.000648148, test, 5.0e-6);
+  test = convert<angle::radians, angle::degrees>(constants::detail::PI_VAL);
+  EXPECT_NEAR(180.0, test, 5.0e-6);
+  test = convert<angle::degrees, angle::radians>(90.0);
+  EXPECT_NEAR(constants::detail::PI_VAL / 2, test, 5.0e-6);
+}
+
+TEST_F(UnitConversion, current) {
+  double test;
+
+  test = convert<current::A, current::mA>(2.1);
+  EXPECT_NEAR(2100.0, test, 5.0e-6);
+}
+
+TEST_F(UnitConversion, temperature) {
+  // temp conversion are weird/hard since they involve translations AND scaling.
+  double test;
+
+  test = convert<kelvin, kelvin>(72.0);
+  EXPECT_NEAR(72.0, test, 5.0e-5);
+  test = convert<fahrenheit, fahrenheit>(72.0);
+  EXPECT_NEAR(72.0, test, 5.0e-5);
+  test = convert<kelvin, fahrenheit>(300.0);
+  EXPECT_NEAR(80.33, test, 5.0e-5);
+  test = convert<fahrenheit, kelvin>(451.0);
+  EXPECT_NEAR(505.928, test, 5.0e-4);
+  test = convert<kelvin, celsius>(300.0);
+  EXPECT_NEAR(26.85, test, 5.0e-3);
+  test = convert<celsius, kelvin>(451.0);
+  EXPECT_NEAR(724.15, test, 5.0e-3);
+  test = convert<fahrenheit, celsius>(72.0);
+  EXPECT_NEAR(22.2222, test, 5.0e-5);
+  test = convert<celsius, fahrenheit>(100.0);
+  EXPECT_NEAR(212.0, test, 5.0e-5);
+  test = convert<fahrenheit, celsius>(32.0);
+  EXPECT_NEAR(0.0, test, 5.0e-5);
+  test = convert<celsius, fahrenheit>(0.0);
+  EXPECT_NEAR(32.0, test, 5.0e-5);
+  test = convert<rankine, kelvin>(100.0);
+  EXPECT_NEAR(55.5556, test, 5.0e-5);
+  test = convert<kelvin, rankine>(100.0);
+  EXPECT_NEAR(180.0, test, 5.0e-5);
+  test = convert<fahrenheit, rankine>(100.0);
+  EXPECT_NEAR(559.67, test, 5.0e-5);
+  test = convert<rankine, fahrenheit>(72.0);
+  EXPECT_NEAR(-387.67, test, 5.0e-5);
+  test = convert<reaumur, kelvin>(100.0);
+  EXPECT_NEAR(398.0, test, 5.0e-1);
+  test = convert<reaumur, celsius>(80.0);
+  EXPECT_NEAR(100.0, test, 5.0e-5);
+  test = convert<celsius, reaumur>(212.0);
+  EXPECT_NEAR(169.6, test, 5.0e-2);
+  test = convert<reaumur, fahrenheit>(80.0);
+  EXPECT_NEAR(212.0, test, 5.0e-5);
+  test = convert<fahrenheit, reaumur>(37.0);
+  EXPECT_NEAR(2.222, test, 5.0e-3);
+}
+
+TEST_F(UnitConversion, luminous_intensity) {
+  double test;
+
+  test = convert<candela, millicandela>(72.0);
+  EXPECT_NEAR(72000.0, test, 5.0e-5);
+  test = convert<millicandela, candela>(376.0);
+  EXPECT_NEAR(0.376, test, 5.0e-5);
+}
+
+TEST_F(UnitConversion, solid_angle) {
+  double test;
+  bool same;
+
+  same = std::is_same<traits::base_unit_of<steradians>,
+                      traits::base_unit_of<degrees_squared>>::value;
+  EXPECT_TRUE(same);
+
+  test = convert<steradians, steradians>(72.0);
+  EXPECT_NEAR(72.0, test, 5.0e-5);
+  test = convert<steradians, degrees_squared>(1.0);
+  EXPECT_NEAR(3282.8, test, 5.0e-2);
+  test = convert<steradians, spats>(8.0);
+  EXPECT_NEAR(0.636619772367582, test, 5.0e-14);
+  test = convert<degrees_squared, steradians>(3282.8);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<degrees_squared, degrees_squared>(72.0);
+  EXPECT_NEAR(72.0, test, 5.0e-5);
+  test = convert<degrees_squared, spats>(3282.8);
+  EXPECT_NEAR(1.0 / (4 * constants::detail::PI_VAL), test, 5.0e-5);
+  test = convert<spats, steradians>(1.0 / (4 * constants::detail::PI_VAL));
+  EXPECT_NEAR(1.0, test, 5.0e-14);
+  test = convert<spats, degrees_squared>(1.0 / (4 * constants::detail::PI_VAL));
+  EXPECT_NEAR(3282.8, test, 5.0e-2);
+  test = convert<spats, spats>(72.0);
+  EXPECT_NEAR(72.0, test, 5.0e-5);
+}
+
+TEST_F(UnitConversion, frequency) {
+  double test;
+
+  test = convert<hertz, kilohertz>(63000.0);
+  EXPECT_NEAR(63.0, test, 5.0e-5);
+  test = convert<hertz, hertz>(6.3);
+  EXPECT_NEAR(6.3, test, 5.0e-5);
+  test = convert<kilohertz, hertz>(5.0);
+  EXPECT_NEAR(5000.0, test, 5.0e-5);
+  test = convert<megahertz, hertz>(1.0);
+  EXPECT_NEAR(1.0e6, test, 5.0e-5);
+}
+
+TEST_F(UnitConversion, velocity) {
+  double test;
+  bool same;
+
+  same = std::is_same<meters_per_second,
+                      unit<std::ratio<1>, category::velocity_unit>>::value;
+  EXPECT_TRUE(same);
+  same = traits::is_convertible_unit<miles_per_hour, meters_per_second>::value;
+  EXPECT_TRUE(same);
+
+  test = convert<meters_per_second, miles_per_hour>(1250.0);
+  EXPECT_NEAR(2796.17, test, 5.0e-3);
+  test = convert<feet_per_second, kilometers_per_hour>(2796.17);
+  EXPECT_NEAR(3068.181418, test, 5.0e-7);
+  test = convert<knots, miles_per_hour>(600.0);
+  EXPECT_NEAR(690.468, test, 5.0e-4);
+  test = convert<miles_per_hour, feet_per_second>(120.0);
+  EXPECT_NEAR(176.0, test, 5.0e-5);
+  test = convert<feet_per_second, meters_per_second>(10.0);
+  EXPECT_NEAR(3.048, test, 5.0e-5);
+}
+
+TEST_F(UnitConversion, angular_velocity) {
+  double test;
+  bool same;
+
+  same =
+      std::is_same<radians_per_second,
+                   unit<std::ratio<1>, category::angular_velocity_unit>>::value;
+  EXPECT_TRUE(same);
+  same = traits::is_convertible_unit<rpm, radians_per_second>::value;
+  EXPECT_TRUE(same);
+
+  test = convert<radians_per_second, milliarcseconds_per_year>(1.0);
+  EXPECT_NEAR(6.504e15, test, 1.0e12);
+  test = convert<degrees_per_second, radians_per_second>(1.0);
+  EXPECT_NEAR(0.0174533, test, 5.0e-8);
+  test = convert<rpm, radians_per_second>(1.0);
+  EXPECT_NEAR(0.10471975512, test, 5.0e-13);
+  test = convert<milliarcseconds_per_year, radians_per_second>(1.0);
+  EXPECT_NEAR(1.537e-16, test, 5.0e-20);
+}
+
+TEST_F(UnitConversion, acceleration) {
+  double test;
+
+  test = convert<standard_gravity, meters_per_second_squared>(1.0);
+  EXPECT_NEAR(9.80665, test, 5.0e-10);
+}
+TEST_F(UnitConversion, force) {
+  double test;
+
+  test = convert<units::force::newton, units::force::newton>(1.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<units::force::newton, units::force::pounds>(6.3);
+  EXPECT_NEAR(1.4163, test, 5.0e-5);
+  test = convert<units::force::newton, units::force::dynes>(5.0);
+  EXPECT_NEAR(500000.0, test, 5.0e-5);
+  test = convert<units::force::newtons, units::force::poundals>(2.1);
+  EXPECT_NEAR(15.1893, test, 5.0e-5);
+  test = convert<units::force::newtons, units::force::kiloponds>(173.0);
+  EXPECT_NEAR(17.6411, test, 5.0e-5);
+  test = convert<units::force::poundals, units::force::kiloponds>(21.879);
+  EXPECT_NEAR(0.308451933, test, 5.0e-10);
+}
+
+TEST_F(UnitConversion, area) {
+  double test;
+
+  test = convert<hectares, acres>(6.3);
+  EXPECT_NEAR(15.5676, test, 5.0e-5);
+  test = convert<square_miles, square_kilometers>(10.0);
+  EXPECT_NEAR(25.8999, test, 5.0e-5);
+  test = convert<square_inch, square_meter>(4.0);
+  EXPECT_NEAR(0.00258064, test, 5.0e-9);
+  test = convert<acre, square_foot>(5.0);
+  EXPECT_NEAR(217800.0, test, 5.0e-5);
+  test = convert<square_meter, square_foot>(1.0);
+  EXPECT_NEAR(10.7639, test, 5.0e-5);
+}
+
+TEST_F(UnitConversion, pressure) {
+  double test;
+
+  test = convert<pascals, torr>(1.0);
+  EXPECT_NEAR(0.00750062, test, 5.0e-5);
+  test = convert<bar, psi>(2.2);
+  EXPECT_NEAR(31.9083, test, 5.0e-5);
+  test = convert<atmospheres, bar>(4.0);
+  EXPECT_NEAR(4.053, test, 5.0e-5);
+  test = convert<torr, pascals>(800.0);
+  EXPECT_NEAR(106657.89474, test, 5.0e-5);
+  test = convert<psi, atmospheres>(38.0);
+  EXPECT_NEAR(2.58575, test, 5.0e-5);
+  test = convert<psi, pascals>(1.0);
+  EXPECT_NEAR(6894.76, test, 5.0e-3);
+  test = convert<pascals, bar>(0.25);
+  EXPECT_NEAR(2.5e-6, test, 5.0e-5);
+  test = convert<torr, atmospheres>(9.0);
+  EXPECT_NEAR(0.0118421, test, 5.0e-8);
+  test = convert<bar, torr>(12.0);
+  EXPECT_NEAR(9000.74, test, 5.0e-3);
+  test = convert<atmospheres, psi>(1.0);
+  EXPECT_NEAR(14.6959, test, 5.0e-5);
+}
+
+TEST_F(UnitConversion, charge) {
+  double test;
+
+  test = convert<coulombs, ampere_hours>(4.0);
+  EXPECT_NEAR(0.00111111, test, 5.0e-9);
+  test = convert<ampere_hours, coulombs>(1.0);
+  EXPECT_NEAR(3600.0, test, 5.0e-6);
+}
+
+TEST_F(UnitConversion, energy) {
+  double test;
+
+  test = convert<joules, calories>(8000.000464);
+  EXPECT_NEAR(1912.046, test, 5.0e-4);
+  test = convert<therms, joules>(12.0);
+  EXPECT_NEAR(1.266e+9, test, 5.0e5);
+  test = convert<megajoules, watt_hours>(100.0);
+  EXPECT_NEAR(27777.778, test, 5.0e-4);
+  test = convert<kilocalories, megajoules>(56.0);
+  EXPECT_NEAR(0.234304, test, 5.0e-7);
+  test = convert<kilojoules, therms>(56.0);
+  EXPECT_NEAR(0.000530904, test, 5.0e-5);
+  test = convert<british_thermal_units, kilojoules>(18.56399995447);
+  EXPECT_NEAR(19.5860568, test, 5.0e-5);
+  test = convert<calories, energy::foot_pounds>(18.56399995447);
+  EXPECT_NEAR(57.28776190423856, test, 5.0e-5);
+  test = convert<megajoules, calories>(1.0);
+  EXPECT_NEAR(239006.0, test, 5.0e-1);
+  test = convert<kilocalories, kilowatt_hours>(2.0);
+  EXPECT_NEAR(0.00232444, test, 5.0e-9);
+  test = convert<therms, kilocalories>(0.1);
+  EXPECT_NEAR(2521.04, test, 5.0e-3);
+  test = convert<watt_hours, megajoules>(67.0);
+  EXPECT_NEAR(0.2412, test, 5.0e-5);
+  test = convert<british_thermal_units, watt_hours>(100.0);
+  EXPECT_NEAR(29.3071, test, 5.0e-5);
+  test = convert<calories, BTU>(100.0);
+  EXPECT_NEAR(0.396567, test, 5.0e-5);
+}
+
+TEST_F(UnitConversion, power) {
+  double test;
+
+  test = convert<compound_unit<energy::foot_pounds, inverse<seconds>>, watts>(
+      550.0);
+  EXPECT_NEAR(745.7, test, 5.0e-2);
+  test = convert<watts, gigawatts>(1000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-4);
+  test = convert<microwatts, watts>(200000.0);
+  EXPECT_NEAR(0.2, test, 5.0e-4);
+  test = convert<horsepower, watts>(100.0);
+  EXPECT_NEAR(74570.0, test, 5.0e-1);
+  test = convert<horsepower, megawatts>(5.0);
+  EXPECT_NEAR(0.0037284994, test, 5.0e-7);
+  test = convert<kilowatts, horsepower>(232.0);
+  EXPECT_NEAR(311.117, test, 5.0e-4);
+  test = convert<milliwatts, horsepower>(1001.0);
+  EXPECT_NEAR(0.001342363, test, 5.0e-9);
+}
+
+TEST_F(UnitConversion, voltage) {
+  double test;
+
+  test = convert<volts, millivolts>(10.0);
+  EXPECT_NEAR(10000.0, test, 5.0e-5);
+  test = convert<picovolts, volts>(1000000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<nanovolts, volts>(1000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<microvolts, volts>(1000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<millivolts, volts>(1000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<kilovolts, volts>(0.001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<megavolts, volts>(0.000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<gigavolts, volts>(0.000000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<statvolts, volts>(299.792458);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<millivolts, statvolts>(1000.0);
+  EXPECT_NEAR(299.792458, test, 5.0e-5);
+  test = convert<abvolts, nanovolts>(0.1);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<microvolts, abvolts>(0.01);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+}
+
+TEST_F(UnitConversion, capacitance) {
+  double test;
+
+  test = convert<farads, millifarads>(10.0);
+  EXPECT_NEAR(10000.0, test, 5.0e-5);
+  test = convert<picofarads, farads>(1000000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<nanofarads, farads>(1000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<microfarads, farads>(1000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<millifarads, farads>(1000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<kilofarads, farads>(0.001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<megafarads, farads>(0.000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<gigafarads, farads>(0.000000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+}
+
+TEST_F(UnitConversion, impedance) {
+  double test;
+
+  test = convert<ohms, milliohms>(10.0);
+  EXPECT_NEAR(10000.0, test, 5.0e-5);
+  test = convert<picoohms, ohms>(1000000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<nanoohms, ohms>(1000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<microohms, ohms>(1000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<milliohms, ohms>(1000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<kiloohms, ohms>(0.001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<megaohms, ohms>(0.000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<gigaohms, ohms>(0.000000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+}
+
+TEST_F(UnitConversion, conductance) {
+  double test;
+
+  test = convert<siemens, millisiemens>(10.0);
+  EXPECT_NEAR(10000.0, test, 5.0e-5);
+  test = convert<picosiemens, siemens>(1000000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<nanosiemens, siemens>(1000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<microsiemens, siemens>(1000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<millisiemens, siemens>(1000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<kilosiemens, siemens>(0.001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<megasiemens, siemens>(0.000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<gigasiemens, siemens>(0.000000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+}
+
+TEST_F(UnitConversion, magnetic_flux) {
+  double test;
+
+  test = convert<webers, milliwebers>(10.0);
+  EXPECT_NEAR(10000.0, test, 5.0e-5);
+  test = convert<picowebers, webers>(1000000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<nanowebers, webers>(1000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<microwebers, webers>(1000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<milliwebers, webers>(1000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<kilowebers, webers>(0.001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<megawebers, webers>(0.000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<gigawebers, webers>(0.000000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<maxwells, webers>(100000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<nanowebers, maxwells>(10.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+}
+
+TEST_F(UnitConversion, magnetic_field_strength) {
+  double test;
+
+  test = convert<teslas, milliteslas>(10.0);
+  EXPECT_NEAR(10000.0, test, 5.0e-5);
+  test = convert<picoteslas, teslas>(1000000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<nanoteslas, teslas>(1000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<microteslas, teslas>(1000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<milliteslas, teslas>(1000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<kiloteslas, teslas>(0.001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<megateslas, teslas>(0.000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<gigateslas, teslas>(0.000000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<gauss, teslas>(10000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<nanoteslas, gauss>(100000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+}
+
+TEST_F(UnitConversion, inductance) {
+  double test;
+
+  test = convert<henries, millihenries>(10.0);
+  EXPECT_NEAR(10000.0, test, 5.0e-5);
+  test = convert<picohenries, henries>(1000000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<nanohenries, henries>(1000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<microhenries, henries>(1000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<millihenries, henries>(1000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<kilohenries, henries>(0.001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<megahenries, henries>(0.000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<gigahenries, henries>(0.000000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+}
+
+TEST_F(UnitConversion, luminous_flux) {
+  double test;
+
+  test = convert<lumens, millilumens>(10.0);
+  EXPECT_NEAR(10000.0, test, 5.0e-5);
+  test = convert<picolumens, lumens>(1000000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<nanolumens, lumens>(1000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<microlumens, lumens>(1000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<millilumens, lumens>(1000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<kilolumens, lumens>(0.001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<megalumens, lumens>(0.000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<gigalumens, lumens>(0.000000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+}
+
+TEST_F(UnitConversion, illuminance) {
+  double test;
+
+  test = convert<luxes, milliluxes>(10.0);
+  EXPECT_NEAR(10000.0, test, 5.0e-5);
+  test = convert<picoluxes, luxes>(1000000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<nanoluxes, luxes>(1000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<microluxes, luxes>(1000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<milliluxes, luxes>(1000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<kiloluxes, luxes>(0.001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<megaluxes, luxes>(0.000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<gigaluxes, luxes>(0.000000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+
+  test = convert<footcandles, luxes>(0.092903);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<lux, lumens_per_square_inch>(1550.0031000062);
+  EXPECT_NEAR(1.0, test, 5.0e-13);
+  test = convert<phots, luxes>(0.0001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+}
+
+TEST_F(UnitConversion, radiation) {
+  double test;
+
+  test = convert<becquerels, millibecquerels>(10.0);
+  EXPECT_NEAR(10000.0, test, 5.0e-5);
+  test = convert<picobecquerels, becquerels>(1000000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<nanobecquerels, becquerels>(1000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<microbecquerels, becquerels>(1000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<millibecquerels, becquerels>(1000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<kilobecquerels, becquerels>(0.001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<megabecquerels, becquerels>(0.000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<gigabecquerels, becquerels>(0.000000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+
+  test = convert<grays, milligrays>(10.0);
+  EXPECT_NEAR(10000.0, test, 5.0e-5);
+  test = convert<picograys, grays>(1000000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<nanograys, grays>(1000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<micrograys, grays>(1000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<milligrays, grays>(1000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<kilograys, grays>(0.001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<megagrays, grays>(0.000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<gigagrays, grays>(0.000000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+
+  test = convert<sieverts, millisieverts>(10.0);
+  EXPECT_NEAR(10000.0, test, 5.0e-5);
+  test = convert<picosieverts, sieverts>(1000000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<nanosieverts, sieverts>(1000000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<microsieverts, sieverts>(1000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<millisieverts, sieverts>(1000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<kilosieverts, sieverts>(0.001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<megasieverts, sieverts>(0.000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<gigasieverts, sieverts>(0.000000001);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+
+  test = convert<becquerels, curies>(37.0e9);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<becquerels, rutherfords>(1000000.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<rads, grays>(100.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+}
+
+TEST_F(UnitConversion, torque) {
+  double test;
+
+  test = convert<torque::foot_pounds, newton_meter>(1.0);
+  EXPECT_NEAR(1.355817948, test, 5.0e-5);
+  test = convert<inch_pounds, newton_meter>(1.0);
+  EXPECT_NEAR(0.112984829, test, 5.0e-5);
+  test = convert<foot_poundals, newton_meter>(1.0);
+  EXPECT_NEAR(4.214011009e-2, test, 5.0e-5);
+  test = convert<meter_kilograms, newton_meter>(1.0);
+  EXPECT_NEAR(9.80665, test, 5.0e-5);
+  test = convert<inch_pound, meter_kilogram>(86.79616930855788);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<foot_poundals, inch_pound>(2.681170713);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+}
+
+TEST_F(UnitConversion, volume) {
+  double test;
+
+  test = convert<cubic_meters, cubic_meter>(1.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<cubic_millimeters, cubic_meter>(1.0);
+  EXPECT_NEAR(1.0e-9, test, 5.0e-5);
+  test = convert<cubic_kilometers, cubic_meter>(1.0);
+  EXPECT_NEAR(1.0e9, test, 5.0e-5);
+  test = convert<liters, cubic_meter>(1.0);
+  EXPECT_NEAR(0.001, test, 5.0e-5);
+  test = convert<milliliters, cubic_meter>(1.0);
+  EXPECT_NEAR(1.0e-6, test, 5.0e-5);
+  test = convert<cubic_inches, cubic_meter>(1.0);
+  EXPECT_NEAR(1.6387e-5, test, 5.0e-10);
+  test = convert<cubic_feet, cubic_meter>(1.0);
+  EXPECT_NEAR(0.0283168, test, 5.0e-8);
+  test = convert<cubic_yards, cubic_meter>(1.0);
+  EXPECT_NEAR(0.764555, test, 5.0e-7);
+  test = convert<cubic_miles, cubic_meter>(1.0);
+  EXPECT_NEAR(4.168e+9, test, 5.0e5);
+  test = convert<gallons, cubic_meter>(1.0);
+  EXPECT_NEAR(0.00378541, test, 5.0e-8);
+  test = convert<quarts, cubic_meter>(1.0);
+  EXPECT_NEAR(0.000946353, test, 5.0e-10);
+  test = convert<pints, cubic_meter>(1.0);
+  EXPECT_NEAR(0.000473176, test, 5.0e-10);
+  test = convert<cups, cubic_meter>(1.0);
+  EXPECT_NEAR(0.00024, test, 5.0e-6);
+  test = convert<volume::fluid_ounces, cubic_meter>(1.0);
+  EXPECT_NEAR(2.9574e-5, test, 5.0e-5);
+  test = convert<barrels, cubic_meter>(1.0);
+  EXPECT_NEAR(0.158987294928, test, 5.0e-13);
+  test = convert<bushels, cubic_meter>(1.0);
+  EXPECT_NEAR(0.0352391, test, 5.0e-8);
+  test = convert<cords, cubic_meter>(1.0);
+  EXPECT_NEAR(3.62456, test, 5.0e-6);
+  test = convert<cubic_fathoms, cubic_meter>(1.0);
+  EXPECT_NEAR(6.11644, test, 5.0e-6);
+  test = convert<tablespoons, cubic_meter>(1.0);
+  EXPECT_NEAR(1.4787e-5, test, 5.0e-10);
+  test = convert<teaspoons, cubic_meter>(1.0);
+  EXPECT_NEAR(4.9289e-6, test, 5.0e-11);
+  test = convert<pinches, cubic_meter>(1.0);
+  EXPECT_NEAR(616.11519921875e-9, test, 5.0e-20);
+  test = convert<dashes, cubic_meter>(1.0);
+  EXPECT_NEAR(308.057599609375e-9, test, 5.0e-20);
+  test = convert<drops, cubic_meter>(1.0);
+  EXPECT_NEAR(82.14869322916e-9, test, 5.0e-9);
+  test = convert<fifths, cubic_meter>(1.0);
+  EXPECT_NEAR(0.00075708236, test, 5.0e-12);
+  test = convert<drams, cubic_meter>(1.0);
+  EXPECT_NEAR(3.69669e-6, test, 5.0e-12);
+  test = convert<gills, cubic_meter>(1.0);
+  EXPECT_NEAR(0.000118294, test, 5.0e-10);
+  test = convert<pecks, cubic_meter>(1.0);
+  EXPECT_NEAR(0.00880977, test, 5.0e-9);
+  test = convert<sacks, cubic_meter>(9.4591978);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<shots, cubic_meter>(1.0);
+  EXPECT_NEAR(4.43603e-5, test, 5.0e-11);
+  test = convert<strikes, cubic_meter>(1.0);
+  EXPECT_NEAR(0.07047814033376, test, 5.0e-5);
+  test = convert<volume::fluid_ounces, milliliters>(1.0);
+  EXPECT_NEAR(29.5735, test, 5.0e-5);
+}
+
+TEST_F(UnitConversion, density) {
+  double test;
+
+  test = convert<kilograms_per_cubic_meter, kilograms_per_cubic_meter>(1.0);
+  EXPECT_NEAR(1.0, test, 5.0e-5);
+  test = convert<grams_per_milliliter, kilograms_per_cubic_meter>(1.0);
+  EXPECT_NEAR(1000.0, test, 5.0e-5);
+  test = convert<kilograms_per_liter, kilograms_per_cubic_meter>(1.0);
+  EXPECT_NEAR(1000.0, test, 5.0e-5);
+  test = convert<ounces_per_cubic_foot, kilograms_per_cubic_meter>(1.0);
+  EXPECT_NEAR(1.001153961, test, 5.0e-10);
+  test = convert<ounces_per_cubic_inch, kilograms_per_cubic_meter>(1.0);
+  EXPECT_NEAR(1.729994044e3, test, 5.0e-7);
+  test = convert<ounces_per_gallon, kilograms_per_cubic_meter>(1.0);
+  EXPECT_NEAR(7.489151707, test, 5.0e-10);
+  test = convert<pounds_per_cubic_foot, kilograms_per_cubic_meter>(1.0);
+  EXPECT_NEAR(16.01846337, test, 5.0e-9);
+  test = convert<pounds_per_cubic_inch, kilograms_per_cubic_meter>(1.0);
+  EXPECT_NEAR(2.767990471e4, test, 5.0e-6);
+  test = convert<pounds_per_gallon, kilograms_per_cubic_meter>(1.0);
+  EXPECT_NEAR(119.8264273, test, 5.0e-8);
+  test = convert<slugs_per_cubic_foot, kilograms_per_cubic_meter>(1.0);
+  EXPECT_NEAR(515.3788184, test, 5.0e-6);
+}
+
+TEST_F(UnitConversion, concentration) {
+  double test;
+
+  test = ppm_t(1.0);
+  EXPECT_NEAR(1.0e-6, test, 5.0e-12);
+  test = ppb_t(1.0);
+  EXPECT_NEAR(1.0e-9, test, 5.0e-12);
+  test = ppt_t(1.0);
+  EXPECT_NEAR(1.0e-12, test, 5.0e-12);
+  test = percent_t(18.0);
+  EXPECT_NEAR(0.18, test, 5.0e-12);
+}
+
+TEST_F(UnitConversion, data) {
+  EXPECT_EQ(8, (convert<byte, bit>(1)));
+
+  EXPECT_EQ(1000, (convert<kilobytes, bytes>(1)));
+  EXPECT_EQ(1000, (convert<megabytes, kilobytes>(1)));
+  EXPECT_EQ(1000, (convert<gigabytes, megabytes>(1)));
+  EXPECT_EQ(1000, (convert<terabytes, gigabytes>(1)));
+  EXPECT_EQ(1000, (convert<petabytes, terabytes>(1)));
+  EXPECT_EQ(1000, (convert<exabytes, petabytes>(1)));
+
+  EXPECT_EQ(1024, (convert<kibibytes, bytes>(1)));
+  EXPECT_EQ(1024, (convert<mebibytes, kibibytes>(1)));
+  EXPECT_EQ(1024, (convert<gibibytes, mebibytes>(1)));
+  EXPECT_EQ(1024, (convert<tebibytes, gibibytes>(1)));
+  EXPECT_EQ(1024, (convert<pebibytes, tebibytes>(1)));
+  EXPECT_EQ(1024, (convert<exbibytes, pebibytes>(1)));
+
+  EXPECT_EQ(93750000, (convert<gigabytes, kibibits>(12)));
+
+  EXPECT_EQ(1000, (convert<kilobits, bits>(1)));
+  EXPECT_EQ(1000, (convert<megabits, kilobits>(1)));
+  EXPECT_EQ(1000, (convert<gigabits, megabits>(1)));
+  EXPECT_EQ(1000, (convert<terabits, gigabits>(1)));
+  EXPECT_EQ(1000, (convert<petabits, terabits>(1)));
+  EXPECT_EQ(1000, (convert<exabits, petabits>(1)));
+
+  EXPECT_EQ(1024, (convert<kibibits, bits>(1)));
+  EXPECT_EQ(1024, (convert<mebibits, kibibits>(1)));
+  EXPECT_EQ(1024, (convert<gibibits, mebibits>(1)));
+  EXPECT_EQ(1024, (convert<tebibits, gibibits>(1)));
+  EXPECT_EQ(1024, (convert<pebibits, tebibits>(1)));
+  EXPECT_EQ(1024, (convert<exbibits, pebibits>(1)));
+
+  // Source: https://en.wikipedia.org/wiki/Binary_prefix
+  EXPECT_NEAR(percent_t(2.4), kibibyte_t(1) / kilobyte_t(1) - 1, 0.005);
+  EXPECT_NEAR(percent_t(4.9), mebibyte_t(1) / megabyte_t(1) - 1, 0.005);
+  EXPECT_NEAR(percent_t(7.4), gibibyte_t(1) / gigabyte_t(1) - 1, 0.005);
+  EXPECT_NEAR(percent_t(10.0), tebibyte_t(1) / terabyte_t(1) - 1, 0.005);
+  EXPECT_NEAR(percent_t(12.6), pebibyte_t(1) / petabyte_t(1) - 1, 0.005);
+  EXPECT_NEAR(percent_t(15.3), exbibyte_t(1) / exabyte_t(1) - 1, 0.005);
+}
+
+TEST_F(UnitConversion, data_transfer_rate) {
+  EXPECT_EQ(8, (convert<bytes_per_second, bits_per_second>(1)));
+
+  EXPECT_EQ(1000, (convert<kilobytes_per_second, bytes_per_second>(1)));
+  EXPECT_EQ(1000, (convert<megabytes_per_second, kilobytes_per_second>(1)));
+  EXPECT_EQ(1000, (convert<gigabytes_per_second, megabytes_per_second>(1)));
+  EXPECT_EQ(1000, (convert<terabytes_per_second, gigabytes_per_second>(1)));
+  EXPECT_EQ(1000, (convert<petabytes_per_second, terabytes_per_second>(1)));
+  EXPECT_EQ(1000, (convert<exabytes_per_second, petabytes_per_second>(1)));
+
+  EXPECT_EQ(1024, (convert<kibibytes_per_second, bytes_per_second>(1)));
+  EXPECT_EQ(1024, (convert<mebibytes_per_second, kibibytes_per_second>(1)));
+  EXPECT_EQ(1024, (convert<gibibytes_per_second, mebibytes_per_second>(1)));
+  EXPECT_EQ(1024, (convert<tebibytes_per_second, gibibytes_per_second>(1)));
+  EXPECT_EQ(1024, (convert<pebibytes_per_second, tebibytes_per_second>(1)));
+  EXPECT_EQ(1024, (convert<exbibytes_per_second, pebibytes_per_second>(1)));
+
+  EXPECT_EQ(93750000, (convert<gigabytes_per_second, kibibits_per_second>(12)));
+
+  EXPECT_EQ(1000, (convert<kilobits_per_second, bits_per_second>(1)));
+  EXPECT_EQ(1000, (convert<megabits_per_second, kilobits_per_second>(1)));
+  EXPECT_EQ(1000, (convert<gigabits_per_second, megabits_per_second>(1)));
+  EXPECT_EQ(1000, (convert<terabits_per_second, gigabits_per_second>(1)));
+  EXPECT_EQ(1000, (convert<petabits_per_second, terabits_per_second>(1)));
+  EXPECT_EQ(1000, (convert<exabits_per_second, petabits_per_second>(1)));
+
+  EXPECT_EQ(1024, (convert<kibibits_per_second, bits_per_second>(1)));
+  EXPECT_EQ(1024, (convert<mebibits_per_second, kibibits_per_second>(1)));
+  EXPECT_EQ(1024, (convert<gibibits_per_second, mebibits_per_second>(1)));
+  EXPECT_EQ(1024, (convert<tebibits_per_second, gibibits_per_second>(1)));
+  EXPECT_EQ(1024, (convert<pebibits_per_second, tebibits_per_second>(1)));
+  EXPECT_EQ(1024, (convert<exbibits_per_second, pebibits_per_second>(1)));
+
+  // Source: https://en.wikipedia.org/wiki/Binary_prefix
+  EXPECT_NEAR(percent_t(2.4),
+              kibibytes_per_second_t(1) / kilobytes_per_second_t(1) - 1, 0.005);
+  EXPECT_NEAR(percent_t(4.9),
+              mebibytes_per_second_t(1) / megabytes_per_second_t(1) - 1, 0.005);
+  EXPECT_NEAR(percent_t(7.4),
+              gibibytes_per_second_t(1) / gigabytes_per_second_t(1) - 1, 0.005);
+  EXPECT_NEAR(percent_t(10.0),
+              tebibytes_per_second_t(1) / terabytes_per_second_t(1) - 1, 0.005);
+  EXPECT_NEAR(percent_t(12.6),
+              pebibytes_per_second_t(1) / petabytes_per_second_t(1) - 1, 0.005);
+  EXPECT_NEAR(percent_t(15.3),
+              exbibytes_per_second_t(1) / exabytes_per_second_t(1) - 1, 0.005);
+}
+
+TEST_F(UnitConversion, pi) {
+  EXPECT_TRUE(
+      units::traits::is_dimensionless_unit<decltype(constants::pi)>::value);
+  EXPECT_TRUE(units::traits::is_dimensionless_unit<constants::PI>::value);
+
+  // implicit conversion/arithmetic
+  EXPECT_NEAR(3.14159, constants::pi, 5.0e-6);
+  EXPECT_NEAR(6.28318531, (2 * constants::pi), 5.0e-9);
+  EXPECT_NEAR(6.28318531, (constants::pi + constants::pi), 5.0e-9);
+  EXPECT_NEAR(0.0, (constants::pi - constants::pi), 5.0e-9);
+  EXPECT_NEAR(31.00627668, units::math::cpow<3>(constants::pi), 5.0e-10);
+  EXPECT_NEAR(0.0322515344, (1.0 / units::math::cpow<3>(constants::pi)),
+              5.0e-11);
+  EXPECT_TRUE(constants::detail::PI_VAL == constants::pi);
+  EXPECT_TRUE(1.0 != constants::pi);
+  EXPECT_TRUE(4.0 > constants::pi);
+  EXPECT_TRUE(3.0 < constants::pi);
+  EXPECT_TRUE(constants::pi > 3.0);
+  EXPECT_TRUE(constants::pi < 4.0);
+
+  // explicit conversion
+  EXPECT_NEAR(3.14159, constants::pi.to<double>(), 5.0e-6);
+
+  // auto multiplication
+  EXPECT_TRUE(
+      (std::is_same<meter_t, decltype(constants::pi * meter_t(1))>::value));
+  EXPECT_TRUE(
+      (std::is_same<meter_t, decltype(meter_t(1) * constants::pi)>::value));
+
+  EXPECT_NEAR(constants::detail::PI_VAL,
+              (constants::pi * meter_t(1)).to<double>(), 5.0e-10);
+  EXPECT_NEAR(constants::detail::PI_VAL,
+              (meter_t(1) * constants::pi).to<double>(), 5.0e-10);
+
+  // explicit multiplication
+  meter_t a = constants::pi * meter_t(1);
+  meter_t b = meter_t(1) * constants::pi;
+
+  EXPECT_NEAR(constants::detail::PI_VAL, a.to<double>(), 5.0e-10);
+  EXPECT_NEAR(constants::detail::PI_VAL, b.to<double>(), 5.0e-10);
+
+  // auto division
+  EXPECT_TRUE(
+      (std::is_same<hertz_t, decltype(constants::pi / second_t(1))>::value));
+  EXPECT_TRUE(
+      (std::is_same<second_t, decltype(second_t(1) / constants::pi)>::value));
+
+  EXPECT_NEAR(constants::detail::PI_VAL,
+              (constants::pi / second_t(1)).to<double>(), 5.0e-10);
+  EXPECT_NEAR(1.0 / constants::detail::PI_VAL,
+              (second_t(1) / constants::pi).to<double>(), 5.0e-10);
+
+  // explicit
+  hertz_t c = constants::pi / second_t(1);
+  second_t d = second_t(1) / constants::pi;
+
+  EXPECT_NEAR(constants::detail::PI_VAL, c.to<double>(), 5.0e-10);
+  EXPECT_NEAR(1.0 / constants::detail::PI_VAL, d.to<double>(), 5.0e-10);
+}
+
+TEST_F(UnitConversion, constants) {
+  // Source: NIST "2014 CODATA recommended values"
+  EXPECT_NEAR(299792458, constants::c(), 5.0e-9);
+  EXPECT_NEAR(6.67408e-11, constants::G(), 5.0e-17);
+  EXPECT_NEAR(6.626070040e-34, constants::h(), 5.0e-44);
+  EXPECT_NEAR(1.2566370614e-6, constants::mu0(), 5.0e-17);
+  EXPECT_NEAR(8.854187817e-12, constants::epsilon0(), 5.0e-21);
+  EXPECT_NEAR(376.73031346177, constants::Z0(), 5.0e-12);
+  EXPECT_NEAR(8987551787.3681764, constants::k_e(), 5.0e-6);
+  EXPECT_NEAR(1.6021766208e-19, constants::e(), 5.0e-29);
+  EXPECT_NEAR(9.10938356e-31, constants::m_e(), 5.0e-40);
+  EXPECT_NEAR(1.672621898e-27, constants::m_p(), 5.0e-37);
+  EXPECT_NEAR(9.274009994e-24, constants::mu_B(), 5.0e-32);
+  EXPECT_NEAR(6.022140857e23, constants::N_A(), 5.0e14);
+  EXPECT_NEAR(8.3144598, constants::R(), 5.0e-8);
+  EXPECT_NEAR(1.38064852e-23, constants::k_B(), 5.0e-31);
+  EXPECT_NEAR(96485.33289, constants::F(), 5.0e-5);
+  EXPECT_NEAR(5.670367e-8, constants::sigma(), 5.0e-14);
+}
+
+TEST_F(UnitConversion, std_chrono) {
+  nanosecond_t a = std::chrono::nanoseconds(10);
+  EXPECT_EQ(nanosecond_t(10), a);
+  microsecond_t b = std::chrono::microseconds(10);
+  EXPECT_EQ(microsecond_t(10), b);
+  millisecond_t c = std::chrono::milliseconds(10);
+  EXPECT_EQ(millisecond_t(10), c);
+  second_t d = std::chrono::seconds(1);
+  EXPECT_EQ(second_t(1), d);
+  minute_t e = std::chrono::minutes(120);
+  EXPECT_EQ(minute_t(120), e);
+  hour_t f = std::chrono::hours(2);
+  EXPECT_EQ(hour_t(2), f);
+
+  std::chrono::nanoseconds g = nanosecond_t(100);
+  EXPECT_EQ(std::chrono::duration_cast<std::chrono::nanoseconds>(g).count(),
+            100);
+  std::chrono::nanoseconds h = microsecond_t(2);
+  EXPECT_EQ(std::chrono::duration_cast<std::chrono::nanoseconds>(h).count(),
+            2000);
+  std::chrono::nanoseconds i = millisecond_t(1);
+  EXPECT_EQ(std::chrono::duration_cast<std::chrono::nanoseconds>(i).count(),
+            1000000);
+  std::chrono::nanoseconds j = second_t(1);
+  EXPECT_EQ(std::chrono::duration_cast<std::chrono::nanoseconds>(j).count(),
+            1000000000);
+  std::chrono::nanoseconds k = minute_t(1);
+  EXPECT_EQ(std::chrono::duration_cast<std::chrono::nanoseconds>(k).count(),
+            60000000000);
+  std::chrono::nanoseconds l = hour_t(1);
+  EXPECT_EQ(std::chrono::duration_cast<std::chrono::nanoseconds>(l).count(),
+            3600000000000);
+}
+
+TEST_F(UnitConversion, squaredTemperature) {
+  using squared_celsius = units::compound_unit<squared<celsius>>;
+  using squared_celsius_t = units::unit_t<squared_celsius>;
+  const squared_celsius_t right(100);
+  const celsius_t rootRight = units::math::sqrt(right);
+  EXPECT_EQ(celsius_t(10), rootRight);
+}
+
+TEST_F(UnitMath, min) {
+  meter_t a(1);
+  foot_t c(1);
+  EXPECT_EQ(c, math::min(a, c));
+}
+
+TEST_F(UnitMath, max) {
+  meter_t a(1);
+  foot_t c(1);
+  EXPECT_EQ(a, math::max(a, c));
+}
+
+TEST_F(UnitMath, cos) {
+  EXPECT_TRUE((std::is_same<typename std::decay<scalar_t>::type,
+                            typename std::decay<decltype(
+                                cos(angle::radian_t(0)))>::type>::value));
+  EXPECT_NEAR(scalar_t(-0.41614683654), cos(angle::radian_t(2)), 5.0e-11);
+  EXPECT_NEAR(scalar_t(-0.70710678118), cos(angle::degree_t(135)),
+              5.0e-11);
+}
+
+TEST_F(UnitMath, sin) {
+  EXPECT_TRUE((std::is_same<typename std::decay<scalar_t>::type,
+                            typename std::decay<decltype(
+                                sin(angle::radian_t(0)))>::type>::value));
+  EXPECT_NEAR(scalar_t(0.90929742682), sin(angle::radian_t(2)), 5.0e-11);
+  EXPECT_NEAR(scalar_t(0.70710678118), sin(angle::degree_t(135)), 5.0e-11);
+}
+
+TEST_F(UnitMath, tan) {
+  EXPECT_TRUE((std::is_same<typename std::decay<scalar_t>::type,
+                            typename std::decay<decltype(
+                                tan(angle::radian_t(0)))>::type>::value));
+  EXPECT_NEAR(scalar_t(-2.18503986326), tan(angle::radian_t(2)), 5.0e-11);
+  EXPECT_NEAR(scalar_t(-1.0), tan(angle::degree_t(135)), 5.0e-11);
+}
+
+TEST_F(UnitMath, acos) {
+  EXPECT_TRUE(
+      (std::is_same<
+          typename std::decay<angle::radian_t>::type,
+          typename std::decay<decltype(acos(scalar_t(0)))>::type>::value));
+  EXPECT_NEAR(angle::radian_t(2).to<double>(),
+              acos(scalar_t(-0.41614683654)).to<double>(), 5.0e-11);
+  EXPECT_NEAR(
+      angle::degree_t(135).to<double>(),
+      angle::degree_t(acos(scalar_t(-0.70710678118654752440084436210485)))
+          .to<double>(),
+      5.0e-12);
+}
+
+TEST_F(UnitMath, asin) {
+  EXPECT_TRUE(
+      (std::is_same<
+          typename std::decay<angle::radian_t>::type,
+          typename std::decay<decltype(asin(scalar_t(0)))>::type>::value));
+  EXPECT_NEAR(angle::radian_t(1.14159265).to<double>(),
+              asin(scalar_t(0.90929742682)).to<double>(), 5.0e-9);
+  EXPECT_NEAR(
+      angle::degree_t(45).to<double>(),
+      angle::degree_t(asin(scalar_t(0.70710678118654752440084436210485)))
+          .to<double>(),
+      5.0e-12);
+}
+
+TEST_F(UnitMath, atan) {
+  EXPECT_TRUE(
+      (std::is_same<
+          typename std::decay<angle::radian_t>::type,
+          typename std::decay<decltype(atan(scalar_t(0)))>::type>::value));
+  EXPECT_NEAR(angle::radian_t(-1.14159265).to<double>(),
+              atan(scalar_t(-2.18503986326)).to<double>(), 5.0e-9);
+  EXPECT_NEAR(angle::degree_t(-45).to<double>(),
+              angle::degree_t(atan(scalar_t(-1.0))).to<double>(), 5.0e-12);
+}
+
+TEST_F(UnitMath, atan2) {
+  EXPECT_TRUE((std::is_same<typename std::decay<angle::radian_t>::type,
+                            typename std::decay<decltype(atan2(
+                                scalar_t(1), scalar_t(1)))>::type>::value));
+  EXPECT_NEAR(angle::radian_t(constants::detail::PI_VAL / 4).to<double>(),
+              atan2(scalar_t(2), scalar_t(2)).to<double>(), 5.0e-12);
+  EXPECT_NEAR(
+      angle::degree_t(45).to<double>(),
+      angle::degree_t(atan2(scalar_t(2), scalar_t(2))).to<double>(),
+      5.0e-12);
+
+  EXPECT_TRUE((std::is_same<typename std::decay<angle::radian_t>::type,
+                            typename std::decay<decltype(atan2(
+                                scalar_t(1), scalar_t(1)))>::type>::value));
+  EXPECT_NEAR(angle::radian_t(constants::detail::PI_VAL / 6).to<double>(),
+              atan2(scalar_t(1), scalar_t(std::sqrt(3))).to<double>(),
+              5.0e-12);
+  EXPECT_NEAR(angle::degree_t(30).to<double>(),
+              angle::degree_t(atan2(scalar_t(1), scalar_t(std::sqrt(3))))
+                  .to<double>(),
+              5.0e-12);
+}
+
+TEST_F(UnitMath, cosh) {
+  EXPECT_TRUE((std::is_same<typename std::decay<scalar_t>::type,
+                            typename std::decay<decltype(
+                                cosh(angle::radian_t(0)))>::type>::value));
+  EXPECT_NEAR(scalar_t(3.76219569108), cosh(angle::radian_t(2)), 5.0e-11);
+  EXPECT_NEAR(scalar_t(5.32275215), cosh(angle::degree_t(135)), 5.0e-9);
+}
+
+TEST_F(UnitMath, sinh) {
+  EXPECT_TRUE((std::is_same<typename std::decay<scalar_t>::type,
+                            typename std::decay<decltype(
+                                sinh(angle::radian_t(0)))>::type>::value));
+  EXPECT_NEAR(scalar_t(3.62686040785), sinh(angle::radian_t(2)), 5.0e-11);
+  EXPECT_NEAR(scalar_t(5.22797192), sinh(angle::degree_t(135)), 5.0e-9);
+}
+
+TEST_F(UnitMath, tanh) {
+  EXPECT_TRUE((std::is_same<typename std::decay<scalar_t>::type,
+                            typename std::decay<decltype(
+                                tanh(angle::radian_t(0)))>::type>::value));
+  EXPECT_NEAR(scalar_t(0.96402758007), tanh(angle::radian_t(2)), 5.0e-11);
+  EXPECT_NEAR(scalar_t(0.98219338), tanh(angle::degree_t(135)), 5.0e-11);
+}
+
+TEST_F(UnitMath, acosh) {
+  EXPECT_TRUE((std::is_same<typename std::decay<angle::radian_t>::type,
+                            typename std::decay<decltype(
+                                acosh(scalar_t(0)))>::type>::value));
+  EXPECT_NEAR(angle::radian_t(1.316957896924817).to<double>(),
+              acosh(scalar_t(2.0)).to<double>(), 5.0e-11);
+  EXPECT_NEAR(angle::degree_t(75.456129290216893).to<double>(),
+              angle::degree_t(acosh(scalar_t(2.0))).to<double>(), 5.0e-12);
+}
+
+TEST_F(UnitMath, asinh) {
+  EXPECT_TRUE((std::is_same<typename std::decay<angle::radian_t>::type,
+                            typename std::decay<decltype(
+                                asinh(scalar_t(0)))>::type>::value));
+  EXPECT_NEAR(angle::radian_t(1.443635475178810).to<double>(),
+              asinh(scalar_t(2)).to<double>(), 5.0e-9);
+  EXPECT_NEAR(angle::degree_t(82.714219883108939).to<double>(),
+              angle::degree_t(asinh(scalar_t(2))).to<double>(), 5.0e-12);
+}
+
+TEST_F(UnitMath, atanh) {
+  EXPECT_TRUE((std::is_same<typename std::decay<angle::radian_t>::type,
+                            typename std::decay<decltype(
+                                atanh(scalar_t(0)))>::type>::value));
+  EXPECT_NEAR(angle::radian_t(0.549306144334055).to<double>(),
+              atanh(scalar_t(0.5)).to<double>(), 5.0e-9);
+  EXPECT_NEAR(angle::degree_t(31.472923730945389).to<double>(),
+              angle::degree_t(atanh(scalar_t(0.5))).to<double>(), 5.0e-12);
+}
+
+TEST_F(UnitMath, exp) {
+  double val = 10.0;
+  EXPECT_EQ(std::exp(val), exp(scalar_t(val)));
+}
+
+TEST_F(UnitMath, log) {
+  double val = 100.0;
+  EXPECT_EQ(std::log(val), log(scalar_t(val)));
+}
+
+TEST_F(UnitMath, log10) {
+  double val = 100.0;
+  EXPECT_EQ(std::log10(val), log10(scalar_t(val)));
+}
+
+TEST_F(UnitMath, modf) {
+  double val = 100.0;
+  double modfr1;
+  scalar_t modfr2;
+  EXPECT_EQ(std::modf(val, &modfr1), modf(scalar_t(val), &modfr2));
+  EXPECT_EQ(modfr1, modfr2);
+}
+
+TEST_F(UnitMath, exp2) {
+  double val = 10.0;
+  EXPECT_EQ(std::exp2(val), exp2(scalar_t(val)));
+}
+
+TEST_F(UnitMath, expm1) {
+  double val = 10.0;
+  EXPECT_EQ(std::expm1(val), expm1(scalar_t(val)));
+}
+
+TEST_F(UnitMath, log1p) {
+  double val = 10.0;
+  EXPECT_EQ(std::log1p(val), log1p(scalar_t(val)));
+}
+
+TEST_F(UnitMath, log2) {
+  double val = 10.0;
+  EXPECT_EQ(std::log2(val), log2(scalar_t(val)));
+}
+
+TEST_F(UnitMath, pow) {
+  bool isSame;
+  meter_t value(10.0);
+
+  auto sq = pow<2>(value);
+  EXPECT_NEAR(100.0, sq(), 5.0e-2);
+  isSame = std::is_same<decltype(sq), square_meter_t>::value;
+  EXPECT_TRUE(isSame);
+
+  auto cube = pow<3>(value);
+  EXPECT_NEAR(1000.0, cube(), 5.0e-2);
+  isSame = std::is_same<decltype(cube), unit_t<cubed<meter>>>::value;
+  EXPECT_TRUE(isSame);
+
+  auto fourth = pow<4>(value);
+  EXPECT_NEAR(10000.0, fourth(), 5.0e-2);
+  isSame = std::is_same<
+      decltype(fourth),
+      unit_t<compound_unit<squared<meter>, squared<meter>>>>::value;
+  EXPECT_TRUE(isSame);
+}
+
+TEST_F(UnitMath, sqrt) {
+  EXPECT_TRUE((std::is_same<typename std::decay<meter_t>::type,
+                            typename std::decay<decltype(sqrt(
+                                square_meter_t(4.0)))>::type>::value));
+  EXPECT_NEAR(meter_t(2.0).to<double>(),
+              sqrt(square_meter_t(4.0)).to<double>(), 5.0e-9);
+
+  EXPECT_TRUE((std::is_same<typename std::decay<angle::radian_t>::type,
+                            typename std::decay<decltype(
+                                sqrt(steradian_t(16.0)))>::type>::value));
+  EXPECT_NEAR(angle::radian_t(4.0).to<double>(),
+              sqrt(steradian_t(16.0)).to<double>(), 5.0e-9);
+
+  EXPECT_TRUE((std::is_convertible<typename std::decay<foot_t>::type,
+                                   typename std::decay<decltype(sqrt(
+                                       square_foot_t(10.0)))>::type>::value));
+
+  // for rational conversion (i.e. no integral root) let's check a bunch of
+  // different ways this could go wrong
+  foot_t resultFt = sqrt(square_foot_t(10.0));
+  EXPECT_NEAR(foot_t(3.16227766017).to<double>(),
+              sqrt(square_foot_t(10.0)).to<double>(), 5.0e-9);
+  EXPECT_NEAR(foot_t(3.16227766017).to<double>(), resultFt.to<double>(),
+              5.0e-9);
+  EXPECT_EQ(resultFt, sqrt(square_foot_t(10.0)));
+}
+
+TEST_F(UnitMath, hypot) {
+  EXPECT_TRUE((std::is_same<typename std::decay<meter_t>::type,
+                            typename std::decay<decltype(hypot(
+                                meter_t(3.0), meter_t(4.0)))>::type>::value));
+  EXPECT_NEAR(meter_t(5.0).to<double>(),
+              (hypot(meter_t(3.0), meter_t(4.0))).to<double>(), 5.0e-9);
+
+  EXPECT_TRUE((std::is_same<typename std::decay<foot_t>::type,
+                            typename std::decay<decltype(hypot(
+                                foot_t(3.0), meter_t(1.2192)))>::type>::value));
+  EXPECT_NEAR(foot_t(5.0).to<double>(),
+              (hypot(foot_t(3.0), meter_t(1.2192))).to<double>(), 5.0e-9);
+}
+
+TEST_F(UnitMath, ceil) {
+  double val = 101.1;
+  EXPECT_EQ(std::ceil(val), ceil(meter_t(val)).to<double>());
+  EXPECT_TRUE((std::is_same<typename std::decay<meter_t>::type,
+                            typename std::decay<decltype(
+                                ceil(meter_t(val)))>::type>::value));
+}
+
+TEST_F(UnitMath, floor) {
+  double val = 101.1;
+  EXPECT_EQ(std::floor(val), floor(scalar_t(val)));
+}
+
+TEST_F(UnitMath, fmod) {
+  EXPECT_EQ(std::fmod(100.0, 101.2),
+            fmod(meter_t(100.0), meter_t(101.2)).to<double>());
+}
+
+TEST_F(UnitMath, trunc) {
+  double val = 101.1;
+  EXPECT_EQ(std::trunc(val), trunc(scalar_t(val)));
+}
+
+TEST_F(UnitMath, round) {
+  double val = 101.1;
+  EXPECT_EQ(std::round(val), round(scalar_t(val)));
+}
+
+TEST_F(UnitMath, copysign) {
+  double sign = -1;
+  meter_t val(5.0);
+  EXPECT_EQ(meter_t(-5.0), copysign(val, sign));
+  EXPECT_EQ(meter_t(-5.0), copysign(val, angle::radian_t(sign)));
+}
+
+TEST_F(UnitMath, fdim) {
+  EXPECT_EQ(meter_t(0.0), fdim(meter_t(8.0), meter_t(10.0)));
+  EXPECT_EQ(meter_t(2.0), fdim(meter_t(10.0), meter_t(8.0)));
+  EXPECT_NEAR(meter_t(9.3904).to<double>(),
+              fdim(meter_t(10.0), foot_t(2.0)).to<double>(),
+              5.0e-320);  // not sure why they aren't comparing exactly equal,
+                          // but clearly they are.
+}
+
+TEST_F(UnitMath, fmin) {
+  EXPECT_EQ(meter_t(8.0), fmin(meter_t(8.0), meter_t(10.0)));
+  EXPECT_EQ(meter_t(8.0), fmin(meter_t(10.0), meter_t(8.0)));
+  EXPECT_EQ(foot_t(2.0), fmin(meter_t(10.0), foot_t(2.0)));
+}
+
+TEST_F(UnitMath, fmax) {
+  EXPECT_EQ(meter_t(10.0), fmax(meter_t(8.0), meter_t(10.0)));
+  EXPECT_EQ(meter_t(10.0), fmax(meter_t(10.0), meter_t(8.0)));
+  EXPECT_EQ(meter_t(10.0), fmax(meter_t(10.0), foot_t(2.0)));
+}
+
+TEST_F(UnitMath, fabs) {
+  EXPECT_EQ(meter_t(10.0), fabs(meter_t(-10.0)));
+  EXPECT_EQ(meter_t(10.0), fabs(meter_t(10.0)));
+}
+
+TEST_F(UnitMath, abs) {
+  EXPECT_EQ(meter_t(10.0), abs(meter_t(-10.0)));
+  EXPECT_EQ(meter_t(10.0), abs(meter_t(10.0)));
+}
+
+TEST_F(UnitMath, fma) {
+  meter_t x(2.0);
+  meter_t y(3.0);
+  square_meter_t z(1.0);
+  EXPECT_EQ(square_meter_t(7.0), fma(x, y, z));
+}
+
+// Constexpr
+#if !defined(_MSC_VER) || _MSC_VER > 1800
+TEST_F(Constexpr, construction) {
+  constexpr meter_t result0(0);
+  constexpr auto result1 = make_unit<meter_t>(1);
+  constexpr auto result2 = meter_t(2);
+
+  EXPECT_EQ(meter_t(0), result0);
+  EXPECT_EQ(meter_t(1), result1);
+  EXPECT_EQ(meter_t(2), result2);
+
+  EXPECT_TRUE(noexcept(result0));
+  EXPECT_TRUE(noexcept(result1));
+  EXPECT_TRUE(noexcept(result2));
+}
+
+TEST_F(Constexpr, constants) {
+  EXPECT_TRUE(noexcept(constants::c()));
+  EXPECT_TRUE(noexcept(constants::G()));
+  EXPECT_TRUE(noexcept(constants::h()));
+  EXPECT_TRUE(noexcept(constants::mu0()));
+  EXPECT_TRUE(noexcept(constants::epsilon0()));
+  EXPECT_TRUE(noexcept(constants::Z0()));
+  EXPECT_TRUE(noexcept(constants::k_e()));
+  EXPECT_TRUE(noexcept(constants::e()));
+  EXPECT_TRUE(noexcept(constants::m_e()));
+  EXPECT_TRUE(noexcept(constants::m_p()));
+  EXPECT_TRUE(noexcept(constants::mu_B()));
+  EXPECT_TRUE(noexcept(constants::N_A()));
+  EXPECT_TRUE(noexcept(constants::R()));
+  EXPECT_TRUE(noexcept(constants::k_B()));
+  EXPECT_TRUE(noexcept(constants::F()));
+  EXPECT_TRUE(noexcept(constants::sigma()));
+}
+
+TEST_F(Constexpr, arithmetic) {
+  constexpr auto result0(1_m + 1_m);
+  constexpr auto result1(1_m - 1_m);
+  constexpr auto result2(1_m * 1_m);
+  constexpr auto result3(1_m / 1_m);
+  constexpr auto result4(meter_t(1) + meter_t(1));
+  constexpr auto result5(meter_t(1) - meter_t(1));
+  constexpr auto result6(meter_t(1) * meter_t(1));
+  constexpr auto result7(meter_t(1) / meter_t(1));
+  constexpr auto result8(units::math::cpow<2>(meter_t(2)));
+  constexpr auto result9 = units::math::cpow<3>(2_m);
+  constexpr auto result10 = 2_m * 2_m;
+
+  EXPECT_TRUE(noexcept(result0));
+  EXPECT_TRUE(noexcept(result1));
+  EXPECT_TRUE(noexcept(result2));
+  EXPECT_TRUE(noexcept(result3));
+  EXPECT_TRUE(noexcept(result4));
+  EXPECT_TRUE(noexcept(result5));
+  EXPECT_TRUE(noexcept(result6));
+  EXPECT_TRUE(noexcept(result7));
+  EXPECT_TRUE(noexcept(result8));
+  EXPECT_TRUE(noexcept(result9));
+  EXPECT_TRUE(noexcept(result10));
+
+  EXPECT_EQ(8_cu_m, result9);
+  EXPECT_EQ(4_sq_m, result10);
+}
+
+TEST_F(Constexpr, realtional) {
+  constexpr bool equalityTrue = (1_m == 1_m);
+  constexpr bool equalityFalse = (1_m == 2_m);
+  constexpr bool lessThanTrue = (1_m < 2_m);
+  constexpr bool lessThanFalse = (1_m < 1_m);
+  constexpr bool lessThanEqualTrue1 = (1_m <= 1_m);
+  constexpr bool lessThanEqualTrue2 = (1_m <= 2_m);
+  constexpr bool lessThanEqualFalse = (1_m < 0_m);
+  constexpr bool greaterThanTrue = (2_m > 1_m);
+  constexpr bool greaterThanFalse = (2_m > 2_m);
+  constexpr bool greaterThanEqualTrue1 = (2_m >= 1_m);
+  constexpr bool greaterThanEqualTrue2 = (2_m >= 2_m);
+  constexpr bool greaterThanEqualFalse = (2_m > 3_m);
+
+  EXPECT_TRUE(equalityTrue);
+  EXPECT_TRUE(lessThanTrue);
+  EXPECT_TRUE(lessThanEqualTrue1);
+  EXPECT_TRUE(lessThanEqualTrue2);
+  EXPECT_TRUE(greaterThanTrue);
+  EXPECT_TRUE(greaterThanEqualTrue1);
+  EXPECT_TRUE(greaterThanEqualTrue2);
+  EXPECT_FALSE(equalityFalse);
+  EXPECT_FALSE(lessThanFalse);
+  EXPECT_FALSE(lessThanEqualFalse);
+  EXPECT_FALSE(greaterThanFalse);
+  EXPECT_FALSE(greaterThanEqualFalse);
+}
+
+TEST_F(Constexpr, stdArray) {
+  constexpr std::array<meter_t, 5> arr = {0_m, 1_m, 2_m, 3_m, 4_m};
+  constexpr bool equal = (arr[3] == 3_m);
+  EXPECT_TRUE(equal);
+}
+
+#endif
+
+TEST_F(CompileTimeArithmetic, unit_value_t) {
+  typedef unit_value_t<meters, 3, 2> mRatio;
+  EXPECT_EQ(meter_t(1.5), mRatio::value());
+}
+
+TEST_F(CompileTimeArithmetic, is_unit_value_t) {
+  typedef unit_value_t<meters, 3, 2> mRatio;
+
+  EXPECT_TRUE((traits::is_unit_value_t<mRatio>::value));
+  EXPECT_FALSE((traits::is_unit_value_t<meter_t>::value));
+  EXPECT_FALSE((traits::is_unit_value_t<double>::value));
+
+  EXPECT_TRUE((traits::is_unit_value_t<mRatio, meters>::value));
+  EXPECT_FALSE((traits::is_unit_value_t<meter_t, meters>::value));
+  EXPECT_FALSE((traits::is_unit_value_t<double, meters>::value));
+}
+
+TEST_F(CompileTimeArithmetic, is_unit_value_t_category) {
+  typedef unit_value_t<feet, 3, 2> mRatio;
+  EXPECT_TRUE(
+      (traits::is_unit_value_t_category<category::length_unit, mRatio>::value));
+  EXPECT_FALSE(
+      (traits::is_unit_value_t_category<category::angle_unit, mRatio>::value));
+  EXPECT_FALSE((
+      traits::is_unit_value_t_category<category::length_unit, meter_t>::value));
+  EXPECT_FALSE(
+      (traits::is_unit_value_t_category<category::length_unit, double>::value));
+}
+
+TEST_F(CompileTimeArithmetic, unit_value_add) {
+  typedef unit_value_t<meters, 3, 2> mRatio;
+
+  using sum = unit_value_add<mRatio, mRatio>;
+  EXPECT_EQ(meter_t(3.0), sum::value());
+  EXPECT_TRUE(
+      (traits::is_unit_value_t_category<category::length_unit, sum>::value));
+
+  typedef unit_value_t<feet, 1> ftRatio;
+
+  using sumf = unit_value_add<ftRatio, mRatio>;
+  EXPECT_TRUE((
+      std::is_same<typename std::decay<foot_t>::type,
+                   typename std::decay<decltype(sumf::value())>::type>::value));
+  EXPECT_NEAR(5.92125984, sumf::value().to<double>(), 5.0e-8);
+  EXPECT_TRUE(
+      (traits::is_unit_value_t_category<category::length_unit, sumf>::value));
+
+  typedef unit_value_t<celsius, 1> cRatio;
+  typedef unit_value_t<fahrenheit, 2> fRatio;
+
+  using sumc = unit_value_add<cRatio, fRatio>;
+  EXPECT_TRUE((
+      std::is_same<typename std::decay<celsius_t>::type,
+                   typename std::decay<decltype(sumc::value())>::type>::value));
+  EXPECT_NEAR(2.11111111111, sumc::value().to<double>(), 5.0e-8);
+  EXPECT_TRUE((traits::is_unit_value_t_category<category::temperature_unit,
+                                                sumc>::value));
+
+  typedef unit_value_t<angle::radian, 1> rRatio;
+  typedef unit_value_t<angle::degree, 3> dRatio;
+
+  using sumr = unit_value_add<rRatio, dRatio>;
+  EXPECT_TRUE((
+      std::is_same<typename std::decay<angle::radian_t>::type,
+                   typename std::decay<decltype(sumr::value())>::type>::value));
+  EXPECT_NEAR(1.05235988, sumr::value().to<double>(), 5.0e-8);
+  EXPECT_TRUE(
+      (traits::is_unit_value_t_category<category::angle_unit, sumr>::value));
+}
+
+TEST_F(CompileTimeArithmetic, unit_value_subtract) {
+  typedef unit_value_t<meters, 3, 2> mRatio;
+
+  using diff = unit_value_subtract<mRatio, mRatio>;
+  EXPECT_EQ(meter_t(0), diff::value());
+  EXPECT_TRUE(
+      (traits::is_unit_value_t_category<category::length_unit, diff>::value));
+
+  typedef unit_value_t<feet, 1> ftRatio;
+
+  using difff = unit_value_subtract<ftRatio, mRatio>;
+  EXPECT_TRUE((std::is_same<
+               typename std::decay<foot_t>::type,
+               typename std::decay<decltype(difff::value())>::type>::value));
+  EXPECT_NEAR(-3.92125984, difff::value().to<double>(), 5.0e-8);
+  EXPECT_TRUE(
+      (traits::is_unit_value_t_category<category::length_unit, difff>::value));
+
+  typedef unit_value_t<celsius, 1> cRatio;
+  typedef unit_value_t<fahrenheit, 2> fRatio;
+
+  using diffc = unit_value_subtract<cRatio, fRatio>;
+  EXPECT_TRUE((std::is_same<
+               typename std::decay<celsius_t>::type,
+               typename std::decay<decltype(diffc::value())>::type>::value));
+  EXPECT_NEAR(-0.11111111111, diffc::value().to<double>(), 5.0e-8);
+  EXPECT_TRUE((traits::is_unit_value_t_category<category::temperature_unit,
+                                                diffc>::value));
+
+  typedef unit_value_t<angle::radian, 1> rRatio;
+  typedef unit_value_t<angle::degree, 3> dRatio;
+
+  using diffr = unit_value_subtract<rRatio, dRatio>;
+  EXPECT_TRUE((std::is_same<
+               typename std::decay<angle::radian_t>::type,
+               typename std::decay<decltype(diffr::value())>::type>::value));
+  EXPECT_NEAR(0.947640122, diffr::value().to<double>(), 5.0e-8);
+  EXPECT_TRUE(
+      (traits::is_unit_value_t_category<category::angle_unit, diffr>::value));
+}
+
+TEST_F(CompileTimeArithmetic, unit_value_multiply) {
+  typedef unit_value_t<meters, 2> mRatio;
+  typedef unit_value_t<feet, 656168, 100000> ftRatio;  // 2 meter
+
+  using product = unit_value_multiply<mRatio, mRatio>;
+  EXPECT_EQ(square_meter_t(4), product::value());
+  EXPECT_TRUE(
+      (traits::is_unit_value_t_category<category::area_unit, product>::value));
+
+  using productM = unit_value_multiply<mRatio, ftRatio>;
+
+  EXPECT_TRUE((std::is_same<
+               typename std::decay<square_meter_t>::type,
+               typename std::decay<decltype(productM::value())>::type>::value));
+  EXPECT_NEAR(4.0, productM::value().to<double>(), 5.0e-7);
+  EXPECT_TRUE(
+      (traits::is_unit_value_t_category<category::area_unit, productM>::value));
+
+  using productF = unit_value_multiply<ftRatio, mRatio>;
+  EXPECT_TRUE((std::is_same<
+               typename std::decay<square_foot_t>::type,
+               typename std::decay<decltype(productF::value())>::type>::value));
+  EXPECT_NEAR(43.0556444224, productF::value().to<double>(), 5.0e-6);
+  EXPECT_TRUE(
+      (traits::is_unit_value_t_category<category::area_unit, productF>::value));
+
+  using productF2 = unit_value_multiply<ftRatio, ftRatio>;
+  EXPECT_TRUE(
+      (std::is_same<
+          typename std::decay<square_foot_t>::type,
+          typename std::decay<decltype(productF2::value())>::type>::value));
+  EXPECT_NEAR(43.0556444224, productF2::value().to<double>(), 5.0e-8);
+  EXPECT_TRUE((
+      traits::is_unit_value_t_category<category::area_unit, productF2>::value));
+
+  typedef unit_value_t<units::force::newton, 5> nRatio;
+
+  using productN = unit_value_multiply<nRatio, ftRatio>;
+  EXPECT_FALSE(
+      (std::is_same<
+          typename std::decay<torque::newton_meter_t>::type,
+          typename std::decay<decltype(productN::value())>::type>::value));
+  EXPECT_TRUE((std::is_convertible<
+               typename std::decay<torque::newton_meter_t>::type,
+               typename std::decay<decltype(productN::value())>::type>::value));
+  EXPECT_NEAR(32.8084, productN::value().to<double>(), 5.0e-8);
+  EXPECT_NEAR(10.0, (productN::value().convert<newton_meter>().to<double>()),
+              5.0e-7);
+  EXPECT_TRUE((traits::is_unit_value_t_category<category::torque_unit,
+                                                productN>::value));
+
+  typedef unit_value_t<angle::radian, 11, 10> r1Ratio;
+  typedef unit_value_t<angle::radian, 22, 10> r2Ratio;
+
+  using productR = unit_value_multiply<r1Ratio, r2Ratio>;
+  EXPECT_TRUE((std::is_same<
+               typename std::decay<steradian_t>::type,
+               typename std::decay<decltype(productR::value())>::type>::value));
+  EXPECT_NEAR(2.42, productR::value().to<double>(), 5.0e-8);
+  EXPECT_NEAR(7944.39137,
+              (productR::value().convert<degrees_squared>().to<double>()),
+              5.0e-6);
+  EXPECT_TRUE((traits::is_unit_value_t_category<category::solid_angle_unit,
+                                                productR>::value));
+}
+
+TEST_F(CompileTimeArithmetic, unit_value_divide) {
+  typedef unit_value_t<meters, 2> mRatio;
+  typedef unit_value_t<feet, 656168, 100000> ftRatio;  // 2 meter
+
+  using product = unit_value_divide<mRatio, mRatio>;
+  EXPECT_EQ(scalar_t(1), product::value());
+  EXPECT_TRUE((
+      traits::is_unit_value_t_category<category::scalar_unit, product>::value));
+
+  using productM = unit_value_divide<mRatio, ftRatio>;
+
+  EXPECT_TRUE((std::is_same<
+               typename std::decay<scalar_t>::type,
+               typename std::decay<decltype(productM::value())>::type>::value));
+  EXPECT_NEAR(1, productM::value().to<double>(), 5.0e-7);
+  EXPECT_TRUE((traits::is_unit_value_t_category<category::scalar_unit,
+                                                productM>::value));
+
+  using productF = unit_value_divide<ftRatio, mRatio>;
+  EXPECT_TRUE((std::is_same<
+               typename std::decay<scalar_t>::type,
+               typename std::decay<decltype(productF::value())>::type>::value));
+  EXPECT_NEAR(1.0, productF::value().to<double>(), 5.0e-6);
+  EXPECT_TRUE((traits::is_unit_value_t_category<category::scalar_unit,
+                                                productF>::value));
+
+  using productF2 = unit_value_divide<ftRatio, ftRatio>;
+  EXPECT_TRUE(
+      (std::is_same<
+          typename std::decay<scalar_t>::type,
+          typename std::decay<decltype(productF2::value())>::type>::value));
+  EXPECT_NEAR(1.0, productF2::value().to<double>(), 5.0e-8);
+  EXPECT_TRUE((traits::is_unit_value_t_category<category::scalar_unit,
+                                                productF2>::value));
+
+  typedef unit_value_t<seconds, 10> sRatio;
+
+  using productMS = unit_value_divide<mRatio, sRatio>;
+  EXPECT_TRUE(
+      (std::is_same<
+          typename std::decay<meters_per_second_t>::type,
+          typename std::decay<decltype(productMS::value())>::type>::value));
+  EXPECT_NEAR(0.2, productMS::value().to<double>(), 5.0e-8);
+  EXPECT_TRUE((traits::is_unit_value_t_category<category::velocity_unit,
+                                                productMS>::value));
+
+  typedef unit_value_t<angle::radian, 20> rRatio;
+
+  using productRS = unit_value_divide<rRatio, sRatio>;
+  EXPECT_TRUE(
+      (std::is_same<
+          typename std::decay<radians_per_second_t>::type,
+          typename std::decay<decltype(productRS::value())>::type>::value));
+  EXPECT_NEAR(2, productRS::value().to<double>(), 5.0e-8);
+  EXPECT_NEAR(114.592,
+              (productRS::value().convert<degrees_per_second>().to<double>()),
+              5.0e-4);
+  EXPECT_TRUE((traits::is_unit_value_t_category<category::angular_velocity_unit,
+                                                productRS>::value));
+}
+
+TEST_F(CompileTimeArithmetic, unit_value_power) {
+  typedef unit_value_t<meters, 2> mRatio;
+
+  using sq = unit_value_power<mRatio, 2>;
+  EXPECT_TRUE((std::is_convertible<
+               typename std::decay<square_meter_t>::type,
+               typename std::decay<decltype(sq::value())>::type>::value));
+  EXPECT_NEAR(4, sq::value().to<double>(), 5.0e-8);
+  EXPECT_TRUE(
+      (traits::is_unit_value_t_category<category::area_unit, sq>::value));
+
+  typedef unit_value_t<angle::radian, 18, 10> rRatio;
+
+  using sqr = unit_value_power<rRatio, 2>;
+  EXPECT_TRUE((std::is_convertible<
+               typename std::decay<steradian_t>::type,
+               typename std::decay<decltype(sqr::value())>::type>::value));
+  EXPECT_NEAR(3.24, sqr::value().to<double>(), 5.0e-8);
+  EXPECT_NEAR(10636.292574038049895092690529904,
+              (sqr::value().convert<degrees_squared>().to<double>()), 5.0e-10);
+  EXPECT_TRUE((traits::is_unit_value_t_category<category::solid_angle_unit,
+                                                sqr>::value));
+}
+
+TEST_F(CompileTimeArithmetic, unit_value_sqrt) {
+  typedef unit_value_t<square_meters, 10> mRatio;
+
+  using root = unit_value_sqrt<mRatio>;
+  EXPECT_TRUE((std::is_convertible<
+               typename std::decay<meter_t>::type,
+               typename std::decay<decltype(root::value())>::type>::value));
+  EXPECT_NEAR(3.16227766017, root::value().to<double>(), 5.0e-9);
+  EXPECT_TRUE(
+      (traits::is_unit_value_t_category<category::length_unit, root>::value));
+
+  typedef unit_value_t<hectare, 51, 7> hRatio;
+
+  using rooth = unit_value_sqrt<hRatio, 100000000>;
+  EXPECT_TRUE((std::is_convertible<
+               typename std::decay<mile_t>::type,
+               typename std::decay<decltype(rooth::value())>::type>::value));
+  EXPECT_NEAR(2.69920623253, rooth::value().to<double>(), 5.0e-8);
+  EXPECT_NEAR(269.920623, rooth::value().convert<meters>().to<double>(),
+              5.0e-6);
+  EXPECT_TRUE(
+      (traits::is_unit_value_t_category<category::length_unit, rooth>::value));
+
+  typedef unit_value_t<steradian, 18, 10> rRatio;
+
+  using rootr = unit_value_sqrt<rRatio>;
+  EXPECT_TRUE((traits::is_angle_unit<decltype(rootr::value())>::value));
+  EXPECT_NEAR(1.3416407865, rootr::value().to<double>(), 5.0e-8);
+  EXPECT_NEAR(76.870352574,
+              rootr::value().convert<angle::degrees>().to<double>(), 5.0e-6);
+  EXPECT_TRUE(
+      (traits::is_unit_value_t_category<category::angle_unit, rootr>::value));
+}
+
+TEST_F(CaseStudies, radarRangeEquation) {
+  watt_t P_t;            // transmit power
+  scalar_t G;            // gain
+  meter_t lambda;        // wavelength
+  square_meter_t sigma;  // radar cross section
+  meter_t R;             // range
+  kelvin_t T_s;          // system noise temp
+  hertz_t B_n;           // bandwidth
+  scalar_t L;            // loss
+
+  P_t = megawatt_t(1.4);
+  G = dB_t(33.0);
+  lambda = constants::c / megahertz_t(2800);
+  sigma = square_meter_t(1.0);
+  R = meter_t(111000.0);
+  T_s = kelvin_t(950.0);
+  B_n = megahertz_t(1.67);
+  L = dB_t(8.0);
+
+  scalar_t SNR = (P_t * math::pow<2>(G) * math::pow<2>(lambda) * sigma) /
+                 (math::pow<3>(4 * constants::pi) * math::pow<4>(R) *
+                  constants::k_B * T_s * B_n * L);
+
+  EXPECT_NEAR(1.535, SNR(), 5.0e-4);
+}
+
+TEST_F(CaseStudies, pythagoreanTheorum) {
+  EXPECT_EQ(meter_t(3), RightTriangle::a::value());
+  EXPECT_EQ(meter_t(4), RightTriangle::b::value());
+  EXPECT_EQ(meter_t(5), RightTriangle::c::value());
+  EXPECT_TRUE(pow<2>(RightTriangle::a::value()) +
+                  pow<2>(RightTriangle::b::value()) ==
+              pow<2>(RightTriangle::c::value()));
+}
diff --git a/wpiutil/src/test/native/cpp/WebSocketClientTest.cpp b/wpiutil/src/test/native/cpp/WebSocketClientTest.cpp
new file mode 100644
index 0000000..2db9b54
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/WebSocketClientTest.cpp
@@ -0,0 +1,298 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include "wpi/WebSocket.h"  // NOLINT(build/include_order)
+
+#include "WebSocketTest.h"
+#include "wpi/Base64.h"
+#include "wpi/HttpParser.h"
+#include "wpi/SmallString.h"
+#include "wpi/raw_uv_ostream.h"
+#include "wpi/sha1.h"
+
+namespace wpi {
+
+class WebSocketClientTest : public WebSocketTest {
+ public:
+  WebSocketClientTest() {
+    // Bare bones server
+    req.header.connect([this](StringRef name, StringRef value) {
+      // save key (required for valid response)
+      if (name.equals_lower("sec-websocket-key")) clientKey = value;
+    });
+    req.headersComplete.connect([this](bool) {
+      // send response
+      SmallVector<uv::Buffer, 4> bufs;
+      raw_uv_ostream os{bufs, 4096};
+      os << "HTTP/1.1 101 Switching Protocols\r\n";
+      os << "Upgrade: websocket\r\n";
+      os << "Connection: Upgrade\r\n";
+
+      // accept hash
+      SHA1 hash;
+      hash.Update(clientKey);
+      hash.Update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
+      if (mockBadAccept) hash.Update("1");
+      SmallString<64> hashBuf;
+      SmallString<64> acceptBuf;
+      os << "Sec-WebSocket-Accept: "
+         << Base64Encode(hash.RawFinal(hashBuf), acceptBuf) << "\r\n";
+
+      if (!mockProtocol.empty())
+        os << "Sec-WebSocket-Protocol: " << mockProtocol << "\r\n";
+
+      os << "\r\n";
+
+      conn->Write(bufs, [](auto bufs, uv::Error) {
+        for (auto& buf : bufs) buf.Deallocate();
+      });
+
+      serverHeadersDone = true;
+      if (connected) connected();
+    });
+
+    serverPipe->Listen([this] {
+      conn = serverPipe->Accept();
+      conn->StartRead();
+      conn->data.connect([this](uv::Buffer& buf, size_t size) {
+        StringRef data{buf.base, size};
+        if (!serverHeadersDone) {
+          data = req.Execute(data);
+          if (req.HasError()) Finish();
+          ASSERT_EQ(req.GetError(), HPE_OK) << http_errno_name(req.GetError());
+          if (data.empty()) return;
+        }
+        wireData.insert(wireData.end(), data.bytes_begin(), data.bytes_end());
+      });
+      conn->end.connect([this] { Finish(); });
+    });
+  }
+
+  bool mockBadAccept = false;
+  std::vector<uint8_t> wireData;
+  std::shared_ptr<uv::Pipe> conn;
+  HttpParser req{HttpParser::kRequest};
+  SmallString<64> clientKey;
+  std::string mockProtocol;
+  bool serverHeadersDone = false;
+  std::function<void()> connected;
+};
+
+TEST_F(WebSocketClientTest, Open) {
+  int gotOpen = 0;
+
+  clientPipe->Connect(pipeName, [&] {
+    auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      Finish();
+      if (code != 1005 && code != 1006)
+        FAIL() << "Code: " << code << " Reason: " << reason;
+    });
+    ws->open.connect([&](StringRef protocol) {
+      ++gotOpen;
+      Finish();
+      ASSERT_TRUE(protocol.empty());
+    });
+  });
+
+  loop->Run();
+
+  if (HasFatalFailure()) return;
+  ASSERT_EQ(gotOpen, 1);
+}
+
+TEST_F(WebSocketClientTest, BadAccept) {
+  int gotClosed = 0;
+
+  mockBadAccept = true;
+
+  clientPipe->Connect(pipeName, [&] {
+    auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
+    ws->closed.connect([&](uint16_t code, StringRef msg) {
+      Finish();
+      ++gotClosed;
+      ASSERT_EQ(code, 1002) << "Message: " << msg;
+    });
+    ws->open.connect([&](StringRef protocol) {
+      Finish();
+      FAIL() << "Got open";
+    });
+  });
+
+  loop->Run();
+
+  if (HasFatalFailure()) return;
+  ASSERT_EQ(gotClosed, 1);
+}
+
+TEST_F(WebSocketClientTest, ProtocolGood) {
+  int gotOpen = 0;
+
+  mockProtocol = "myProtocol";
+
+  clientPipe->Connect(pipeName, [&] {
+    auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName,
+                                      {"myProtocol", "myProtocol2"});
+    ws->closed.connect([&](uint16_t code, StringRef msg) {
+      Finish();
+      if (code != 1005 && code != 1006)
+        FAIL() << "Code: " << code << "Message: " << msg;
+    });
+    ws->open.connect([&](StringRef protocol) {
+      ++gotOpen;
+      Finish();
+      ASSERT_EQ(protocol, "myProtocol");
+    });
+  });
+
+  loop->Run();
+
+  if (HasFatalFailure()) return;
+  ASSERT_EQ(gotOpen, 1);
+}
+
+TEST_F(WebSocketClientTest, ProtocolRespNotReq) {
+  int gotClosed = 0;
+
+  mockProtocol = "myProtocol";
+
+  clientPipe->Connect(pipeName, [&] {
+    auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
+    ws->closed.connect([&](uint16_t code, StringRef msg) {
+      Finish();
+      ++gotClosed;
+      ASSERT_EQ(code, 1003) << "Message: " << msg;
+    });
+    ws->open.connect([&](StringRef protocol) {
+      Finish();
+      FAIL() << "Got open";
+    });
+  });
+
+  loop->Run();
+
+  if (HasFatalFailure()) return;
+  ASSERT_EQ(gotClosed, 1);
+}
+
+TEST_F(WebSocketClientTest, ProtocolReqNotResp) {
+  int gotClosed = 0;
+
+  clientPipe->Connect(pipeName, [&] {
+    auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName,
+                                      StringRef{"myProtocol"});
+    ws->closed.connect([&](uint16_t code, StringRef msg) {
+      Finish();
+      ++gotClosed;
+      ASSERT_EQ(code, 1002) << "Message: " << msg;
+    });
+    ws->open.connect([&](StringRef protocol) {
+      Finish();
+      FAIL() << "Got open";
+    });
+  });
+
+  loop->Run();
+
+  if (HasFatalFailure()) return;
+  ASSERT_EQ(gotClosed, 1);
+}
+
+//
+// Send and receive data.  Most of these cases are tested in
+// WebSocketServerTest, so only spot check differences like masking.
+//
+
+class WebSocketClientDataTest : public WebSocketClientTest,
+                                public ::testing::WithParamInterface<size_t> {
+ public:
+  WebSocketClientDataTest() {
+    clientPipe->Connect(pipeName, [&] {
+      ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
+      if (setupWebSocket) setupWebSocket();
+    });
+  }
+
+  std::function<void()> setupWebSocket;
+  std::shared_ptr<WebSocket> ws;
+};
+
+INSTANTIATE_TEST_SUITE_P(WebSocketClientDataTests, WebSocketClientDataTest,
+                         ::testing::Values(0, 1, 125, 126, 65535, 65536));
+
+TEST_P(WebSocketClientDataTest, SendBinary) {
+  int gotCallback = 0;
+  std::vector<uint8_t> data(GetParam(), 0x03u);
+  setupWebSocket = [&] {
+    ws->open.connect([&](StringRef) {
+      ws->SendBinary(uv::Buffer(data), [&](auto bufs, uv::Error) {
+        ++gotCallback;
+        ws->Terminate();
+        ASSERT_FALSE(bufs.empty());
+        ASSERT_EQ(bufs[0].base, reinterpret_cast<const char*>(data.data()));
+      });
+    });
+  };
+
+  loop->Run();
+
+  auto expectData = BuildMessage(0x02, true, true, data);
+  AdjustMasking(wireData);
+  ASSERT_EQ(wireData, expectData);
+  ASSERT_EQ(gotCallback, 1);
+}
+
+TEST_P(WebSocketClientDataTest, ReceiveBinary) {
+  int gotCallback = 0;
+  std::vector<uint8_t> data(GetParam(), 0x03u);
+  setupWebSocket = [&] {
+    ws->binary.connect([&](ArrayRef<uint8_t> inData, bool fin) {
+      ++gotCallback;
+      ws->Terminate();
+      ASSERT_TRUE(fin);
+      std::vector<uint8_t> recvData{inData.begin(), inData.end()};
+      ASSERT_EQ(data, recvData);
+    });
+  };
+  auto message = BuildMessage(0x02, true, false, data);
+  connected = [&] {
+    conn->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  };
+
+  loop->Run();
+
+  ASSERT_EQ(gotCallback, 1);
+}
+
+//
+// The client must close the connection if a masked frame is received.
+//
+
+TEST_P(WebSocketClientDataTest, ReceiveMasked) {
+  int gotCallback = 0;
+  std::vector<uint8_t> data(GetParam(), ' ');
+  setupWebSocket = [&] {
+    ws->text.connect([&](StringRef, bool) {
+      ws->Terminate();
+      FAIL() << "Should not have gotten masked message";
+    });
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      ++gotCallback;
+      ASSERT_EQ(code, 1002) << "reason: " << reason;
+    });
+  };
+  auto message = BuildMessage(0x01, true, true, data);
+  connected = [&] {
+    conn->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  };
+
+  loop->Run();
+
+  ASSERT_EQ(gotCallback, 1);
+}
+
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/WebSocketIntegrationTest.cpp b/wpiutil/src/test/native/cpp/WebSocketIntegrationTest.cpp
new file mode 100644
index 0000000..9a66a2e
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/WebSocketIntegrationTest.cpp
@@ -0,0 +1,148 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include "wpi/WebSocketServer.h"  // NOLINT(build/include_order)
+
+#include "WebSocketTest.h"
+#include "wpi/HttpParser.h"
+#include "wpi/SmallString.h"
+
+namespace wpi {
+
+class WebSocketIntegrationTest : public WebSocketTest {};
+
+TEST_F(WebSocketIntegrationTest, Open) {
+  int gotServerOpen = 0;
+  int gotClientOpen = 0;
+
+  serverPipe->Listen([&]() {
+    auto conn = serverPipe->Accept();
+    auto server = WebSocketServer::Create(*conn);
+    server->connected.connect([&](StringRef url, WebSocket&) {
+      ++gotServerOpen;
+      ASSERT_EQ(url, "/test");
+    });
+  });
+
+  clientPipe->Connect(pipeName, [&] {
+    auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      Finish();
+      if (code != 1005 && code != 1006)
+        FAIL() << "Code: " << code << " Reason: " << reason;
+    });
+    ws->open.connect([&, s = ws.get()](StringRef) {
+      ++gotClientOpen;
+      s->Close();
+    });
+  });
+
+  loop->Run();
+
+  ASSERT_EQ(gotServerOpen, 1);
+  ASSERT_EQ(gotClientOpen, 1);
+}
+
+TEST_F(WebSocketIntegrationTest, Protocol) {
+  int gotServerOpen = 0;
+  int gotClientOpen = 0;
+
+  serverPipe->Listen([&]() {
+    auto conn = serverPipe->Accept();
+    auto server = WebSocketServer::Create(*conn, {"proto1", "proto2"});
+    server->connected.connect([&](StringRef, WebSocket& ws) {
+      ++gotServerOpen;
+      ASSERT_EQ(ws.GetProtocol(), "proto1");
+    });
+  });
+
+  clientPipe->Connect(pipeName, [&] {
+    auto ws =
+        WebSocket::CreateClient(*clientPipe, "/test", pipeName, {"proto1"});
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      Finish();
+      if (code != 1005 && code != 1006)
+        FAIL() << "Code: " << code << " Reason: " << reason;
+    });
+    ws->open.connect([&, s = ws.get()](StringRef protocol) {
+      ++gotClientOpen;
+      s->Close();
+      ASSERT_EQ(protocol, "proto1");
+    });
+  });
+
+  loop->Run();
+
+  ASSERT_EQ(gotServerOpen, 1);
+  ASSERT_EQ(gotClientOpen, 1);
+}
+
+TEST_F(WebSocketIntegrationTest, ServerSendBinary) {
+  int gotData = 0;
+
+  serverPipe->Listen([&]() {
+    auto conn = serverPipe->Accept();
+    auto server = WebSocketServer::Create(*conn);
+    server->connected.connect([&](StringRef, WebSocket& ws) {
+      ws.SendBinary(uv::Buffer{"\x03\x04", 2}, [&](auto, uv::Error) {});
+      ws.Close();
+    });
+  });
+
+  clientPipe->Connect(pipeName, [&] {
+    auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      Finish();
+      if (code != 1005 && code != 1006)
+        FAIL() << "Code: " << code << " Reason: " << reason;
+    });
+    ws->binary.connect([&](ArrayRef<uint8_t> data, bool) {
+      ++gotData;
+      std::vector<uint8_t> recvData{data.begin(), data.end()};
+      std::vector<uint8_t> expectData{0x03, 0x04};
+      ASSERT_EQ(recvData, expectData);
+    });
+  });
+
+  loop->Run();
+
+  ASSERT_EQ(gotData, 1);
+}
+
+TEST_F(WebSocketIntegrationTest, ClientSendText) {
+  int gotData = 0;
+
+  serverPipe->Listen([&]() {
+    auto conn = serverPipe->Accept();
+    auto server = WebSocketServer::Create(*conn);
+    server->connected.connect([&](StringRef, WebSocket& ws) {
+      ws.text.connect([&](StringRef data, bool) {
+        ++gotData;
+        ASSERT_EQ(data, "hello");
+      });
+    });
+  });
+
+  clientPipe->Connect(pipeName, [&] {
+    auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      Finish();
+      if (code != 1005 && code != 1006)
+        FAIL() << "Code: " << code << " Reason: " << reason;
+    });
+    ws->open.connect([&, s = ws.get()](StringRef) {
+      s->SendText(uv::Buffer{"hello"}, [&](auto, uv::Error) {});
+      s->Close();
+    });
+  });
+
+  loop->Run();
+
+  ASSERT_EQ(gotData, 1);
+}
+
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/WebSocketServerTest.cpp b/wpiutil/src/test/native/cpp/WebSocketServerTest.cpp
new file mode 100644
index 0000000..d11fdda
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/WebSocketServerTest.cpp
@@ -0,0 +1,736 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include "wpi/WebSocket.h"  // NOLINT(build/include_order)
+
+#include "WebSocketTest.h"
+#include "wpi/Base64.h"
+#include "wpi/HttpParser.h"
+#include "wpi/SmallString.h"
+#include "wpi/raw_uv_ostream.h"
+#include "wpi/sha1.h"
+
+namespace wpi {
+
+class WebSocketServerTest : public WebSocketTest {
+ public:
+  WebSocketServerTest() {
+    resp.headersComplete.connect([this](bool) { headersDone = true; });
+
+    serverPipe->Listen([this]() {
+      auto conn = serverPipe->Accept();
+      ws = WebSocket::CreateServer(*conn, "foo", "13");
+      if (setupWebSocket) setupWebSocket();
+    });
+    clientPipe->Connect(pipeName, [this]() {
+      clientPipe->StartRead();
+      clientPipe->data.connect([this](uv::Buffer& buf, size_t size) {
+        StringRef data{buf.base, size};
+        if (!headersDone) {
+          data = resp.Execute(data);
+          if (resp.HasError()) Finish();
+          ASSERT_EQ(resp.GetError(), HPE_OK)
+              << http_errno_name(resp.GetError());
+          if (data.empty()) return;
+        }
+        wireData.insert(wireData.end(), data.bytes_begin(), data.bytes_end());
+        if (handleData) handleData(data);
+      });
+      clientPipe->end.connect([this]() { Finish(); });
+    });
+  }
+
+  std::function<void()> setupWebSocket;
+  std::function<void(StringRef)> handleData;
+  std::vector<uint8_t> wireData;
+  std::shared_ptr<WebSocket> ws;
+  HttpParser resp{HttpParser::kResponse};
+  bool headersDone = false;
+};
+
+//
+// Terminate closes the endpoint but doesn't send a close frame.
+//
+
+TEST_F(WebSocketServerTest, Terminate) {
+  int gotClosed = 0;
+  setupWebSocket = [&] {
+    ws->open.connect([&](StringRef) { ws->Terminate(); });
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      ++gotClosed;
+      ASSERT_EQ(code, 1006) << "reason: " << reason;
+    });
+  };
+
+  loop->Run();
+
+  ASSERT_TRUE(wireData.empty());  // terminate doesn't send data
+  ASSERT_EQ(gotClosed, 1);
+}
+
+TEST_F(WebSocketServerTest, TerminateCode) {
+  int gotClosed = 0;
+  setupWebSocket = [&] {
+    ws->open.connect([&](StringRef) { ws->Terminate(1000); });
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      ++gotClosed;
+      ASSERT_EQ(code, 1000) << "reason: " << reason;
+    });
+  };
+
+  loop->Run();
+
+  ASSERT_TRUE(wireData.empty());  // terminate doesn't send data
+  ASSERT_EQ(gotClosed, 1);
+}
+
+TEST_F(WebSocketServerTest, TerminateReason) {
+  int gotClosed = 0;
+  setupWebSocket = [&] {
+    ws->open.connect([&](StringRef) { ws->Terminate(1000, "reason"); });
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      ++gotClosed;
+      ASSERT_EQ(code, 1000);
+      ASSERT_EQ(reason, "reason");
+    });
+  };
+
+  loop->Run();
+
+  ASSERT_TRUE(wireData.empty());  // terminate doesn't send data
+  ASSERT_EQ(gotClosed, 1);
+}
+
+//
+// Close() sends a close frame.
+//
+
+TEST_F(WebSocketServerTest, CloseBasic) {
+  int gotClosed = 0;
+  setupWebSocket = [&] {
+    ws->open.connect([&](StringRef) { ws->Close(); });
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      ++gotClosed;
+      ASSERT_EQ(code, 1005) << "reason: " << reason;
+    });
+  };
+  // need to respond with close for server to finish shutdown
+  auto message = BuildMessage(0x08, true, true, {});
+  handleData = [&](StringRef) {
+    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  };
+
+  loop->Run();
+
+  auto expectData = BuildMessage(0x08, true, false, {});
+  ASSERT_EQ(wireData, expectData);
+  ASSERT_EQ(gotClosed, 1);
+}
+
+TEST_F(WebSocketServerTest, CloseCode) {
+  int gotClosed = 0;
+  setupWebSocket = [&] {
+    ws->open.connect([&](StringRef) { ws->Close(1000); });
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      ++gotClosed;
+      ASSERT_EQ(code, 1000) << "reason: " << reason;
+    });
+  };
+  // need to respond with close for server to finish shutdown
+  const uint8_t contents[] = {0x03u, 0xe8u};
+  auto message = BuildMessage(0x08, true, true, contents);
+  handleData = [&](StringRef) {
+    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  };
+
+  loop->Run();
+
+  auto expectData = BuildMessage(0x08, true, false, contents);
+  ASSERT_EQ(wireData, expectData);
+  ASSERT_EQ(gotClosed, 1);
+}
+
+TEST_F(WebSocketServerTest, CloseReason) {
+  int gotClosed = 0;
+  setupWebSocket = [&] {
+    ws->open.connect([&](StringRef) { ws->Close(1000, "hangup"); });
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      ++gotClosed;
+      ASSERT_EQ(code, 1000);
+      ASSERT_EQ(reason, "hangup");
+    });
+  };
+  // need to respond with close for server to finish shutdown
+  const uint8_t contents[] = {0x03u, 0xe8u, 'h', 'a', 'n', 'g', 'u', 'p'};
+  auto message = BuildMessage(0x08, true, true, contents);
+  handleData = [&](StringRef) {
+    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  };
+
+  loop->Run();
+
+  auto expectData = BuildMessage(0x08, true, false, contents);
+  ASSERT_EQ(wireData, expectData);
+  ASSERT_EQ(gotClosed, 1);
+}
+
+//
+// Receiving a close frame results in closure and echoing the close frame.
+//
+
+TEST_F(WebSocketServerTest, ReceiveCloseBasic) {
+  int gotClosed = 0;
+  setupWebSocket = [&] {
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      ++gotClosed;
+      ASSERT_EQ(code, 1005) << "reason: " << reason;
+    });
+  };
+  auto message = BuildMessage(0x08, true, true, {});
+  resp.headersComplete.connect([&](bool) {
+    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  });
+
+  loop->Run();
+
+  // the endpoint should echo the message
+  auto expectData = BuildMessage(0x08, true, false, {});
+  ASSERT_EQ(wireData, expectData);
+  ASSERT_EQ(gotClosed, 1);
+}
+
+TEST_F(WebSocketServerTest, ReceiveCloseCode) {
+  int gotClosed = 0;
+  setupWebSocket = [&] {
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      ++gotClosed;
+      ASSERT_EQ(code, 1000) << "reason: " << reason;
+    });
+  };
+  const uint8_t contents[] = {0x03u, 0xe8u};
+  auto message = BuildMessage(0x08, true, true, contents);
+  resp.headersComplete.connect([&](bool) {
+    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  });
+
+  loop->Run();
+
+  // the endpoint should echo the message
+  auto expectData = BuildMessage(0x08, true, false, contents);
+  ASSERT_EQ(wireData, expectData);
+  ASSERT_EQ(gotClosed, 1);
+}
+
+TEST_F(WebSocketServerTest, ReceiveCloseReason) {
+  int gotClosed = 0;
+  setupWebSocket = [&] {
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      ++gotClosed;
+      ASSERT_EQ(code, 1000);
+      ASSERT_EQ(reason, "hangup");
+    });
+  };
+  const uint8_t contents[] = {0x03u, 0xe8u, 'h', 'a', 'n', 'g', 'u', 'p'};
+  auto message = BuildMessage(0x08, true, true, contents);
+  resp.headersComplete.connect([&](bool) {
+    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  });
+
+  loop->Run();
+
+  // the endpoint should echo the message
+  auto expectData = BuildMessage(0x08, true, false, contents);
+  ASSERT_EQ(wireData, expectData);
+  ASSERT_EQ(gotClosed, 1);
+}
+
+//
+// If an unknown opcode is received, the receiving endpoint MUST _Fail the
+// WebSocket Connection_.
+//
+
+class WebSocketServerBadOpcodeTest
+    : public WebSocketServerTest,
+      public ::testing::WithParamInterface<uint8_t> {};
+
+INSTANTIATE_TEST_SUITE_P(WebSocketServerBadOpcodeTests,
+                         WebSocketServerBadOpcodeTest,
+                         ::testing::Values(3, 4, 5, 6, 7, 0xb, 0xc, 0xd, 0xe,
+                                           0xf));
+
+TEST_P(WebSocketServerBadOpcodeTest, Receive) {
+  int gotCallback = 0;
+  std::vector<uint8_t> data(4, 0x03);
+  setupWebSocket = [&] {
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      ++gotCallback;
+      ASSERT_EQ(code, 1002) << "reason: " << reason;
+    });
+  };
+  auto message = BuildMessage(GetParam(), true, true, data);
+  resp.headersComplete.connect([&](bool) {
+    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  });
+
+  loop->Run();
+
+  ASSERT_EQ(gotCallback, 1);
+}
+
+//
+// Control frames themselves MUST NOT be fragmented.
+//
+
+class WebSocketServerControlFrameTest
+    : public WebSocketServerTest,
+      public ::testing::WithParamInterface<uint8_t> {};
+
+INSTANTIATE_TEST_SUITE_P(WebSocketServerControlFrameTests,
+                         WebSocketServerControlFrameTest,
+                         ::testing::Values(0x8, 0x9, 0xa));
+
+TEST_P(WebSocketServerControlFrameTest, ReceiveFragment) {
+  int gotCallback = 0;
+  std::vector<uint8_t> data(4, 0x03);
+  setupWebSocket = [&] {
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      ++gotCallback;
+      ASSERT_EQ(code, 1002) << "reason: " << reason;
+    });
+  };
+  auto message = BuildMessage(GetParam(), false, true, data);
+  resp.headersComplete.connect([&](bool) {
+    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  });
+
+  loop->Run();
+
+  ASSERT_EQ(gotCallback, 1);
+}
+
+//
+// A fragmented message consists of a single frame with the FIN bit
+// clear and an opcode other than 0, followed by zero or more frames
+// with the FIN bit clear and the opcode set to 0, and terminated by
+// a single frame with the FIN bit set and an opcode of 0.
+//
+
+// No previous message
+TEST_F(WebSocketServerTest, ReceiveFragmentInvalidNoPrevFrame) {
+  int gotCallback = 0;
+  std::vector<uint8_t> data(4, 0x03);
+  setupWebSocket = [&] {
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      ++gotCallback;
+      ASSERT_EQ(code, 1002) << "reason: " << reason;
+    });
+  };
+  auto message = BuildMessage(0x00, false, true, data);
+  resp.headersComplete.connect([&](bool) {
+    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  });
+
+  loop->Run();
+
+  ASSERT_EQ(gotCallback, 1);
+}
+
+// No previous message with FIN=1.
+TEST_F(WebSocketServerTest, ReceiveFragmentInvalidNoPrevFragment) {
+  int gotCallback = 0;
+  std::vector<uint8_t> data(4, 0x03);
+  setupWebSocket = [&] {
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      ++gotCallback;
+      ASSERT_EQ(code, 1002) << "reason: " << reason;
+    });
+  };
+  auto message = BuildMessage(0x01, true, true, {});  // FIN=1
+  auto message2 = BuildMessage(0x00, false, true, data);
+  resp.headersComplete.connect([&](bool) {
+    clientPipe->Write({uv::Buffer(message), uv::Buffer(message2)},
+                      [&](auto bufs, uv::Error) {});
+  });
+
+  loop->Run();
+
+  ASSERT_EQ(gotCallback, 1);
+}
+
+// Incomplete fragment
+TEST_F(WebSocketServerTest, ReceiveFragmentInvalidIncomplete) {
+  int gotCallback = 0;
+  setupWebSocket = [&] {
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      ++gotCallback;
+      ASSERT_EQ(code, 1002) << "reason: " << reason;
+    });
+  };
+  auto message = BuildMessage(0x01, false, true, {});
+  auto message2 = BuildMessage(0x00, false, true, {});
+  auto message3 = BuildMessage(0x01, true, true, {});
+  resp.headersComplete.connect([&](bool) {
+    clientPipe->Write(
+        {uv::Buffer(message), uv::Buffer(message2), uv::Buffer(message3)},
+        [&](auto bufs, uv::Error) {});
+  });
+
+  loop->Run();
+
+  ASSERT_EQ(gotCallback, 1);
+}
+
+// Normally fragments are combined into a single callback
+TEST_F(WebSocketServerTest, ReceiveFragment) {
+  int gotCallback = 0;
+
+  std::vector<uint8_t> data(4, 0x03);
+  std::vector<uint8_t> data2(4, 0x04);
+  std::vector<uint8_t> data3(4, 0x05);
+  std::vector<uint8_t> combData{data};
+  combData.insert(combData.end(), data2.begin(), data2.end());
+  combData.insert(combData.end(), data3.begin(), data3.end());
+
+  setupWebSocket = [&] {
+    ws->binary.connect([&](ArrayRef<uint8_t> inData, bool fin) {
+      ++gotCallback;
+      ws->Terminate();
+      ASSERT_TRUE(fin);
+      std::vector<uint8_t> recvData{inData.begin(), inData.end()};
+      ASSERT_EQ(combData, recvData);
+    });
+  };
+
+  auto message = BuildMessage(0x02, false, true, data);
+  auto message2 = BuildMessage(0x00, false, true, data2);
+  auto message3 = BuildMessage(0x00, true, true, data3);
+  resp.headersComplete.connect([&](bool) {
+    clientPipe->Write(
+        {uv::Buffer(message), uv::Buffer(message2), uv::Buffer(message3)},
+        [&](auto bufs, uv::Error) {});
+  });
+
+  loop->Run();
+
+  ASSERT_EQ(gotCallback, 1);
+}
+
+// But can be configured for multiple callbacks
+TEST_F(WebSocketServerTest, ReceiveFragmentSeparate) {
+  int gotCallback = 0;
+
+  std::vector<uint8_t> data(4, 0x03);
+  std::vector<uint8_t> data2(4, 0x04);
+  std::vector<uint8_t> data3(4, 0x05);
+  std::vector<uint8_t> combData{data};
+  combData.insert(combData.end(), data2.begin(), data2.end());
+  combData.insert(combData.end(), data3.begin(), data3.end());
+
+  setupWebSocket = [&] {
+    ws->SetCombineFragments(false);
+    ws->binary.connect([&](ArrayRef<uint8_t> inData, bool fin) {
+      std::vector<uint8_t> recvData{inData.begin(), inData.end()};
+      switch (++gotCallback) {
+        case 1:
+          ASSERT_FALSE(fin);
+          ASSERT_EQ(data, recvData);
+          break;
+        case 2:
+          ASSERT_FALSE(fin);
+          ASSERT_EQ(data2, recvData);
+          break;
+        case 3:
+          ws->Terminate();
+          ASSERT_TRUE(fin);
+          ASSERT_EQ(data3, recvData);
+          break;
+        default:
+          FAIL() << "too many callbacks";
+          break;
+      }
+    });
+  };
+
+  auto message = BuildMessage(0x02, false, true, data);
+  auto message2 = BuildMessage(0x00, false, true, data2);
+  auto message3 = BuildMessage(0x00, true, true, data3);
+  resp.headersComplete.connect([&](bool) {
+    clientPipe->Write(
+        {uv::Buffer(message), uv::Buffer(message2), uv::Buffer(message3)},
+        [&](auto bufs, uv::Error) {});
+  });
+
+  loop->Run();
+
+  ASSERT_EQ(gotCallback, 3);
+}
+
+//
+// Maximum message size is limited.
+//
+
+// Single message
+TEST_F(WebSocketServerTest, ReceiveTooLarge) {
+  int gotCallback = 0;
+  std::vector<uint8_t> data(2048, 0x03u);
+  setupWebSocket = [&] {
+    ws->SetMaxMessageSize(1024);
+    ws->binary.connect([&](auto, bool) {
+      ws->Terminate();
+      FAIL() << "Should not have gotten unmasked message";
+    });
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      ++gotCallback;
+      ASSERT_EQ(code, 1009) << "reason: " << reason;
+    });
+  };
+  auto message = BuildMessage(0x01, true, true, data);
+  resp.headersComplete.connect([&](bool) {
+    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  });
+
+  loop->Run();
+
+  ASSERT_EQ(gotCallback, 1);
+}
+
+// Applied across fragments if combining
+TEST_F(WebSocketServerTest, ReceiveTooLargeFragmented) {
+  int gotCallback = 0;
+  std::vector<uint8_t> data(768, 0x03u);
+  setupWebSocket = [&] {
+    ws->SetMaxMessageSize(1024);
+    ws->binary.connect([&](auto, bool) {
+      ws->Terminate();
+      FAIL() << "Should not have gotten unmasked message";
+    });
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      ++gotCallback;
+      ASSERT_EQ(code, 1009) << "reason: " << reason;
+    });
+  };
+  auto message = BuildMessage(0x01, false, true, data);
+  auto message2 = BuildMessage(0x00, true, true, data);
+  resp.headersComplete.connect([&](bool) {
+    clientPipe->Write({uv::Buffer(message), uv::Buffer(message2)},
+                      [&](auto bufs, uv::Error) {});
+  });
+
+  loop->Run();
+
+  ASSERT_EQ(gotCallback, 1);
+}
+
+//
+// Send and receive data.
+//
+
+class WebSocketServerDataTest : public WebSocketServerTest,
+                                public ::testing::WithParamInterface<size_t> {};
+
+INSTANTIATE_TEST_SUITE_P(WebSocketServerDataTests, WebSocketServerDataTest,
+                         ::testing::Values(0, 1, 125, 126, 65535, 65536));
+
+TEST_P(WebSocketServerDataTest, SendText) {
+  int gotCallback = 0;
+  std::vector<uint8_t> data(GetParam(), ' ');
+  setupWebSocket = [&] {
+    ws->open.connect([&](StringRef) {
+      ws->SendText(uv::Buffer(data), [&](auto bufs, uv::Error) {
+        ++gotCallback;
+        ws->Terminate();
+        ASSERT_FALSE(bufs.empty());
+        ASSERT_EQ(bufs[0].base, reinterpret_cast<const char*>(data.data()));
+      });
+    });
+  };
+
+  loop->Run();
+
+  auto expectData = BuildMessage(0x01, true, false, data);
+  ASSERT_EQ(wireData, expectData);
+  ASSERT_EQ(gotCallback, 1);
+}
+
+TEST_P(WebSocketServerDataTest, SendBinary) {
+  int gotCallback = 0;
+  std::vector<uint8_t> data(GetParam(), 0x03u);
+  setupWebSocket = [&] {
+    ws->open.connect([&](StringRef) {
+      ws->SendBinary(uv::Buffer(data), [&](auto bufs, uv::Error) {
+        ++gotCallback;
+        ws->Terminate();
+        ASSERT_FALSE(bufs.empty());
+        ASSERT_EQ(bufs[0].base, reinterpret_cast<const char*>(data.data()));
+      });
+    });
+  };
+
+  loop->Run();
+
+  auto expectData = BuildMessage(0x02, true, false, data);
+  ASSERT_EQ(wireData, expectData);
+  ASSERT_EQ(gotCallback, 1);
+}
+
+TEST_P(WebSocketServerDataTest, SendPing) {
+  int gotCallback = 0;
+  std::vector<uint8_t> data(GetParam(), 0x03u);
+  setupWebSocket = [&] {
+    ws->open.connect([&](StringRef) {
+      ws->SendPing(uv::Buffer(data), [&](auto bufs, uv::Error) {
+        ++gotCallback;
+        ws->Terminate();
+        ASSERT_FALSE(bufs.empty());
+        ASSERT_EQ(bufs[0].base, reinterpret_cast<const char*>(data.data()));
+      });
+    });
+  };
+
+  loop->Run();
+
+  auto expectData = BuildMessage(0x09, true, false, data);
+  ASSERT_EQ(wireData, expectData);
+  ASSERT_EQ(gotCallback, 1);
+}
+
+TEST_P(WebSocketServerDataTest, SendPong) {
+  int gotCallback = 0;
+  std::vector<uint8_t> data(GetParam(), 0x03u);
+  setupWebSocket = [&] {
+    ws->open.connect([&](StringRef) {
+      ws->SendPong(uv::Buffer(data), [&](auto bufs, uv::Error) {
+        ++gotCallback;
+        ws->Terminate();
+        ASSERT_FALSE(bufs.empty());
+        ASSERT_EQ(bufs[0].base, reinterpret_cast<const char*>(data.data()));
+      });
+    });
+  };
+
+  loop->Run();
+
+  auto expectData = BuildMessage(0x0a, true, false, data);
+  ASSERT_EQ(wireData, expectData);
+  ASSERT_EQ(gotCallback, 1);
+}
+
+TEST_P(WebSocketServerDataTest, ReceiveText) {
+  int gotCallback = 0;
+  std::vector<uint8_t> data(GetParam(), ' ');
+  setupWebSocket = [&] {
+    ws->text.connect([&](StringRef inData, bool fin) {
+      ++gotCallback;
+      ws->Terminate();
+      ASSERT_TRUE(fin);
+      std::vector<uint8_t> recvData;
+      recvData.insert(recvData.end(), inData.bytes_begin(), inData.bytes_end());
+      ASSERT_EQ(data, recvData);
+    });
+  };
+  auto message = BuildMessage(0x01, true, true, data);
+  resp.headersComplete.connect([&](bool) {
+    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  });
+
+  loop->Run();
+
+  ASSERT_EQ(gotCallback, 1);
+}
+
+TEST_P(WebSocketServerDataTest, ReceiveBinary) {
+  int gotCallback = 0;
+  std::vector<uint8_t> data(GetParam(), 0x03u);
+  setupWebSocket = [&] {
+    ws->binary.connect([&](ArrayRef<uint8_t> inData, bool fin) {
+      ++gotCallback;
+      ws->Terminate();
+      ASSERT_TRUE(fin);
+      std::vector<uint8_t> recvData{inData.begin(), inData.end()};
+      ASSERT_EQ(data, recvData);
+    });
+  };
+  auto message = BuildMessage(0x02, true, true, data);
+  resp.headersComplete.connect([&](bool) {
+    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  });
+
+  loop->Run();
+
+  ASSERT_EQ(gotCallback, 1);
+}
+
+TEST_P(WebSocketServerDataTest, ReceivePing) {
+  int gotCallback = 0;
+  std::vector<uint8_t> data(GetParam(), 0x03u);
+  setupWebSocket = [&] {
+    ws->ping.connect([&](ArrayRef<uint8_t> inData) {
+      ++gotCallback;
+      ws->Terminate();
+      std::vector<uint8_t> recvData{inData.begin(), inData.end()};
+      ASSERT_EQ(data, recvData);
+    });
+  };
+  auto message = BuildMessage(0x09, true, true, data);
+  resp.headersComplete.connect([&](bool) {
+    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  });
+
+  loop->Run();
+
+  ASSERT_EQ(gotCallback, 1);
+}
+
+TEST_P(WebSocketServerDataTest, ReceivePong) {
+  int gotCallback = 0;
+  std::vector<uint8_t> data(GetParam(), 0x03u);
+  setupWebSocket = [&] {
+    ws->pong.connect([&](ArrayRef<uint8_t> inData) {
+      ++gotCallback;
+      ws->Terminate();
+      std::vector<uint8_t> recvData{inData.begin(), inData.end()};
+      ASSERT_EQ(data, recvData);
+    });
+  };
+  auto message = BuildMessage(0x0a, true, true, data);
+  resp.headersComplete.connect([&](bool) {
+    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  });
+
+  loop->Run();
+
+  ASSERT_EQ(gotCallback, 1);
+}
+
+//
+// The server must close the connection if an unmasked frame is received.
+//
+
+TEST_P(WebSocketServerDataTest, ReceiveUnmasked) {
+  int gotCallback = 0;
+  std::vector<uint8_t> data(GetParam(), ' ');
+  setupWebSocket = [&] {
+    ws->text.connect([&](StringRef, bool) {
+      ws->Terminate();
+      FAIL() << "Should not have gotten unmasked message";
+    });
+    ws->closed.connect([&](uint16_t code, StringRef reason) {
+      ++gotCallback;
+      ASSERT_EQ(code, 1002) << "reason: " << reason;
+    });
+  };
+  auto message = BuildMessage(0x01, true, false, data);
+  resp.headersComplete.connect([&](bool) {
+    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  });
+
+  loop->Run();
+
+  ASSERT_EQ(gotCallback, 1);
+}
+
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/WebSocketTest.cpp b/wpiutil/src/test/native/cpp/WebSocketTest.cpp
new file mode 100644
index 0000000..c27bac0
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/WebSocketTest.cpp
@@ -0,0 +1,346 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include "wpi/WebSocket.h"  // NOLINT(build/include_order)
+
+#include "WebSocketTest.h"
+
+#include "wpi/HttpParser.h"
+
+namespace wpi {
+
+#ifdef _WIN32
+const char* WebSocketTest::pipeName = "\\\\.\\pipe\\websocket-unit-test";
+#else
+const char* WebSocketTest::pipeName = "/tmp/websocket-unit-test";
+#endif
+const uint8_t WebSocketTest::testMask[4] = {0x11, 0x22, 0x33, 0x44};
+
+void WebSocketTest::SetUpTestCase() {
+#ifndef _WIN32
+  unlink(pipeName);
+#endif
+}
+
+std::vector<uint8_t> WebSocketTest::BuildHeader(uint8_t opcode, bool fin,
+                                                bool masking, uint64_t len) {
+  std::vector<uint8_t> data;
+  data.push_back(opcode | (fin ? 0x80u : 0x00u));
+  if (len < 126) {
+    data.push_back(len | (masking ? 0x80 : 0x00u));
+  } else if (len < 65536) {
+    data.push_back(126u | (masking ? 0x80 : 0x00u));
+    data.push_back(len >> 8);
+    data.push_back(len & 0xff);
+  } else {
+    data.push_back(127u | (masking ? 0x80u : 0x00u));
+    for (int i = 56; i >= 0; i -= 8) data.push_back((len >> i) & 0xff);
+  }
+  if (masking) data.insert(data.end(), &testMask[0], &testMask[4]);
+  return data;
+}
+
+std::vector<uint8_t> WebSocketTest::BuildMessage(uint8_t opcode, bool fin,
+                                                 bool masking,
+                                                 ArrayRef<uint8_t> data) {
+  auto finalData = BuildHeader(opcode, fin, masking, data.size());
+  size_t headerSize = finalData.size();
+  finalData.insert(finalData.end(), data.begin(), data.end());
+  if (masking) {
+    uint8_t mask[4] = {finalData[headerSize - 4], finalData[headerSize - 3],
+                       finalData[headerSize - 2], finalData[headerSize - 1]};
+    int n = 0;
+    for (size_t i = headerSize, end = finalData.size(); i < end; ++i) {
+      finalData[i] ^= mask[n++];
+      if (n >= 4) n = 0;
+    }
+  }
+  return finalData;
+}
+
+// If the message is masked, changes the mask to match the mask set by
+// BuildHeader() by unmasking and remasking.
+void WebSocketTest::AdjustMasking(MutableArrayRef<uint8_t> message) {
+  if (message.size() < 2) return;
+  if ((message[1] & 0x80) == 0) return;  // not masked
+  size_t maskPos;
+  uint8_t len = message[1] & 0x7f;
+  if (len == 126)
+    maskPos = 4;
+  else if (len == 127)
+    maskPos = 10;
+  else
+    maskPos = 2;
+  uint8_t mask[4] = {message[maskPos], message[maskPos + 1],
+                     message[maskPos + 2], message[maskPos + 3]};
+  message[maskPos] = testMask[0];
+  message[maskPos + 1] = testMask[1];
+  message[maskPos + 2] = testMask[2];
+  message[maskPos + 3] = testMask[3];
+  int n = 0;
+  for (auto& ch : message.slice(maskPos + 4)) {
+    ch ^= mask[n] ^ testMask[n];
+    if (++n >= 4) n = 0;
+  }
+}
+
+TEST_F(WebSocketTest, CreateClientBasic) {
+  int gotHost = 0;
+  int gotUpgrade = 0;
+  int gotConnection = 0;
+  int gotKey = 0;
+  int gotVersion = 0;
+
+  HttpParser req{HttpParser::kRequest};
+  req.url.connect([](StringRef url) { ASSERT_EQ(url, "/test"); });
+  req.header.connect([&](StringRef name, StringRef value) {
+    if (name.equals_lower("host")) {
+      ASSERT_EQ(value, pipeName);
+      ++gotHost;
+    } else if (name.equals_lower("upgrade")) {
+      ASSERT_EQ(value, "websocket");
+      ++gotUpgrade;
+    } else if (name.equals_lower("connection")) {
+      ASSERT_EQ(value, "Upgrade");
+      ++gotConnection;
+    } else if (name.equals_lower("sec-websocket-key")) {
+      ++gotKey;
+    } else if (name.equals_lower("sec-websocket-version")) {
+      ASSERT_EQ(value, "13");
+      ++gotVersion;
+    } else {
+      FAIL() << "unexpected header " << name.str();
+    }
+  });
+  req.headersComplete.connect([&](bool) { Finish(); });
+
+  serverPipe->Listen([&]() {
+    auto conn = serverPipe->Accept();
+    conn->StartRead();
+    conn->data.connect([&](uv::Buffer& buf, size_t size) {
+      req.Execute(StringRef{buf.base, size});
+      if (req.HasError()) Finish();
+      ASSERT_EQ(req.GetError(), HPE_OK) << http_errno_name(req.GetError());
+    });
+  });
+  clientPipe->Connect(pipeName, [&]() {
+    auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
+  });
+
+  loop->Run();
+
+  if (HasFatalFailure()) return;
+  ASSERT_EQ(gotHost, 1);
+  ASSERT_EQ(gotUpgrade, 1);
+  ASSERT_EQ(gotConnection, 1);
+  ASSERT_EQ(gotKey, 1);
+  ASSERT_EQ(gotVersion, 1);
+}
+
+TEST_F(WebSocketTest, CreateClientExtraHeaders) {
+  int gotExtra1 = 0;
+  int gotExtra2 = 0;
+  HttpParser req{HttpParser::kRequest};
+  req.header.connect([&](StringRef name, StringRef value) {
+    if (name.equals("Extra1")) {
+      ASSERT_EQ(value, "Data1");
+      ++gotExtra1;
+    } else if (name.equals("Extra2")) {
+      ASSERT_EQ(value, "Data2");
+      ++gotExtra2;
+    }
+  });
+  req.headersComplete.connect([&](bool) { Finish(); });
+
+  serverPipe->Listen([&]() {
+    auto conn = serverPipe->Accept();
+    conn->StartRead();
+    conn->data.connect([&](uv::Buffer& buf, size_t size) {
+      req.Execute(StringRef{buf.base, size});
+      if (req.HasError()) Finish();
+      ASSERT_EQ(req.GetError(), HPE_OK) << http_errno_name(req.GetError());
+    });
+  });
+  clientPipe->Connect(pipeName, [&]() {
+    WebSocket::ClientOptions options;
+    SmallVector<std::pair<StringRef, StringRef>, 4> extraHeaders;
+    extraHeaders.emplace_back("Extra1", "Data1");
+    extraHeaders.emplace_back("Extra2", "Data2");
+    options.extraHeaders = extraHeaders;
+    auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName,
+                                      ArrayRef<StringRef>{}, options);
+  });
+
+  loop->Run();
+
+  if (HasFatalFailure()) return;
+  ASSERT_EQ(gotExtra1, 1);
+  ASSERT_EQ(gotExtra2, 1);
+}
+
+TEST_F(WebSocketTest, CreateClientTimeout) {
+  int gotClosed = 0;
+  serverPipe->Listen([&]() { auto conn = serverPipe->Accept(); });
+  clientPipe->Connect(pipeName, [&]() {
+    WebSocket::ClientOptions options;
+    options.handshakeTimeout = uv::Timer::Time{100};
+    auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName,
+                                      ArrayRef<StringRef>{}, options);
+    ws->closed.connect([&](uint16_t code, StringRef) {
+      Finish();
+      ++gotClosed;
+      ASSERT_EQ(code, 1006);
+    });
+  });
+
+  loop->Run();
+
+  if (HasFatalFailure()) return;
+  ASSERT_EQ(gotClosed, 1);
+}
+
+TEST_F(WebSocketTest, CreateServerBasic) {
+  int gotStatus = 0;
+  int gotUpgrade = 0;
+  int gotConnection = 0;
+  int gotAccept = 0;
+  int gotOpen = 0;
+
+  HttpParser resp{HttpParser::kResponse};
+  resp.status.connect([&](StringRef status) {
+    ++gotStatus;
+    ASSERT_EQ(resp.GetStatusCode(), 101u) << "status: " << status;
+  });
+  resp.header.connect([&](StringRef name, StringRef value) {
+    if (name.equals_lower("upgrade")) {
+      ASSERT_EQ(value, "websocket");
+      ++gotUpgrade;
+    } else if (name.equals_lower("connection")) {
+      ASSERT_EQ(value, "Upgrade");
+      ++gotConnection;
+    } else if (name.equals_lower("sec-websocket-accept")) {
+      ASSERT_EQ(value, "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=");
+      ++gotAccept;
+    } else {
+      FAIL() << "unexpected header " << name.str();
+    }
+  });
+  resp.headersComplete.connect([&](bool) { Finish(); });
+
+  serverPipe->Listen([&]() {
+    auto conn = serverPipe->Accept();
+    auto ws = WebSocket::CreateServer(*conn, "dGhlIHNhbXBsZSBub25jZQ==", "13");
+    ws->open.connect([&](StringRef protocol) {
+      ++gotOpen;
+      ASSERT_TRUE(protocol.empty());
+    });
+  });
+  clientPipe->Connect(pipeName, [&] {
+    clientPipe->StartRead();
+    clientPipe->data.connect([&](uv::Buffer& buf, size_t size) {
+      resp.Execute(StringRef{buf.base, size});
+      if (resp.HasError()) Finish();
+      ASSERT_EQ(resp.GetError(), HPE_OK) << http_errno_name(resp.GetError());
+    });
+  });
+
+  loop->Run();
+
+  if (HasFatalFailure()) return;
+  ASSERT_EQ(gotStatus, 1);
+  ASSERT_EQ(gotUpgrade, 1);
+  ASSERT_EQ(gotConnection, 1);
+  ASSERT_EQ(gotAccept, 1);
+  ASSERT_EQ(gotOpen, 1);
+}
+
+TEST_F(WebSocketTest, CreateServerProtocol) {
+  int gotProtocol = 0;
+  int gotOpen = 0;
+
+  HttpParser resp{HttpParser::kResponse};
+  resp.header.connect([&](StringRef name, StringRef value) {
+    if (name.equals_lower("sec-websocket-protocol")) {
+      ++gotProtocol;
+      ASSERT_EQ(value, "myProtocol");
+    }
+  });
+  resp.headersComplete.connect([&](bool) { Finish(); });
+
+  serverPipe->Listen([&]() {
+    auto conn = serverPipe->Accept();
+    auto ws = WebSocket::CreateServer(*conn, "foo", "13", "myProtocol");
+    ws->open.connect([&](StringRef protocol) {
+      ++gotOpen;
+      ASSERT_EQ(protocol, "myProtocol");
+    });
+  });
+  clientPipe->Connect(pipeName, [&] {
+    clientPipe->StartRead();
+    clientPipe->data.connect([&](uv::Buffer& buf, size_t size) {
+      resp.Execute(StringRef{buf.base, size});
+      if (resp.HasError()) Finish();
+      ASSERT_EQ(resp.GetError(), HPE_OK) << http_errno_name(resp.GetError());
+    });
+  });
+
+  loop->Run();
+
+  if (HasFatalFailure()) return;
+  ASSERT_EQ(gotProtocol, 1);
+  ASSERT_EQ(gotOpen, 1);
+}
+
+TEST_F(WebSocketTest, CreateServerBadVersion) {
+  int gotStatus = 0;
+  int gotVersion = 0;
+  int gotUpgrade = 0;
+
+  HttpParser resp{HttpParser::kResponse};
+  resp.status.connect([&](StringRef status) {
+    ++gotStatus;
+    ASSERT_EQ(resp.GetStatusCode(), 426u) << "status: " << status;
+  });
+  resp.header.connect([&](StringRef name, StringRef value) {
+    if (name.equals_lower("sec-websocket-version")) {
+      ++gotVersion;
+      ASSERT_EQ(value, "13");
+    } else if (name.equals_lower("upgrade")) {
+      ++gotUpgrade;
+      ASSERT_EQ(value, "WebSocket");
+    } else {
+      FAIL() << "unexpected header " << name.str();
+    }
+  });
+  resp.headersComplete.connect([&](bool) { Finish(); });
+
+  serverPipe->Listen([&] {
+    auto conn = serverPipe->Accept();
+    auto ws = WebSocket::CreateServer(*conn, "foo", "14");
+    ws->open.connect([&](StringRef) {
+      Finish();
+      FAIL();
+    });
+  });
+  clientPipe->Connect(pipeName, [&] {
+    clientPipe->StartRead();
+    clientPipe->data.connect([&](uv::Buffer& buf, size_t size) {
+      resp.Execute(StringRef{buf.base, size});
+      if (resp.HasError()) Finish();
+      ASSERT_EQ(resp.GetError(), HPE_OK) << http_errno_name(resp.GetError());
+    });
+  });
+
+  loop->Run();
+
+  if (HasFatalFailure()) return;
+  ASSERT_EQ(gotStatus, 1);
+  ASSERT_EQ(gotVersion, 1);
+  ASSERT_EQ(gotUpgrade, 1);
+}
+
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/WebSocketTest.h b/wpiutil/src/test/native/cpp/WebSocketTest.h
new file mode 100644
index 0000000..8b40440
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/WebSocketTest.h
@@ -0,0 +1,73 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#pragma once
+
+#include <cstdio>
+#include <memory>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "wpi/ArrayRef.h"
+#include "wpi/uv/Loop.h"
+#include "wpi/uv/Pipe.h"
+#include "wpi/uv/Timer.h"
+
+namespace wpi {
+
+class WebSocketTest : public ::testing::Test {
+ public:
+  static const char* pipeName;
+
+  static void SetUpTestCase();
+
+  WebSocketTest() {
+    loop = uv::Loop::Create();
+    clientPipe = uv::Pipe::Create(loop);
+    serverPipe = uv::Pipe::Create(loop);
+
+    serverPipe->Bind(pipeName);
+
+#if 0
+    auto debugTimer = uv::Timer::Create(loop);
+    debugTimer->timeout.connect([this] {
+      std::printf("Active handles:\n");
+      uv_print_active_handles(loop->GetRaw(), stdout);
+    });
+    debugTimer->Start(uv::Timer::Time{100}, uv::Timer::Time{100});
+    debugTimer->Unreference();
+#endif
+
+    auto failTimer = uv::Timer::Create(loop);
+    failTimer->timeout.connect([this] {
+      loop->Stop();
+      FAIL() << "loop failed to terminate";
+    });
+    failTimer->Start(uv::Timer::Time{1000});
+    failTimer->Unreference();
+  }
+
+  ~WebSocketTest() { Finish(); }
+
+  void Finish() {
+    loop->Walk([](uv::Handle& it) { it.Close(); });
+  }
+
+  static std::vector<uint8_t> BuildHeader(uint8_t opcode, bool fin,
+                                          bool masking, uint64_t len);
+  static std::vector<uint8_t> BuildMessage(uint8_t opcode, bool fin,
+                                           bool masking,
+                                           ArrayRef<uint8_t> data);
+  static void AdjustMasking(MutableArrayRef<uint8_t> message);
+  static const uint8_t testMask[4];
+
+  std::shared_ptr<uv::Loop> loop;
+  std::shared_ptr<uv::Pipe> clientPipe;
+  std::shared_ptr<uv::Pipe> serverPipe;
+};
+
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/WorkerThreadTest.cpp b/wpiutil/src/test/native/cpp/WorkerThreadTest.cpp
new file mode 100644
index 0000000..610029c
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/WorkerThreadTest.cpp
@@ -0,0 +1,73 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include "wpi/WorkerThread.h"  // NOLINT(build/include_order)
+
+#include "gtest/gtest.h"  // NOLINT(build/include_order)
+
+#include <thread>
+
+#include "wpi/uv/Loop.h"
+
+namespace wpi {
+
+TEST(WorkerThread, Future) {
+  WorkerThread<int(bool)> worker;
+  future<int> f =
+      worker.QueueWork([](bool v) -> int { return v ? 1 : 2; }, true);
+  ASSERT_EQ(f.get(), 1);
+}
+
+TEST(WorkerThread, FutureVoid) {
+  int callbacks = 0;
+  WorkerThread<void(int)> worker;
+  future<void> f = worker.QueueWork(
+      [&](int v) {
+        ++callbacks;
+        ASSERT_EQ(v, 3);
+      },
+      3);
+  f.get();
+  ASSERT_EQ(callbacks, 1);
+}
+
+TEST(WorkerThread, Loop) {
+  int callbacks = 0;
+  WorkerThread<int(bool)> worker;
+  auto loop = uv::Loop::Create();
+  worker.SetLoop(*loop);
+  worker.QueueWorkThen([](bool v) -> int { return v ? 1 : 2; },
+                       [&](int v2) {
+                         ++callbacks;
+                         loop->Stop();
+                         ASSERT_EQ(v2, 1);
+                       },
+                       true);
+  auto f = worker.QueueWork([](bool) -> int { return 2; }, true);
+  ASSERT_EQ(f.get(), 2);
+  loop->Run();
+  ASSERT_EQ(callbacks, 1);
+}
+
+TEST(WorkerThread, LoopVoid) {
+  int callbacks = 0;
+  WorkerThread<void(bool)> worker;
+  auto loop = uv::Loop::Create();
+  worker.SetLoop(*loop);
+  worker.QueueWorkThen([](bool) {},
+                       [&]() {
+                         ++callbacks;
+                         loop->Stop();
+                       },
+                       true);
+  auto f = worker.QueueWork([](bool) {}, true);
+  f.get();
+  loop->Run();
+  ASSERT_EQ(callbacks, 1);
+}
+
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/future_test.cpp b/wpiutil/src/test/native/cpp/future_test.cpp
new file mode 100644
index 0000000..b9b79a8
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/future_test.cpp
@@ -0,0 +1,84 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include "wpi/future.h"  // NOLINT(build/include_order)
+
+#include "gtest/gtest.h"  // NOLINT(build/include_order)
+
+#include <thread>
+
+namespace wpi {
+
+TEST(Future, Then) {
+  promise<bool> inPromise;
+  future<int> outFuture =
+      inPromise.get_future().then([](bool v) { return v ? 5 : 6; });
+
+  inPromise.set_value(true);
+  ASSERT_EQ(outFuture.get(), 5);
+}
+
+TEST(Future, ThenSame) {
+  promise<bool> inPromise;
+  future<bool> outFuture =
+      inPromise.get_future().then([](bool v) { return !v; });
+
+  inPromise.set_value(true);
+  ASSERT_EQ(outFuture.get(), false);
+}
+
+TEST(Future, ThenFromVoid) {
+  promise<void> inPromise;
+  future<int> outFuture = inPromise.get_future().then([] { return 5; });
+
+  inPromise.set_value();
+  ASSERT_EQ(outFuture.get(), 5);
+}
+
+TEST(Future, ThenToVoid) {
+  promise<bool> inPromise;
+  future<void> outFuture = inPromise.get_future().then([](bool v) {});
+
+  inPromise.set_value(true);
+  ASSERT_TRUE(outFuture.is_ready());
+}
+
+TEST(Future, ThenVoidVoid) {
+  promise<void> inPromise;
+  future<void> outFuture = inPromise.get_future().then([] {});
+
+  inPromise.set_value();
+  ASSERT_TRUE(outFuture.is_ready());
+}
+
+TEST(Future, Implicit) {
+  promise<bool> inPromise;
+  future<int> outFuture = inPromise.get_future();
+
+  inPromise.set_value(true);
+  ASSERT_EQ(outFuture.get(), 1);
+}
+
+TEST(Future, MoveSame) {
+  promise<bool> inPromise;
+  future<bool> outFuture1 = inPromise.get_future();
+  future<bool> outFuture(std::move(outFuture1));
+
+  inPromise.set_value(true);
+  ASSERT_EQ(outFuture.get(), true);
+}
+
+TEST(Future, MoveVoid) {
+  promise<void> inPromise;
+  future<void> outFuture1 = inPromise.get_future();
+  future<void> outFuture(std::move(outFuture1));
+
+  inPromise.set_value();
+  ASSERT_TRUE(outFuture.is_ready());
+}
+
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/hostname.cpp b/wpiutil/src/test/native/cpp/hostname.cpp
new file mode 100644
index 0000000..d5ec535
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/hostname.cpp
@@ -0,0 +1,24 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved.                        */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include "wpi/hostname.h"
+
+#include "gtest/gtest.h"
+#include "wpi/SmallString.h"
+#include "wpi/SmallVector.h"
+
+namespace wpi {
+TEST(HostNameTest, HostNameNotEmpty) { ASSERT_NE(GetHostname(), ""); }
+TEST(HostNameTest, HostNameNotEmptySmallVector) {
+  SmallVector<char, 256> name;
+  ASSERT_NE(GetHostname(name), "");
+}
+TEST(HostNameTest, HostNameEq) {
+  SmallVector<char, 256> nameBuf;
+  ASSERT_EQ(GetHostname(nameBuf), GetHostname());
+}
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/json/unit-algorithms.cpp b/wpiutil/src/test/native/cpp/json/unit-algorithms.cpp
new file mode 100644
index 0000000..f1a8745
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-algorithms.cpp
@@ -0,0 +1,310 @@
+/*----------------------------------------------------------------------------*/
+/* Modifications Copyright (c) FIRST 2017. All Rights Reserved.               */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include "unit-json.h"
+using wpi::json;
+
+#include <algorithm>
+
+class JsonAlgorithmsTest : public ::testing::Test {
+ protected:
+    json j_array = {13, 29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz"};
+    json j_object = {{"one", 1}, {"two", 2}};
+};
+
+// non-modifying sequence operations
+TEST_F(JsonAlgorithmsTest, AllOf)
+{
+    EXPECT_TRUE(std::all_of(j_array.begin(), j_array.end(), [](const json & value)
+    {
+        return value.size() > 0;
+    }));
+    EXPECT_TRUE(std::all_of(j_object.begin(), j_object.end(), [](const json & value)
+    {
+        return value.type() == json::value_t::number_integer;
+    }));
+}
+
+TEST_F(JsonAlgorithmsTest, AnyOf)
+{
+    EXPECT_TRUE(std::any_of(j_array.begin(), j_array.end(), [](const json & value)
+    {
+        return value.is_string() && value.get<std::string>() == "foo";
+    }));
+    EXPECT_TRUE(std::any_of(j_object.begin(), j_object.end(), [](const json & value)
+    {
+        return value.get<int>() > 1;
+    }));
+}
+
+TEST_F(JsonAlgorithmsTest, NoneOf)
+{
+    EXPECT_TRUE(std::none_of(j_array.begin(), j_array.end(), [](const json & value)
+    {
+        return value.size() == 0;
+    }));
+    EXPECT_TRUE(std::none_of(j_object.begin(), j_object.end(), [](const json & value)
+    {
+        return value.get<int>() <= 0;
+    }));
+}
+
+TEST_F(JsonAlgorithmsTest, ForEachReading)
+{
+    int sum = 0;
+
+    std::for_each(j_array.cbegin(), j_array.cend(), [&sum](const json & value)
+    {
+        if (value.is_number())
+        {
+            sum += static_cast<int>(value);
+        }
+    });
+
+    EXPECT_EQ(sum, 45);
+}
+
+TEST_F(JsonAlgorithmsTest, ForEachWriting)
+{
+    auto add17 = [](json & value)
+    {
+        if (value.is_array())
+        {
+            value.push_back(17);
+        }
+    };
+
+    std::for_each(j_array.begin(), j_array.end(), add17);
+
+    EXPECT_EQ(j_array[6], json({1, 2, 3, 17}));
+}
+
+TEST_F(JsonAlgorithmsTest, Count)
+{
+    EXPECT_EQ(std::count(j_array.begin(), j_array.end(), json(true)), 1);
+}
+
+TEST_F(JsonAlgorithmsTest, CountIf)
+{
+    auto count1 = std::count_if(j_array.begin(), j_array.end(), [](const json & value)
+    {
+        return (value.is_number());
+    });
+    EXPECT_EQ(count1, 3);
+    auto count2 = std::count_if(j_array.begin(), j_array.end(), [](const json&)
+    {
+        return true;
+    });
+    EXPECT_EQ(count2, 9);
+}
+
+TEST_F(JsonAlgorithmsTest, Mismatch)
+{
+    json j_array2 = {13, 29, 3, {{"one", 1}, {"two", 2}, {"three", 3}}, true, false, {1, 2, 3}, "foo", "baz"};
+    auto res = std::mismatch(j_array.begin(), j_array.end(), j_array2.begin());
+    EXPECT_EQ(*res.first, json({{"one", 1}, {"two", 2}}));
+    EXPECT_EQ(*res.second, json({{"one", 1}, {"two", 2}, {"three", 3}}));
+}
+
+TEST_F(JsonAlgorithmsTest, EqualOperatorEquals)
+{
+    EXPECT_TRUE(std::equal(j_array.begin(), j_array.end(), j_array.begin()));
+    EXPECT_TRUE(std::equal(j_object.begin(), j_object.end(), j_object.begin()));
+    EXPECT_FALSE(std::equal(j_array.begin(), j_array.end(), j_object.begin()));
+}
+
+TEST_F(JsonAlgorithmsTest, EqualUserComparison)
+{
+    // compare objects only by size of its elements
+    json j_array2 = {13, 29, 3, {"Hello", "World"}, true, false, {{"one", 1}, {"two", 2}, {"three", 3}}, "foo", "baz"};
+    EXPECT_FALSE(std::equal(j_array.begin(), j_array.end(), j_array2.begin()));
+    EXPECT_TRUE(std::equal(j_array.begin(), j_array.end(), j_array2.begin(),
+                     [](const json & a, const json & b)
+    {
+        return (a.size() == b.size());
+    }));
+}
+
+TEST_F(JsonAlgorithmsTest, Find)
+{
+    auto it = std::find(j_array.begin(), j_array.end(), json(false));
+    EXPECT_EQ(std::distance(j_array.begin(), it), 5);
+}
+
+TEST_F(JsonAlgorithmsTest, FindIf)
+{
+    auto it = std::find_if(j_array.begin(), j_array.end(),
+                           [](const json & value)
+    {
+        return value.is_boolean();
+    });
+    EXPECT_EQ(std::distance(j_array.begin(), it), 4);
+}
+
+TEST_F(JsonAlgorithmsTest, FindIfNot)
+{
+    auto it = std::find_if_not(j_array.begin(), j_array.end(),
+                               [](const json & value)
+    {
+        return value.is_number();
+    });
+    EXPECT_EQ(std::distance(j_array.begin(), it), 3);
+}
+
+TEST_F(JsonAlgorithmsTest, AdjacentFind)
+{
+    EXPECT_EQ(std::adjacent_find(j_array.begin(), j_array.end()), j_array.end());
+    auto it = std::adjacent_find(j_array.begin(), j_array.end(),
+                             [](const json & v1, const json & v2)
+    {
+        return v1.type() == v2.type();
+    });
+    EXPECT_EQ(it, j_array.begin());
+}
+
+// modifying sequence operations
+TEST_F(JsonAlgorithmsTest, Reverse)
+{
+    std::reverse(j_array.begin(), j_array.end());
+    EXPECT_EQ(j_array, json({"baz", "foo", {1, 2, 3}, false, true, {{"one", 1}, {"two", 2}}, 3, 29, 13}));
+}
+
+TEST_F(JsonAlgorithmsTest, Rotate)
+{
+    std::rotate(j_array.begin(), j_array.begin() + 1, j_array.end());
+    EXPECT_EQ(j_array, json({29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz", 13}));
+}
+
+TEST_F(JsonAlgorithmsTest, Partition)
+{
+    auto it = std::partition(j_array.begin(), j_array.end(), [](const json & v)
+    {
+        return v.is_string();
+    });
+    EXPECT_EQ(std::distance(j_array.begin(), it), 2);
+    EXPECT_FALSE(it[2].is_string());
+}
+
+// sorting operations
+TEST_F(JsonAlgorithmsTest, SortOperatorEquals)
+{
+    json j = {13, 29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz", nullptr};
+    std::sort(j.begin(), j.end());
+    EXPECT_EQ(j, json({nullptr, false, true, 3, 13, 29, {{"one", 1}, {"two", 2}}, {1, 2, 3}, "baz", "foo"}));
+}
+
+TEST_F(JsonAlgorithmsTest, SortUserComparison)
+{
+    json j = {3, {{"one", 1}, {"two", 2}}, {1, 2, 3}, nullptr};
+    std::sort(j.begin(), j.end(), [](const json & a, const json & b)
+    {
+        return a.size() < b.size();
+    });
+    EXPECT_EQ(j, json({nullptr, 3, {{"one", 1}, {"two", 2}}, {1, 2, 3}}));
+}
+
+TEST_F(JsonAlgorithmsTest, SortObject)
+{
+    json j({{"one", 1}, {"two", 2}});
+    EXPECT_THROW_MSG(std::sort(j.begin(), j.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+}
+
+TEST_F(JsonAlgorithmsTest, PartialSort)
+{
+    json j = {13, 29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz", nullptr};
+    std::partial_sort(j.begin(), j.begin() + 4, j.end());
+    EXPECT_EQ(j, json({nullptr, false, true, 3, {{"one", 1}, {"two", 2}}, 29, {1, 2, 3}, "foo", "baz", 13}));
+}
+
+// set operations
+TEST_F(JsonAlgorithmsTest, Merge)
+{
+    json j1 = {2, 4, 6, 8};
+    json j2 = {1, 2, 3, 5, 7};
+    json j3;
+
+    std::merge(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
+    EXPECT_EQ(j3, json({1, 2, 2, 3, 4, 5, 6, 7, 8}));
+}
+
+TEST_F(JsonAlgorithmsTest, SetDifference)
+{
+    json j1 = {1, 2, 3, 4, 5, 6, 7, 8};
+    json j2 = {1, 2, 3, 5, 7};
+    json j3;
+
+    std::set_difference(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
+    EXPECT_EQ(j3, json({4, 6, 8}));
+}
+
+TEST_F(JsonAlgorithmsTest, SetIntersection)
+{
+    json j1 = {1, 2, 3, 4, 5, 6, 7, 8};
+    json j2 = {1, 2, 3, 5, 7};
+    json j3;
+
+    std::set_intersection(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
+    EXPECT_EQ(j3, json({1, 2, 3, 5, 7}));
+}
+
+TEST_F(JsonAlgorithmsTest, SetUnion)
+{
+    json j1 = {2, 4, 6, 8};
+    json j2 = {1, 2, 3, 5, 7};
+    json j3;
+
+    std::set_union(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
+    EXPECT_EQ(j3, json({1, 2, 3, 4, 5, 6, 7, 8}));
+}
+
+TEST_F(JsonAlgorithmsTest, SetSymmetricDifference)
+{
+    json j1 = {2, 4, 6, 8};
+    json j2 = {1, 2, 3, 5, 7};
+    json j3;
+
+    std::set_symmetric_difference(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
+    EXPECT_EQ(j3, json({1, 3, 4, 5, 6, 7, 8}));
+}
+
+TEST_F(JsonAlgorithmsTest, HeapOperations)
+{
+    std::make_heap(j_array.begin(), j_array.end());
+    EXPECT_TRUE(std::is_heap(j_array.begin(), j_array.end()));
+    std::sort_heap(j_array.begin(), j_array.end());
+    EXPECT_EQ(j_array, json({false, true, 3, 13, 29, {{"one", 1}, {"two", 2}}, {1, 2, 3}, "baz", "foo"}));
+}
diff --git a/wpiutil/src/test/native/cpp/json/unit-capacity.cpp b/wpiutil/src/test/native/cpp/json/unit-capacity.cpp
new file mode 100644
index 0000000..8f5433e
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-capacity.cpp
@@ -0,0 +1,528 @@
+/*----------------------------------------------------------------------------*/
+/* Modifications Copyright (c) FIRST 2017. All Rights Reserved.               */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include "unit-json.h"
+using wpi::json;
+
+TEST(JsonEmptyTest, Boolean)
+{
+    json j = true;
+    json j_const(j);
+
+    // result of empty
+    {
+        EXPECT_FALSE(j.empty());
+        EXPECT_FALSE(j_const.empty());
+    }
+
+    // definition of empty
+    {
+        EXPECT_EQ(j.empty(), (j.begin() == j.end()));
+        EXPECT_EQ(j_const.empty(), (j_const.begin() == j_const.end()));
+    }
+}
+
+TEST(JsonEmptyTest, String)
+{
+    json j = "hello world";
+    json j_const(j);
+
+    // result of empty
+    {
+        EXPECT_FALSE(j.empty());
+        EXPECT_FALSE(j_const.empty());
+    }
+
+    // definition of empty
+    {
+        EXPECT_EQ(j.empty(), (j.begin() == j.end()));
+        EXPECT_EQ(j_const.empty(), (j_const.begin() == j_const.end()));
+    }
+}
+
+TEST(JsonEmptyTest, ArrayEmpty)
+{
+    json j = json::array();
+    json j_const(j);
+
+    // result of empty
+    {
+        EXPECT_TRUE(j.empty());
+        EXPECT_TRUE(j_const.empty());
+    }
+
+    // definition of empty
+    {
+        EXPECT_EQ(j.empty(), (j.begin() == j.end()));
+        EXPECT_EQ(j_const.empty(), (j_const.begin() == j_const.end()));
+    }
+}
+
+TEST(JsonEmptyTest, ArrayFilled)
+{
+    json j = {1, 2, 3};
+    json j_const(j);
+
+    // result of empty
+    {
+        EXPECT_FALSE(j.empty());
+        EXPECT_FALSE(j_const.empty());
+    }
+
+    // definition of empty
+    {
+        EXPECT_EQ(j.empty(), (j.begin() == j.end()));
+        EXPECT_EQ(j_const.empty(), (j_const.begin() == j_const.end()));
+    }
+}
+
+TEST(JsonEmptyTest, ObjectEmpty)
+{
+    json j = json::object();
+    json j_const(j);
+
+    // result of empty
+    {
+        EXPECT_TRUE(j.empty());
+        EXPECT_TRUE(j_const.empty());
+    }
+
+    // definition of empty
+    {
+        EXPECT_EQ(j.empty(), (j.begin() == j.end()));
+        EXPECT_EQ(j_const.empty(), (j_const.begin() == j_const.end()));
+    }
+}
+
+TEST(JsonEmptyTest, ObjectFilled)
+{
+    json j = {{"one", 1}, {"two", 2}, {"three", 3}};
+    json j_const(j);
+
+    // result of empty
+    {
+        EXPECT_FALSE(j.empty());
+        EXPECT_FALSE(j_const.empty());
+    }
+
+    // definition of empty
+    {
+        EXPECT_EQ(j.empty(), (j.begin() == j.end()));
+        EXPECT_EQ(j_const.empty(), (j_const.begin() == j_const.end()));
+    }
+}
+
+TEST(JsonEmptyTest, NumberInteger)
+{
+    json j = 23;
+    json j_const(j);
+
+    // result of empty
+    {
+        EXPECT_FALSE(j.empty());
+        EXPECT_FALSE(j_const.empty());
+    }
+
+    // definition of empty
+    {
+        EXPECT_EQ(j.empty(), (j.begin() == j.end()));
+        EXPECT_EQ(j_const.empty(), (j_const.begin() == j_const.end()));
+    }
+}
+
+TEST(JsonEmptyTest, NumberUnsigned)
+{
+    json j = 23u;
+    json j_const(j);
+
+    // result of empty
+    {
+        EXPECT_FALSE(j.empty());
+        EXPECT_FALSE(j_const.empty());
+    }
+
+    // definition of empty
+    {
+        EXPECT_EQ(j.empty(), (j.begin() == j.end()));
+        EXPECT_EQ(j_const.empty(), (j_const.begin() == j_const.end()));
+    }
+}
+
+TEST(JsonEmptyTest, NumberFloat)
+{
+    json j = 23.42;
+    json j_const(j);
+
+    // result of empty
+    {
+        EXPECT_FALSE(j.empty());
+        EXPECT_FALSE(j_const.empty());
+    }
+
+    // definition of empty
+    {
+        EXPECT_EQ(j.empty(), (j.begin() == j.end()));
+        EXPECT_EQ(j_const.empty(), (j_const.begin() == j_const.end()));
+    }
+}
+
+TEST(JsonEmptyTest, Null)
+{
+    json j = nullptr;
+    json j_const(j);
+
+    // result of empty
+    {
+        EXPECT_TRUE(j.empty());
+        EXPECT_TRUE(j_const.empty());
+    }
+
+    // definition of empty
+    {
+        EXPECT_EQ(j.empty(), (j.begin() == j.end()));
+        EXPECT_EQ(j_const.empty(), (j_const.begin() == j_const.end()));
+    }
+}
+
+TEST(JsonSizeTest, Boolean)
+{
+    json j = true;
+    json j_const(j);
+
+    // result of size
+    {
+        EXPECT_EQ(j.size(), 1u);
+        EXPECT_EQ(j_const.size(), 1u);
+    }
+
+    // definition of size
+    {
+        EXPECT_EQ(std::distance(j.begin(), j.end()), static_cast<int>(j.size()));
+        EXPECT_EQ(std::distance(j_const.begin(), j_const.end()),
+                  static_cast<int>(j_const.size()));
+    }
+}
+
+TEST(JsonSizeTest, String)
+{
+    json j = "hello world";
+    json j_const(j);
+
+    // result of size
+    {
+        EXPECT_EQ(j.size(), 1u);
+        EXPECT_EQ(j_const.size(), 1u);
+    }
+
+    // definition of size
+    {
+        EXPECT_EQ(std::distance(j.begin(), j.end()), static_cast<int>(j.size()));
+        EXPECT_EQ(std::distance(j_const.begin(), j_const.end()),
+                  static_cast<int>(j_const.size()));
+    }
+}
+
+TEST(JsonSizeTest, ArrayEmpty)
+{
+    json j = json::array();
+    json j_const(j);
+
+    // result of size
+    {
+        EXPECT_EQ(j.size(), 0u);
+        EXPECT_EQ(j_const.size(), 0u);
+    }
+
+    // definition of size
+    {
+        EXPECT_EQ(std::distance(j.begin(), j.end()), static_cast<int>(j.size()));
+        EXPECT_EQ(std::distance(j_const.begin(), j_const.end()),
+                  static_cast<int>(j_const.size()));
+    }
+}
+
+TEST(JsonSizeTest, ArrayFilled)
+{
+    json j = {1, 2, 3};
+    json j_const(j);
+
+    // result of size
+    {
+        EXPECT_EQ(j.size(), 3u);
+        EXPECT_EQ(j_const.size(), 3u);
+    }
+
+    // definition of size
+    {
+        EXPECT_EQ(std::distance(j.begin(), j.end()), static_cast<int>(j.size()));
+        EXPECT_EQ(std::distance(j_const.begin(), j_const.end()),
+                  static_cast<int>(j_const.size()));
+    }
+}
+
+TEST(JsonSizeTest, ObjectEmpty)
+{
+    json j = json::object();
+    json j_const(j);
+
+    // result of size
+    {
+        EXPECT_EQ(j.size(), 0u);
+        EXPECT_EQ(j_const.size(), 0u);
+    }
+
+    // definition of size
+    {
+        EXPECT_EQ(std::distance(j.begin(), j.end()), static_cast<int>(j.size()));
+        EXPECT_EQ(std::distance(j_const.begin(), j_const.end()),
+                  static_cast<int>(j_const.size()));
+    }
+}
+
+TEST(JsonSizeTest, ObjectFilled)
+{
+    json j = {{"one", 1}, {"two", 2}, {"three", 3}};
+    json j_const(j);
+
+    // result of size
+    {
+        EXPECT_EQ(j.size(), 3u);
+        EXPECT_EQ(j_const.size(), 3u);
+    }
+
+    // definition of size
+    {
+        EXPECT_EQ(std::distance(j.begin(), j.end()), static_cast<int>(j.size()));
+        EXPECT_EQ(std::distance(j_const.begin(), j_const.end()),
+                  static_cast<int>(j_const.size()));
+    }
+}
+
+TEST(JsonSizeTest, NumberInteger)
+{
+    json j = 23;
+    json j_const(j);
+
+    // result of size
+    {
+        EXPECT_EQ(j.size(), 1u);
+        EXPECT_EQ(j_const.size(), 1u);
+    }
+
+    // definition of size
+    {
+        EXPECT_EQ(std::distance(j.begin(), j.end()), static_cast<int>(j.size()));
+        EXPECT_EQ(std::distance(j_const.begin(), j_const.end()),
+                  static_cast<int>(j_const.size()));
+    }
+}
+
+TEST(JsonSizeTest, NumberUnsigned)
+{
+    json j = 23u;
+    json j_const(j);
+
+    // result of size
+    {
+        EXPECT_EQ(j.size(), 1u);
+        EXPECT_EQ(j_const.size(), 1u);
+    }
+
+    // definition of size
+    {
+        EXPECT_EQ(std::distance(j.begin(), j.end()), static_cast<int>(j.size()));
+        EXPECT_EQ(std::distance(j_const.begin(), j_const.end()),
+                  static_cast<int>(j_const.size()));
+    }
+}
+
+TEST(JsonSizeTest, NumberFloat)
+{
+    json j = 23.42;
+    json j_const(j);
+
+    // result of size
+    {
+        EXPECT_EQ(j.size(), 1u);
+        EXPECT_EQ(j_const.size(), 1u);
+    }
+
+    // definition of size
+    {
+        EXPECT_EQ(std::distance(j.begin(), j.end()), static_cast<int>(j.size()));
+        EXPECT_EQ(std::distance(j_const.begin(), j_const.end()),
+                  static_cast<int>(j_const.size()));
+    }
+}
+
+TEST(JsonSizeTest, Null)
+{
+    json j = nullptr;
+    json j_const(j);
+
+    // result of size
+    {
+        EXPECT_EQ(j.size(), 0u);
+        EXPECT_EQ(j_const.size(), 0u);
+    }
+
+    // definition of size
+    {
+        EXPECT_EQ(std::distance(j.begin(), j.end()), static_cast<int>(j.size()));
+        EXPECT_EQ(std::distance(j_const.begin(), j_const.end()),
+                  static_cast<int>(j_const.size()));
+    }
+}
+
+TEST(JsonMaxSizeTest, Boolean)
+{
+    json j = true;
+    json j_const(j);
+
+    // result of max_size
+    {
+        EXPECT_EQ(j.max_size(), 1u);
+        EXPECT_EQ(j_const.max_size(), 1u);
+    }
+}
+
+TEST(JsonMaxSizeTest, String)
+{
+    json j = "hello world";
+    json j_const(j);
+
+    // result of max_size
+    {
+        EXPECT_EQ(j.max_size(), 1u);
+        EXPECT_EQ(j_const.max_size(), 1u);
+    }
+}
+
+TEST(JsonMaxSizeTest, ArrayEmpty)
+{
+    json j = json::array();
+    json j_const(j);
+
+    // result of max_size
+    {
+        EXPECT_GE(j.max_size(), j.size());
+        EXPECT_GE(j_const.max_size(), j_const.size());
+    }
+}
+
+TEST(JsonMaxSizeTest, ArrayFilled)
+{
+    json j = {1, 2, 3};
+    json j_const(j);
+
+    // result of max_size
+    {
+        EXPECT_GE(j.max_size(), j.size());
+        EXPECT_GE(j_const.max_size(), j_const.size());
+    }
+}
+
+TEST(JsonMaxSizeTest, ObjectEmpty)
+{
+    json j = json::object();
+    json j_const(j);
+
+    // result of max_size
+    {
+        EXPECT_GE(j.max_size(), j.size());
+        EXPECT_GE(j_const.max_size(), j_const.size());
+    }
+}
+
+TEST(JsonMaxSizeTest, ObjectFilled)
+{
+    json j = {{"one", 1}, {"two", 2}, {"three", 3}};
+    json j_const(j);
+
+    // result of max_size
+    {
+        EXPECT_GE(j.max_size(), j.size());
+        EXPECT_GE(j_const.max_size(), j_const.size());
+    }
+}
+
+TEST(JsonMaxSizeTest, NumberInteger)
+{
+    json j = 23;
+    json j_const(j);
+
+    // result of max_size
+    {
+        EXPECT_EQ(j.max_size(), 1u);
+        EXPECT_EQ(j_const.max_size(), 1u);
+    }
+}
+
+TEST(JsonMaxSizeTest, NumberUnsigned)
+{
+    json j = 23u;
+    json j_const(j);
+
+    // result of max_size
+    {
+        EXPECT_EQ(j.max_size(), 1u);
+        EXPECT_EQ(j_const.max_size(), 1u);
+    }
+}
+
+TEST(JsonMaxSizeTest, NumberFloat)
+{
+    json j = 23.42;
+    json j_const(j);
+
+    // result of max_size
+    {
+        EXPECT_EQ(j.max_size(), 1u);
+        EXPECT_EQ(j_const.max_size(), 1u);
+    }
+}
+
+TEST(JsonMaxSizeTest, Null)
+{
+    json j = nullptr;
+    json j_const(j);
+
+    // result of max_size
+    {
+        EXPECT_EQ(j.max_size(), 0u);
+        EXPECT_EQ(j_const.max_size(), 0u);
+    }
+}
diff --git a/wpiutil/src/test/native/cpp/json/unit-cbor.cpp b/wpiutil/src/test/native/cpp/json/unit-cbor.cpp
new file mode 100644
index 0000000..2e37a17
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-cbor.cpp
@@ -0,0 +1,1701 @@
+/*----------------------------------------------------------------------------*/
+/* Modifications Copyright (c) FIRST 2017. All Rights Reserved.               */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include "unit-json.h"
+using wpi::json;
+
+#include <fstream>
+
+TEST(CborDiscardedTest, Case)
+{
+    // discarded values are not serialized
+    json j = json::value_t::discarded;
+    const auto result = json::to_cbor(j);
+    ASSERT_TRUE(result.empty());
+}
+
+TEST(CborNullTest, Case)
+{
+    json j = nullptr;
+    std::vector<uint8_t> expected = {0xf6};
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+TEST(CborBooleanTest, True)
+{
+    json j = true;
+    std::vector<uint8_t> expected = {0xf5};
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+TEST(CborBooleanTest, False)
+{
+    json j = false;
+    std::vector<uint8_t> expected = {0xf4};
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+// -9223372036854775808..-4294967297
+class CborSignedNeg8Test : public ::testing::TestWithParam<int64_t> {};
+TEST_P(CborSignedNeg8Test, Case)
+{
+    // create JSON value with integer number
+    json j = GetParam();
+
+    // check type
+    ASSERT_TRUE(j.is_number_integer());
+
+    // create expected byte vector
+    std::vector<uint8_t> expected;
+    expected.push_back(static_cast<uint8_t>(0x3b));
+    uint64_t positive = static_cast<uint64_t>(-1 - GetParam());
+    expected.push_back(static_cast<uint8_t>((positive >> 56) & 0xff));
+    expected.push_back(static_cast<uint8_t>((positive >> 48) & 0xff));
+    expected.push_back(static_cast<uint8_t>((positive >> 40) & 0xff));
+    expected.push_back(static_cast<uint8_t>((positive >> 32) & 0xff));
+    expected.push_back(static_cast<uint8_t>((positive >> 24) & 0xff));
+    expected.push_back(static_cast<uint8_t>((positive >> 16) & 0xff));
+    expected.push_back(static_cast<uint8_t>((positive >> 8) & 0xff));
+    expected.push_back(static_cast<uint8_t>(positive & 0xff));
+
+    // compare result + size
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+    ASSERT_EQ(result.size(), 9u);
+
+    // check individual bytes
+    EXPECT_EQ(result[0], 0x3b);
+    uint64_t restored = (static_cast<uint64_t>(static_cast<uint8_t>(result[1])) << 070) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[2])) << 060) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[3])) << 050) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[4])) << 040) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[5])) << 030) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[6])) << 020) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[7])) << 010) +
+                        static_cast<uint64_t>(static_cast<uint8_t>(result[8]));
+    EXPECT_EQ(restored, positive);
+    EXPECT_EQ(-1 - static_cast<int64_t>(restored), GetParam());
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+static const int64_t neg8_numbers[] = {
+    INT64_MIN,
+    -1000000000000000000,
+    -100000000000000000,
+    -10000000000000000,
+    -1000000000000000,
+    -100000000000000,
+    -10000000000000,
+    -1000000000000,
+    -100000000000,
+    -10000000000,
+    -4294967297,
+};
+
+INSTANTIATE_TEST_SUITE_P(CborSignedNeg8Tests, CborSignedNeg8Test,
+                        ::testing::ValuesIn(neg8_numbers));
+
+// -4294967296..-65537
+class CborSignedNeg4Test : public ::testing::TestWithParam<int64_t> {};
+TEST_P(CborSignedNeg4Test, Case)
+{
+    // create JSON value with integer number
+    json j = GetParam();
+
+    // check type
+    ASSERT_TRUE(j.is_number_integer());
+
+    // create expected byte vector
+    std::vector<uint8_t> expected;
+    expected.push_back(static_cast<uint8_t>(0x3a));
+    uint32_t positive = static_cast<uint32_t>(static_cast<uint64_t>(-1 - GetParam()) & 0x00000000ffffffff);
+    expected.push_back(static_cast<uint8_t>((positive >> 24) & 0xff));
+    expected.push_back(static_cast<uint8_t>((positive >> 16) & 0xff));
+    expected.push_back(static_cast<uint8_t>((positive >> 8) & 0xff));
+    expected.push_back(static_cast<uint8_t>(positive & 0xff));
+
+    // compare result + size
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+    ASSERT_EQ(result.size(), 5u);
+
+    // check individual bytes
+    EXPECT_EQ(result[0], 0x3a);
+    uint32_t restored = (static_cast<uint32_t>(static_cast<uint8_t>(result[1])) << 030) +
+                        (static_cast<uint32_t>(static_cast<uint8_t>(result[2])) << 020) +
+                        (static_cast<uint32_t>(static_cast<uint8_t>(result[3])) << 010) +
+                        static_cast<uint32_t>(static_cast<uint8_t>(result[4]));
+    EXPECT_EQ(restored, positive);
+    EXPECT_EQ(-1ll - restored, GetParam());
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+static const int64_t neg4_numbers[] = {
+    -65537,
+    -100000,
+    -1000000,
+    -10000000,
+    -100000000,
+    -1000000000,
+    -4294967296,
+};
+
+INSTANTIATE_TEST_SUITE_P(CborSignedNeg4Tests, CborSignedNeg4Test,
+                        ::testing::ValuesIn(neg4_numbers));
+
+// -65536..-257
+TEST(CborSignedTest, Neg2)
+{
+    for (int32_t i = -65536; i <= -257; ++i) {
+        SCOPED_TRACE(i);
+
+        // create JSON value with integer number
+        json j = i;
+
+        // check type
+        ASSERT_TRUE(j.is_number_integer());
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(static_cast<uint8_t>(0x39));
+        uint16_t positive = static_cast<uint16_t>(-1 - i);
+        expected.push_back(static_cast<uint8_t>((positive >> 8) & 0xff));
+        expected.push_back(static_cast<uint8_t>(positive & 0xff));
+
+        // compare result + size
+        const auto result = json::to_cbor(j);
+        EXPECT_EQ(result, expected);
+        ASSERT_EQ(result.size(), 3u);
+
+        // check individual bytes
+        EXPECT_EQ(result[0], 0x39);
+        uint16_t restored = static_cast<uint16_t>(static_cast<uint8_t>(result[1]) * 256 + static_cast<uint8_t>(result[2]));
+        EXPECT_EQ(restored, positive);
+        EXPECT_EQ(-1 - restored, i);
+
+        // roundtrip
+        EXPECT_EQ(json::from_cbor(result), j);
+    }
+}
+
+// -9263 (int 16)
+TEST(CborSignedTest, NegInt16)
+{
+    json j = -9263;
+    std::vector<uint8_t> expected = {0x39,0x24,0x2e};
+
+    const auto result = json::to_cbor(j);
+    ASSERT_EQ(result, expected);
+
+    int16_t restored = static_cast<int16_t>(-1 - ((result[1] << 8) + result[2]));
+    EXPECT_EQ(restored, -9263);
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+// -256..-24
+TEST(CborSignedTest, Neg1)
+{
+    for (auto i = -256; i < -24; ++i)
+    {
+        SCOPED_TRACE(i);
+
+        // create JSON value with integer number
+        json j = i;
+
+        // check type
+        ASSERT_TRUE(j.is_number_integer());
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(static_cast<uint8_t>(0x38));
+        expected.push_back(static_cast<uint8_t>(-1 - i));
+
+        // compare result + size
+        const auto result = json::to_cbor(j);
+        EXPECT_EQ(result, expected);
+        ASSERT_EQ(result.size(), 2u);
+
+        // check individual bytes
+        EXPECT_EQ(result[0], 0x38);
+        EXPECT_EQ(static_cast<int16_t>(-1 - static_cast<uint8_t>(result[1])), i);
+
+        // roundtrip
+        EXPECT_EQ(json::from_cbor(result), j);
+    }
+}
+
+// -24..-1
+TEST(CborSignedTest, Neg0)
+{
+    for (auto i = -24; i <= -1; ++i)
+    {
+        SCOPED_TRACE(i);
+
+        // create JSON value with integer number
+        json j = i;
+
+        // check type
+        ASSERT_TRUE(j.is_number_integer());
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(static_cast<uint8_t>(0x20 - 1 - static_cast<uint8_t>(i)));
+
+        // compare result + size
+        const auto result = json::to_cbor(j);
+        EXPECT_EQ(result, expected);
+        ASSERT_EQ(result.size(), 1u);
+
+        // check individual bytes
+        EXPECT_EQ(static_cast<int8_t>(0x20 - 1 - result[0]), i);
+
+        // roundtrip
+        EXPECT_EQ(json::from_cbor(result), j);
+    }
+}
+
+// 0..23
+TEST(CborSignedTest, Pos0)
+{
+    for (size_t i = 0; i <= 23; ++i)
+    {
+        SCOPED_TRACE(i);
+
+        // create JSON value with integer number
+        json j = -1;
+        j.get_ref<int64_t&>() = static_cast<int64_t>(i);
+
+        // check type
+        ASSERT_TRUE(j.is_number_integer());
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(static_cast<uint8_t>(i));
+
+        // compare result + size
+        const auto result = json::to_cbor(j);
+        EXPECT_EQ(result, expected);
+        ASSERT_EQ(result.size(), 1u);
+
+        // check individual bytes
+        EXPECT_EQ(result[0], static_cast<uint8_t>(i));
+
+        // roundtrip
+        EXPECT_EQ(json::from_cbor(result), j);
+    }
+}
+
+// 24..255
+TEST(CborSignedTest, Pos1)
+{
+    for (size_t i = 24; i <= 255; ++i)
+    {
+        SCOPED_TRACE(i);
+
+        // create JSON value with integer number
+        json j = -1;
+        j.get_ref<int64_t&>() = static_cast<int64_t>(i);
+
+        // check type
+        ASSERT_TRUE(j.is_number_integer());
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(static_cast<uint8_t>(0x18));
+        expected.push_back(static_cast<uint8_t>(i));
+
+        // compare result + size
+        const auto result = json::to_cbor(j);
+        EXPECT_EQ(result, expected);
+        ASSERT_EQ(result.size(), 2u);
+
+        // check individual bytes
+        EXPECT_EQ(result[0], 0x18);
+        EXPECT_EQ(result[1], static_cast<uint8_t>(i));
+
+        // roundtrip
+        EXPECT_EQ(json::from_cbor(result), j);
+    }
+}
+
+// 256..65535
+TEST(CborSignedTest, Pos2)
+{
+    for (size_t i = 256; i <= 65535; ++i)
+    {
+        SCOPED_TRACE(i);
+
+        // create JSON value with integer number
+        json j = -1;
+        j.get_ref<int64_t&>() = static_cast<int64_t>(i);
+
+        // check type
+        ASSERT_TRUE(j.is_number_integer());
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(static_cast<uint8_t>(0x19));
+        expected.push_back(static_cast<uint8_t>((i >> 8) & 0xff));
+        expected.push_back(static_cast<uint8_t>(i & 0xff));
+
+        // compare result + size
+        const auto result = json::to_cbor(j);
+        EXPECT_EQ(result, expected);
+        ASSERT_EQ(result.size(), 3u);
+
+        // check individual bytes
+        EXPECT_EQ(result[0], 0x19);
+        uint16_t restored = static_cast<uint16_t>(static_cast<uint8_t>(result[1]) * 256 + static_cast<uint8_t>(result[2]));
+        EXPECT_EQ(restored, i);
+
+        // roundtrip
+        EXPECT_EQ(json::from_cbor(result), j);
+    }
+}
+
+// 65536..4294967295
+class CborSignedPos4Test : public ::testing::TestWithParam<uint32_t> {};
+TEST_P(CborSignedPos4Test, Case)
+{
+    // create JSON value with integer number
+    json j = -1;
+    j.get_ref<int64_t&>() =
+        static_cast<int64_t>(GetParam());
+
+    // check type
+    ASSERT_TRUE(j.is_number_integer());
+
+    // create expected byte vector
+    std::vector<uint8_t> expected;
+    expected.push_back(0x1a);
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 24) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 16) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 8) & 0xff));
+    expected.push_back(static_cast<uint8_t>(GetParam() & 0xff));
+
+    // compare result + size
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+    ASSERT_EQ(result.size(), 5u);
+
+    // check individual bytes
+    EXPECT_EQ(result[0], 0x1a);
+    uint32_t restored = (static_cast<uint32_t>(static_cast<uint8_t>(result[1])) << 030) +
+                        (static_cast<uint32_t>(static_cast<uint8_t>(result[2])) << 020) +
+                        (static_cast<uint32_t>(static_cast<uint8_t>(result[3])) << 010) +
+                        static_cast<uint32_t>(static_cast<uint8_t>(result[4]));
+    EXPECT_EQ(restored, GetParam());
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+static const uint32_t pos4_numbers[] = {
+    65536u,
+    77777u,
+    1048576u,
+};
+
+INSTANTIATE_TEST_SUITE_P(CborSignedPos4Tests, CborSignedPos4Test,
+                        ::testing::ValuesIn(pos4_numbers));
+
+// 4294967296..4611686018427387903
+class CborSignedPos8Test : public ::testing::TestWithParam<uint64_t> {};
+TEST_P(CborSignedPos8Test, Case)
+{
+    // create JSON value with integer number
+    json j = -1;
+    j.get_ref<int64_t&>() =
+        static_cast<int64_t>(GetParam());
+
+    // check type
+    ASSERT_TRUE(j.is_number_integer());
+
+    // create expected byte vector
+    std::vector<uint8_t> expected;
+    expected.push_back(0x1b);
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 070) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 060) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 050) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 040) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 030) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 020) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 010) & 0xff));
+    expected.push_back(static_cast<uint8_t>(GetParam() & 0xff));
+
+    // compare result + size
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+    ASSERT_EQ(result.size(), 9u);
+
+    // check individual bytes
+    EXPECT_EQ(result[0], 0x1b);
+    uint64_t restored = (static_cast<uint64_t>(static_cast<uint8_t>(result[1])) << 070) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[2])) << 060) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[3])) << 050) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[4])) << 040) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[5])) << 030) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[6])) << 020) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[7])) << 010) +
+                        static_cast<uint64_t>(static_cast<uint8_t>(result[8]));
+    EXPECT_EQ(restored, GetParam());
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+static const uint64_t pos8_numbers[] = {
+    4294967296ul,
+    4611686018427387903ul
+};
+
+INSTANTIATE_TEST_SUITE_P(CborSignedPos8Tests, CborSignedPos8Test,
+                        ::testing::ValuesIn(pos8_numbers));
+
+/*
+// -32768..-129 (int 16)
+{
+    for (int16_t i = -32768; i <= -129; ++i)
+    {
+        SCOPED_TRACE(i);
+
+        // create JSON value with integer number
+        json j = i;
+
+        // check type
+        ASSERT_TRUE(j.is_number_integer());
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(0xd1);
+        expected.push_back(static_cast<uint8_t>((i >> 8) & 0xff));
+        expected.push_back(static_cast<uint8_t>(i & 0xff));
+
+        // compare result + size
+        const auto result = json::to_msgpack(j);
+        EXPECT_EQ(result == expected);
+        ASSERT_EQ(result.size(), 3u);
+
+        // check individual bytes
+        EXPECT_EQ(result[0], 0xd1);
+        int16_t restored = (result[1] << 8) + result[2];
+        EXPECT_EQ(restored, i);
+
+        // roundtrip
+        EXPECT_EQ(json::from_msgpack(result), j);
+    }
+}
+*/
+
+// 0..23 (Integer)
+TEST(CborUnsignedTest, Pos0)
+{
+    for (size_t i = 0; i <= 23; ++i)
+    {
+        SCOPED_TRACE(i);
+
+        // create JSON value with unsigned integer number
+        json j = i;
+
+        // check type
+        ASSERT_TRUE(j.is_number_unsigned());
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(static_cast<uint8_t>(i));
+
+        // compare result + size
+        const auto result = json::to_cbor(j);
+        EXPECT_EQ(result, expected);
+        ASSERT_EQ(result.size(), 1u);
+
+        // check individual bytes
+        EXPECT_EQ(result[0], static_cast<uint8_t>(i));
+
+        // roundtrip
+        EXPECT_EQ(json::from_cbor(result), j);
+    }
+}
+
+// 24..255 (one-byte uint8_t)
+TEST(CborUnsignedTest, Pos1)
+{
+    for (size_t i = 24; i <= 255; ++i)
+    {
+        SCOPED_TRACE(i);
+
+        // create JSON value with unsigned integer number
+        json j = i;
+
+        // check type
+        ASSERT_TRUE(j.is_number_unsigned());
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(0x18);
+        expected.push_back(static_cast<uint8_t>(i));
+
+        // compare result + size
+        const auto result = json::to_cbor(j);
+        EXPECT_EQ(result, expected);
+        ASSERT_EQ(result.size(), 2u);
+
+        // check individual bytes
+        EXPECT_EQ(result[0], 0x18);
+        uint8_t restored = static_cast<uint8_t>(result[1]);
+        EXPECT_EQ(restored, i);
+
+        // roundtrip
+        EXPECT_EQ(json::from_cbor(result), j);
+    }
+}
+
+// 256..65535 (two-byte uint16_t)
+TEST(CborUnsignedTest, Pos2)
+{
+    for (size_t i = 256; i <= 65535; ++i)
+    {
+        SCOPED_TRACE(i);
+
+        // create JSON value with unsigned integer number
+        json j = i;
+
+        // check type
+        ASSERT_TRUE(j.is_number_unsigned());
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(0x19);
+        expected.push_back(static_cast<uint8_t>((i >> 8) & 0xff));
+        expected.push_back(static_cast<uint8_t>(i & 0xff));
+
+        // compare result + size
+        const auto result = json::to_cbor(j);
+        EXPECT_EQ(result, expected);
+        ASSERT_EQ(result.size(), 3u);
+
+        // check individual bytes
+        EXPECT_EQ(result[0], 0x19);
+        uint16_t restored = static_cast<uint16_t>(static_cast<uint8_t>(result[1]) * 256 + static_cast<uint8_t>(result[2]));
+        EXPECT_EQ(restored, i);
+
+        // roundtrip
+        EXPECT_EQ(json::from_cbor(result), j);
+    }
+}
+
+// 65536..4294967295 (four-byte uint32_t)
+class CborUnsignedPos4Test : public ::testing::TestWithParam<uint32_t> {};
+TEST_P(CborUnsignedPos4Test, Case)
+{
+    // create JSON value with unsigned integer number
+    json j = GetParam();
+
+    // check type
+    ASSERT_TRUE(j.is_number_unsigned());
+
+    // create expected byte vector
+    std::vector<uint8_t> expected;
+    expected.push_back(0x1a);
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 24) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 16) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 8) & 0xff));
+    expected.push_back(static_cast<uint8_t>(GetParam() & 0xff));
+
+    // compare result + size
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+    ASSERT_EQ(result.size(), 5u);
+
+    // check individual bytes
+    EXPECT_EQ(result[0], 0x1a);
+    uint32_t restored = (static_cast<uint32_t>(static_cast<uint8_t>(result[1])) << 030) +
+                        (static_cast<uint32_t>(static_cast<uint8_t>(result[2])) << 020) +
+                        (static_cast<uint32_t>(static_cast<uint8_t>(result[3])) << 010) +
+                        static_cast<uint32_t>(static_cast<uint8_t>(result[4]));
+    EXPECT_EQ(restored, GetParam());
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+INSTANTIATE_TEST_SUITE_P(CborUnsignedPos4Tests, CborUnsignedPos4Test,
+                        ::testing::ValuesIn(pos4_numbers));
+
+// 4294967296..4611686018427387903 (eight-byte uint64_t)
+class CborUnsignedPos8Test : public ::testing::TestWithParam<uint64_t> {};
+TEST_P(CborUnsignedPos8Test, Case)
+{
+    // create JSON value with integer number
+    json j = GetParam();
+
+    // check type
+    ASSERT_TRUE(j.is_number_unsigned());
+
+    // create expected byte vector
+    std::vector<uint8_t> expected;
+    expected.push_back(0x1b);
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 070) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 060) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 050) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 040) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 030) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 020) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 010) & 0xff));
+    expected.push_back(static_cast<uint8_t>(GetParam() & 0xff));
+
+    // compare result + size
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+    ASSERT_EQ(result.size(), 9u);
+
+    // check individual bytes
+    EXPECT_EQ(result[0], 0x1b);
+    uint64_t restored = (static_cast<uint64_t>(static_cast<uint8_t>(result[1])) << 070) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[2])) << 060) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[3])) << 050) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[4])) << 040) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[5])) << 030) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[6])) << 020) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[7])) << 010) +
+                        static_cast<uint64_t>(static_cast<uint8_t>(result[8]));
+    EXPECT_EQ(restored, GetParam());
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+INSTANTIATE_TEST_SUITE_P(CborUnsignedPos8Tests, CborUnsignedPos8Test,
+                        ::testing::ValuesIn(pos8_numbers));
+
+// 3.1415925
+TEST(CborFloatTest, Number)
+{
+    double v = 3.1415925;
+    json j = v;
+    std::vector<uint8_t> expected = {0xfb,0x40,0x09,0x21,0xfb,0x3f,0xa6,0xde,0xfc};
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+    EXPECT_EQ(json::from_cbor(result), v);
+}
+
+TEST(CborFloatTest, HalfInfinity)
+{
+    json j = json::from_cbor(std::vector<uint8_t>({0xf9,0x7c,0x00}));
+    double d = j;
+    EXPECT_FALSE(std::isfinite(d));
+    EXPECT_EQ(j.dump(), "null");
+}
+
+TEST(CborFloatTest, HalfNaN)
+{
+    json j = json::from_cbor(std::vector<uint8_t>({0xf9,0x7c,0x01}));
+    double d = j;
+    EXPECT_TRUE(std::isnan(d));
+    EXPECT_EQ(j.dump(), "null");
+}
+
+// N = 0..23
+TEST(CborStringTest, String1)
+{
+    for (size_t N = 0; N <= 0x17; ++N)
+    {
+        SCOPED_TRACE(N);
+
+        // create JSON value with string containing of N * 'x'
+        const auto s = std::string(N, 'x');
+        json j = s;
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(static_cast<uint8_t>(0x60 + N));
+        for (size_t i = 0; i < N; ++i)
+        {
+            expected.push_back('x');
+        }
+
+        // compare result + size
+        const auto result = json::to_cbor(j);
+        EXPECT_EQ(result, expected);
+        ASSERT_EQ(result.size(), N + 1);
+        // check that no null byte is appended
+        if (N > 0)
+        {
+            EXPECT_NE(result.back(), '\x00');
+        }
+
+        // roundtrip
+        EXPECT_EQ(json::from_cbor(result), j);
+    }
+}
+
+// N = 24..255
+TEST(CborStringTest, String2)
+{
+    for (size_t N = 24; N <= 255; ++N)
+    {
+        SCOPED_TRACE(N);
+
+        // create JSON value with string containing of N * 'x'
+        const auto s = std::string(N, 'x');
+        json j = s;
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(static_cast<uint8_t>(0x78));
+        expected.push_back(static_cast<uint8_t>(N));
+        for (size_t i = 0; i < N; ++i)
+        {
+            expected.push_back('x');
+        }
+
+        // compare result + size
+        const auto result = json::to_cbor(j);
+        EXPECT_EQ(result, expected);
+        ASSERT_EQ(result.size(), N + 2);
+        // check that no null byte is appended
+        EXPECT_NE(result.back(), '\x00');
+
+        // roundtrip
+        EXPECT_EQ(json::from_cbor(result), j);
+    }
+}
+
+// N = 256..65535
+class CborString3Test : public ::testing::TestWithParam<size_t> {};
+TEST_P(CborString3Test, Case)
+{
+    // create JSON value with string containing of N * 'x'
+    const auto s = std::string(GetParam(), 'x');
+    json j = s;
+
+    // create expected byte vector
+    std::vector<uint8_t> expected;
+    expected.push_back(0x79);
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 8) & 0xff));
+    expected.push_back(static_cast<uint8_t>(GetParam() & 0xff));
+    for (size_t i = 0; i < GetParam(); ++i)
+    {
+        expected.push_back('x');
+    }
+
+    // compare result + size
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+    ASSERT_EQ(result.size(), GetParam() + 3);
+    // check that no null byte is appended
+    EXPECT_NE(result.back(), '\x00');
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+static size_t string3_lens[] = {
+    256u,
+    999u,
+    1025u,
+    3333u,
+    2048u,
+    65535u
+};
+
+INSTANTIATE_TEST_SUITE_P(CborString3Tests, CborString3Test,
+                        ::testing::ValuesIn(string3_lens));
+
+// N = 65536..4294967295
+class CborString5Test : public ::testing::TestWithParam<size_t> {};
+TEST_P(CborString5Test, Case)
+{
+    // create JSON value with string containing of N * 'x'
+    const auto s = std::string(GetParam(), 'x');
+    json j = s;
+
+    // create expected byte vector
+    std::vector<uint8_t> expected;
+    expected.push_back(0x7a);
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 24) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 16) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 8) & 0xff));
+    expected.push_back(static_cast<uint8_t>(GetParam() & 0xff));
+    for (size_t i = 0; i < GetParam(); ++i)
+    {
+        expected.push_back('x');
+    }
+
+    // compare result + size
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+    ASSERT_EQ(result.size(), GetParam() + 5);
+    // check that no null byte is appended
+    EXPECT_NE(result.back(), '\x00');
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+static size_t string5_lens[] = {
+    65536u,
+    77777u,
+    1048576u
+};
+
+INSTANTIATE_TEST_SUITE_P(CborString5Tests, CborString5Test,
+                        ::testing::ValuesIn(string5_lens));
+
+TEST(CborArrayTest, Empty)
+{
+    json j = json::array();
+    std::vector<uint8_t> expected = {0x80};
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+// [null]
+TEST(CborArrayTest, Null)
+{
+    json j = {nullptr};
+    std::vector<uint8_t> expected = {0x81,0xf6};
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+// [1,2,3,4,5]
+TEST(CborArrayTest, Simple)
+{
+    json j = json::parse("[1,2,3,4,5]");
+    std::vector<uint8_t> expected = {0x85,0x01,0x02,0x03,0x04,0x05};
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+// [[[[]]]]
+TEST(CborArrayTest, NestEmpty)
+{
+    json j = json::parse("[[[[]]]]");
+    std::vector<uint8_t> expected = {0x81,0x81,0x81,0x80};
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+// array with uint16_t elements
+TEST(CborArrayTest, UInt16)
+{
+    json j(257, nullptr);
+    std::vector<uint8_t> expected(j.size() + 3, 0xf6); // all null
+    expected[0] = static_cast<uint8_t>(0x99); // array 16 bit
+    expected[1] = 0x01; // size (0x0101), byte 0
+    expected[2] = 0x01; // size (0x0101), byte 1
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+// array with uint32_t elements
+TEST(CborArrayTest, UInt32)
+{
+    json j(65793, nullptr);
+    std::vector<uint8_t> expected(j.size() + 5, 0xf6); // all null
+    expected[0] = static_cast<uint8_t>(0x9a); // array 32 bit
+    expected[1] = 0x00; // size (0x00010101), byte 0
+    expected[2] = 0x01; // size (0x00010101), byte 1
+    expected[3] = 0x01; // size (0x00010101), byte 2
+    expected[4] = 0x01; // size (0x00010101), byte 3
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+/*
+// array with uint64_t elements
+TEST(CborArrayTest, UInt64)
+{
+    json j(4294967296, nullptr);
+    std::vector<uint8_t> expected(j.size() + 9, 0xf6); // all null
+    expected[0] = 0x9b; // array 64 bit
+    expected[1] = 0x00; // size (0x0000000100000000), byte 0
+    expected[2] = 0x00; // size (0x0000000100000000), byte 1
+    expected[3] = 0x00; // size (0x0000000100000000), byte 2
+    expected[4] = 0x01; // size (0x0000000100000000), byte 3
+    expected[5] = 0x00; // size (0x0000000100000000), byte 4
+    expected[6] = 0x00; // size (0x0000000100000000), byte 5
+    expected[7] = 0x00; // size (0x0000000100000000), byte 6
+    expected[8] = 0x00; // size (0x0000000100000000), byte 7
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+*/
+
+TEST(CborObjectTest, Empty)
+{
+    json j = json::object();
+    std::vector<uint8_t> expected = {0xa0};
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+// {"":null}
+TEST(CborObjectTest, EmptyKey)
+{
+    json j = {{"", nullptr}};
+    std::vector<uint8_t> expected = {0xa1,0x60,0xf6};
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+// {"a": {"b": {"c": {}}}}
+TEST(CborObjectTest, NestedEmpty)
+{
+    json j = json::parse("{\"a\": {\"b\": {\"c\": {}}}}");
+    std::vector<uint8_t> expected = {0xa1,0x61,0x61,0xa1,0x61,0x62,0xa1,0x61,0x63,0xa0};
+    const auto result = json::to_cbor(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+// object with uint8_t elements
+TEST(CborObjectTest, UInt8)
+{
+    json j;
+    for (auto i = 0; i < 255; ++i)
+    {
+        // format i to a fixed width of 5
+        // each entry will need 7 bytes: 6 for string, 1 for null
+        std::stringstream ss;
+        ss << std::setw(5) << std::setfill('0') << i;
+        j.emplace(ss.str(), nullptr);
+    }
+
+    const auto result = json::to_cbor(j);
+
+    // Checking against an expected vector byte by byte is
+    // difficult, because no assumption on the order of key/value
+    // pairs are made. We therefore only check the prefix (type and
+    // size and the overall size. The rest is then handled in the
+    // roundtrip check.
+    ASSERT_EQ(result.size(), 1787u); // 1 type, 1 size, 255*7 content
+    EXPECT_EQ(result[0], static_cast<uint8_t>(0xb8)); // map 8 bit
+    EXPECT_EQ(result[1], static_cast<uint8_t>(0xff)); // size byte (0xff)
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+// object with uint16_t elements
+TEST(CborObjectTest, UInt16)
+{
+    json j;
+    for (auto i = 0; i < 256; ++i)
+    {
+        // format i to a fixed width of 5
+        // each entry will need 7 bytes: 6 for string, 1 for null
+        std::stringstream ss;
+        ss << std::setw(5) << std::setfill('0') << i;
+        j.emplace(ss.str(), nullptr);
+    }
+
+    const auto result = json::to_cbor(j);
+
+    // Checking against an expected vector byte by byte is
+    // difficult, because no assumption on the order of key/value
+    // pairs are made. We therefore only check the prefix (type and
+    // size and the overall size. The rest is then handled in the
+    // roundtrip check.
+    ASSERT_EQ(result.size(), 1795u); // 1 type, 2 size, 256*7 content
+    EXPECT_EQ(result[0], static_cast<uint8_t>(0xb9)); // map 16 bit
+    EXPECT_EQ(result[1], 0x01); // byte 0 of size (0x0100)
+    EXPECT_EQ(result[2], 0x00); // byte 1 of size (0x0100)
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+// object with uint32_t elements
+TEST(CborObjectTest, UInt32)
+{
+    json j;
+    for (auto i = 0; i < 65536; ++i)
+    {
+        // format i to a fixed width of 5
+        // each entry will need 7 bytes: 6 for string, 1 for null
+        std::stringstream ss;
+        ss << std::setw(5) << std::setfill('0') << i;
+        j.emplace(ss.str(), nullptr);
+    }
+
+    const auto result = json::to_cbor(j);
+
+    // Checking against an expected vector byte by byte is
+    // difficult, because no assumption on the order of key/value
+    // pairs are made. We therefore only check the prefix (type and
+    // size and the overall size. The rest is then handled in the
+    // roundtrip check.
+    ASSERT_EQ(result.size(), 458757u); // 1 type, 4 size, 65536*7 content
+    EXPECT_EQ(result[0], static_cast<uint8_t>(0xba)); // map 32 bit
+    EXPECT_EQ(result[1], 0x00); // byte 0 of size (0x00010000)
+    EXPECT_EQ(result[2], 0x01); // byte 1 of size (0x00010000)
+    EXPECT_EQ(result[3], 0x00); // byte 2 of size (0x00010000)
+    EXPECT_EQ(result[4], 0x00); // byte 3 of size (0x00010000)
+
+    // roundtrip
+    EXPECT_EQ(json::from_cbor(result), j);
+}
+
+// 0x7b (string)
+TEST(CborAdditionalDeserializationTest, Case7b)
+{
+    std::vector<uint8_t> given{0x7b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x61};
+    json j = json::from_cbor(given);
+    EXPECT_EQ(j, "a");
+}
+
+// 0x9b (array)
+TEST(CborAdditionalDeserializationTest, Case9b)
+{
+    std::vector<uint8_t> given{0x9b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xf4};
+    json j = json::from_cbor(given);
+    EXPECT_EQ(j, json::parse("[false]"));
+}
+
+// 0xbb (map)
+TEST(CborAdditionalDeserializationTest, Casebb)
+{
+    std::vector<uint8_t> given{0xbb,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x60,0xf4};
+    json j = json::from_cbor(given);
+    EXPECT_EQ(j, json::parse("{\"\": false}"));
+}
+
+TEST(CborErrorTest, TooShortByteVector)
+{
+    EXPECT_THROW_MSG(json::from_cbor(std::vector<uint8_t>({0x18})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 2: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_cbor(std::vector<uint8_t>({0x19})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 2: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_cbor(std::vector<uint8_t>({0x19,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 3: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_cbor(std::vector<uint8_t>({0x1a})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 2: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_cbor(std::vector<uint8_t>({0x1a,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 3: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_cbor(std::vector<uint8_t>({0x1a,0x00,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 4: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_cbor(std::vector<uint8_t>({0x1a,0x00,0x00,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 5: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_cbor(std::vector<uint8_t>({0x1b})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 2: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_cbor(std::vector<uint8_t>({0x1b,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 3: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_cbor(std::vector<uint8_t>({0x1b,0x00,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 4: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_cbor(std::vector<uint8_t>({0x1b,0x00,0x00,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 5: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_cbor(std::vector<uint8_t>({0x1b,0x00,0x00,0x00,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 6: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_cbor(std::vector<uint8_t>({0x1b,0x00,0x00,0x00,0x00,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 7: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_cbor(std::vector<uint8_t>({0x1b,0x00,0x00,0x00,0x00,0x00,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 8: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_cbor(std::vector<uint8_t>({0x1b,0x00,0x00,0x00,0x00,0x00,0x00,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 9: unexpected end of input");
+}
+
+TEST(CborErrorTest, UnsupportedBytesConcrete)
+{
+    EXPECT_THROW_MSG(json::from_cbor(std::vector<uint8_t>({0x1c})), json::parse_error,
+                     "[json.exception.parse_error.112] parse error at 1: error reading CBOR; last byte: 0x1c");
+    EXPECT_THROW_MSG(json::from_cbor(std::vector<uint8_t>({0xf8})), json::parse_error,
+                     "[json.exception.parse_error.112] parse error at 1: error reading CBOR; last byte: 0xf8");
+}
+
+class CborUnsupportedBytesTest : public ::testing::TestWithParam<uint8_t> {
+};
+TEST_P(CborUnsupportedBytesTest, Case)
+{
+    EXPECT_THROW(json::from_cbor(std::vector<uint8_t>({GetParam()})), json::parse_error);
+}
+
+static const uint8_t unsupported_bytes_cases[] = {
+    // ?
+    0x1c, 0x1d, 0x1e, 0x1f,
+    // ?
+    0x3c, 0x3d, 0x3e, 0x3f,
+    // byte strings
+    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+    // byte strings
+    0x58, 0x59, 0x5a, 0x5b,
+    // ?
+    0x5c, 0x5d, 0x5e,
+    // byte string
+    0x5f,
+    // ?
+    0x7c, 0x7d, 0x7e,
+    // ?
+    0x9c, 0x9d, 0x9e,
+    // ?
+    0xbc, 0xbd, 0xbe,
+    // date/time
+    0xc0, 0xc1,
+    // bignum
+    0xc2, 0xc3,
+    // fraction
+    0xc4,
+    // bigfloat
+    0xc5,
+    // tagged item
+    0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4,
+    // expected conversion
+    0xd5, 0xd6, 0xd7,
+    // more tagged items
+    0xd8, 0xd9, 0xda, 0xdb,
+    // ?
+    0xdc, 0xdd, 0xde, 0xdf,
+    // (simple value)
+    0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3,
+    // undefined
+    0xf7,
+    // simple value
+    0xf8,
+};
+
+INSTANTIATE_TEST_SUITE_P(CborUnsupportedBytesTests, CborUnsupportedBytesTest,
+                        ::testing::ValuesIn(unsupported_bytes_cases));
+#if 0
+// use this testcase outside [hide] to run it with Valgrind
+TEST(CborRoundtripTest, Sample)
+{
+    std::string filename = "test/data/json_testsuite/sample.json";
+
+    // parse JSON file
+    std::ifstream f_json(filename);
+    json j1 = json::parse(f_json);
+
+    // parse CBOR file
+    std::ifstream f_cbor(filename + ".cbor", std::ios::binary);
+    std::vector<uint8_t> packed((std::istreambuf_iterator<char>(f_cbor)),
+                                std::istreambuf_iterator<char>());
+    json j2;
+    j2 = json::from_cbor(packed);
+
+    // compare parsed JSON values
+    EXPECT_EQ(j1, j2);
+
+    // check with different start index
+    packed.insert(packed.begin(), 5, 0xff);
+    EXPECT_EQ(j1, json::from_cbor(packed, 5));
+}
+
+/*
+The following test cases were found during a two-day session with
+AFL-Fuzz. As a result, empty byte vectors and excessive lengths are
+detected.
+*/
+class CborRegressionFuzzTest : public ::testing::TestWithParam<const char*> {};
+TEST_P(CborRegressionFuzzTest, Case)
+{
+    try
+    {
+        // parse CBOR file
+        std::ifstream f_cbor(GetParam(), std::ios::binary);
+        std::vector<uint8_t> vec1(
+            (std::istreambuf_iterator<char>(f_cbor)),
+            std::istreambuf_iterator<char>());
+        json j1 = json::from_cbor(vec1);
+
+        try
+        {
+            // step 2: round trip
+            std::string vec2 = json::to_cbor(j1);
+
+            // parse serialization
+            json j2 = json::from_cbor(vec2);
+
+            // deserializations must match
+            EXPECT_EQ(j1, j2);
+        }
+        catch (const json::parse_error&)
+        {
+            // parsing a CBOR serialization must not fail
+            FAIL();
+        }
+    }
+    catch (const json::parse_error&)
+    {
+        // parse errors are ok, because input may be random bytes
+    }
+}
+
+static const char* fuzz_test_cases[] = {
+    "test/data/cbor_regression/test01",
+    "test/data/cbor_regression/test02",
+    "test/data/cbor_regression/test03",
+    "test/data/cbor_regression/test04",
+    "test/data/cbor_regression/test05",
+    "test/data/cbor_regression/test06",
+    "test/data/cbor_regression/test07",
+    "test/data/cbor_regression/test08",
+    "test/data/cbor_regression/test09",
+    "test/data/cbor_regression/test10",
+    "test/data/cbor_regression/test11",
+    "test/data/cbor_regression/test12",
+    "test/data/cbor_regression/test13",
+    "test/data/cbor_regression/test14",
+    "test/data/cbor_regression/test15",
+    "test/data/cbor_regression/test16",
+    "test/data/cbor_regression/test17",
+    "test/data/cbor_regression/test18",
+    "test/data/cbor_regression/test19",
+    "test/data/cbor_regression/test20",
+    "test/data/cbor_regression/test21",
+};
+
+INSTANTIATE_TEST_SUITE_P(CborRegressionFuzzTests, CborRegressionFuzzTest,
+                        ::testing::ValuesIn(fuzz_test_cases));
+
+class CborRegressionFlynnTest : public ::testing::TestWithParam<const char*> {};
+TEST_F(CborRegressionFlynnTest, Case)
+{
+    // parse JSON file
+    std::ifstream f_json(GetParam());
+    json j1 = json::parse(f_json);
+
+    // parse CBOR file
+    std::ifstream f_cbor(filename + ".cbor", std::ios::binary);
+    std::vector<uint8_t> packed(
+        (std::istreambuf_iterator<char>(f_cbor)),
+        std::istreambuf_iterator<char>());
+    json j2;
+    j2 = json::from_cbor(packed);
+
+    // compare parsed JSON values
+    EXPECT_EQ(j1, j2);
+}
+
+static const char* flynn_test_cases[] = {
+    "test/data/json_nlohmann_tests/all_unicode.json",
+    "test/data/json.org/1.json",
+    "test/data/json.org/2.json",
+    "test/data/json.org/3.json",
+    "test/data/json.org/4.json",
+    "test/data/json.org/5.json",
+    "test/data/json_roundtrip/roundtrip01.json",
+    "test/data/json_roundtrip/roundtrip02.json",
+    "test/data/json_roundtrip/roundtrip03.json",
+    "test/data/json_roundtrip/roundtrip04.json",
+    "test/data/json_roundtrip/roundtrip05.json",
+    "test/data/json_roundtrip/roundtrip06.json",
+    "test/data/json_roundtrip/roundtrip07.json",
+    "test/data/json_roundtrip/roundtrip08.json",
+    "test/data/json_roundtrip/roundtrip09.json",
+    "test/data/json_roundtrip/roundtrip10.json",
+    "test/data/json_roundtrip/roundtrip11.json",
+    "test/data/json_roundtrip/roundtrip12.json",
+    "test/data/json_roundtrip/roundtrip13.json",
+    "test/data/json_roundtrip/roundtrip14.json",
+    "test/data/json_roundtrip/roundtrip15.json",
+    "test/data/json_roundtrip/roundtrip16.json",
+    "test/data/json_roundtrip/roundtrip17.json",
+    "test/data/json_roundtrip/roundtrip18.json",
+    "test/data/json_roundtrip/roundtrip19.json",
+    "test/data/json_roundtrip/roundtrip20.json",
+    "test/data/json_roundtrip/roundtrip21.json",
+    "test/data/json_roundtrip/roundtrip22.json",
+    "test/data/json_roundtrip/roundtrip23.json",
+    "test/data/json_roundtrip/roundtrip24.json",
+    "test/data/json_roundtrip/roundtrip25.json",
+    "test/data/json_roundtrip/roundtrip26.json",
+    "test/data/json_roundtrip/roundtrip27.json",
+    "test/data/json_roundtrip/roundtrip28.json",
+    "test/data/json_roundtrip/roundtrip29.json",
+    "test/data/json_roundtrip/roundtrip30.json",
+    "test/data/json_roundtrip/roundtrip31.json",
+    "test/data/json_roundtrip/roundtrip32.json",
+    "test/data/json_testsuite/sample.json", // kills AppVeyor
+    "test/data/json_tests/pass1.json",
+    "test/data/json_tests/pass2.json",
+    "test/data/json_tests/pass3.json",
+    "test/data/regression/floats.json",
+    "test/data/regression/signed_ints.json",
+    "test/data/regression/unsigned_ints.json",
+    "test/data/regression/working_file.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_arraysWithSpaces.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_empty-string.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_empty.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_ending_with_newline.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_false.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_heterogeneous.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_null.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_with_1_and_newline.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_with_leading_space.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_with_several_null.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_with_trailing_space.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_0e+1.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_0e1.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_after_space.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_double_close_to_zero.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_double_huge_neg_exp.json",
+    //"test/data/nst_json_testsuite/test_parsing/y_number_huge_exp.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_int_with_exp.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_minus_zero.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_negative_int.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_negative_one.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_negative_zero.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_real_capital_e.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_real_capital_e_neg_exp.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_real_capital_e_pos_exp.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_real_exponent.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_real_fraction_exponent.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_real_neg_exp.json",
+    //"test/data/nst_json_testsuite/test_parsing/y_number_real_neg_overflow.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_real_pos_exponent.json",
+    //"test/data/nst_json_testsuite/test_parsing/y_number_real_pos_overflow.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_real_underflow.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_simple_int.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_simple_real.json",
+    //"test/data/nst_json_testsuite/test_parsing/y_number_too_big_neg_int.json",
+    //"test/data/nst_json_testsuite/test_parsing/y_number_too_big_pos_int.json",
+    //"test/data/nst_json_testsuite/test_parsing/y_number_very_big_negative_int.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_basic.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_duplicated_key.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_duplicated_key_and_value.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_empty.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_empty_key.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_escaped_null_in_key.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_extreme_numbers.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_long_strings.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_simple.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_string_unicode.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_with_newlines.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_UTF-16_Surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pair.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pairs.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_allowed_escapes.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_backslash_and_u_escaped_zero.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_backslash_doublequotes.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_comments.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_double_escape_a.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_double_escape_n.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_escaped_control_character.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_escaped_noncharacter.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_in_array.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_in_array_with_leading_space.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_last_surrogates_1_and_2.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_newline_uescaped.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+1FFFF.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_null_escape.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_one-byte-utf-8.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_pi.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_simple_ascii.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_space.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_three-byte-utf-8.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_two-byte-utf-8.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_u+2028_line_sep.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_u+2029_par_sep.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_uEscape.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_unescaped_char_delete.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_unicode.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_unicodeEscapedBackslash.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_unicode_2.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_unicode_U+2064_invisible_plus.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_unicode_escaped_double_quote.json",
+    // "test/data/nst_json_testsuite/test_parsing/y_string_utf16.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_utf8.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_with_del_character.json",
+    "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_false.json",
+    "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_int.json",
+    "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_negative_real.json",
+    "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_null.json",
+    "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_string.json",
+    "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_true.json",
+    "test/data/nst_json_testsuite/test_parsing/y_structure_string_empty.json",
+    "test/data/nst_json_testsuite/test_parsing/y_structure_trailing_newline.json",
+    "test/data/nst_json_testsuite/test_parsing/y_structure_true_in_array.json",
+    "test/data/nst_json_testsuite/test_parsing/y_structure_whitespace_array.json",
+};
+
+INSTANTIATE_TEST_SUITE_P(CborRegressionFlynnTests, CborRegressionFlynnTest,
+                        ::testing::ValuesIn(flynn_test_cases));
+
+#endif
+TEST(CborFirstBytesTest, Unsupported)
+{
+    // these bytes will fail immediately with exception parse_error.112
+    std::set<uint8_t> unsupported =
+    {
+        //// types not supported by this library
+
+        // byte strings
+        0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+        0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+        0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+        // byte strings
+        0x58, 0x59, 0x5a, 0x5b, 0x5f,
+        // date/time
+        0xc0, 0xc1,
+        // bignum
+        0xc2, 0xc3,
+        // decimal fracion
+        0xc4,
+        // bigfloat
+        0xc5,
+        // tagged item
+        0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd,
+        0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd8,
+        0xd9, 0xda, 0xdb,
+        // expected conversion
+        0xd5, 0xd6, 0xd7,
+        // simple value
+        0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
+        0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xef, 0xf0,
+        0xf1, 0xf2, 0xf3,
+        0xf8,
+        // undefined
+        0xf7,
+
+        //// bytes not specified by CBOR
+
+        0x1c, 0x1d, 0x1e, 0x1f,
+        0x3c, 0x3d, 0x3e, 0x3f,
+        0x5c, 0x5d, 0x5e,
+        0x7c, 0x7d, 0x7e,
+        0x9c, 0x9d, 0x9e,
+        0xbc, 0xbd, 0xbe,
+        0xdc, 0xdd, 0xde, 0xdf,
+        0xee,
+        0xfc, 0xfe, 0xfd,
+
+        /// break cannot be the first byte
+
+        0xff
+    };
+
+    for (auto i = 0; i < 256; ++i)
+    {
+        const auto byte = static_cast<uint8_t>(i);
+
+        try
+        {
+            json::from_cbor(std::vector<uint8_t>(1, byte));
+        }
+        catch (const json::parse_error& e)
+        {
+            // check that parse_error.112 is only thrown if the
+            // first byte is in the unsupported set
+            SCOPED_TRACE(e.what());
+            if (std::find(unsupported.begin(), unsupported.end(),
+                          static_cast<uint8_t>(byte)) != unsupported.end())
+            {
+                EXPECT_EQ(e.id, 112);
+            }
+            else
+            {
+                EXPECT_NE(e.id, 112);
+            }
+        }
+    }
+}
+
+// examples from RFC 7049 Appendix A
+namespace internal {
+struct CborRoundtripTestParam {
+  const char* plain;
+  std::vector<uint8_t> encoded;
+  bool test_encode;
+};
+}  // namespace internal
+
+class CborRoundtripTest
+    : public ::testing::TestWithParam<internal::CborRoundtripTestParam> {
+};
+TEST_P(CborRoundtripTest, Case)
+{
+    if (GetParam().test_encode)
+    {
+        EXPECT_EQ(json::to_cbor(json::parse(GetParam().plain)), GetParam().encoded);
+    }
+    EXPECT_EQ(json::parse(GetParam().plain), json::from_cbor(GetParam().encoded));
+}
+
+static const internal::CborRoundtripTestParam rfc7049_appendix_a_numbers[] = {
+    {"0", {0x00}, true},
+    {"1", {0x01}, true},
+    {"10", {0x0a}, true},
+    {"23", {0x17}, true},
+    {"24", {0x18,0x18}, true},
+    {"25", {0x18,0x19}, true},
+    {"100", {0x18,0x64}, true},
+    {"1000", {0x19,0x03,0xe8}, true},
+    {"1000000", {0x1a,0x00,0x0f,0x42,0x40}, true},
+    {"1000000000000", {0x1b,0x00,0x00,0x00,0xe8,0xd4,0xa5,0x10,0x00}, true},
+    {"18446744073709551615", {0x1b,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff}, true},
+    // positive bignum is not supported
+    //{"18446744073709551616", {0xc2,0x49,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00", 11), true},
+    //{"-18446744073709551616", {0x3b,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff}, true},
+    // negative bignum is not supported
+    //{"-18446744073709551617", {0xc3,0x49,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, true},
+    {"-1", {0x20}, true},
+    {"-10", {0x29}, true},
+    {"-100", {0x38,0x63}, true},
+    {"-1000", {0x39,0x03,0xe7}, true},
+    // half-precision float
+    {"0.0", {0xf9,0x00,0x00}, false},
+    // half-precision float
+    {"-0.0", {0xf9,0x80,0x00}, false},
+    // half-precision float
+    {"1.0", {0xf9,0x3c,0x00}, false},
+    {"1.1", {0xfb,0x3f,0xf1,0x99,0x99,0x99,0x99,0x99,0x9a}, true},
+    // half-precision float
+    {"1.5", {0xf9,0x3e,0x00}, false},
+    // half-precision float
+    {"65504.0", {0xf9,0x7b,0xff}, false},
+    {"100000.0", {0xfa,0x47,0xc3,0x50,0x00}, false},
+    {"3.4028234663852886e+38", {0xfa,0x7f,0x7f,0xff,0xff}, false},
+    {"1.0e+300", {0xfb,0x7e,0x37,0xe4,0x3c,0x88,0x00,0x75,0x9c}, true},
+    // half-precision float
+    {"5.960464477539063e-8", {0xf9,0x00,0x01}, false},
+    // half-precision float
+    {"0.00006103515625", {0xf9,0x04,0x00}, false},
+    // half-precision float
+    {"-4.0", {0xf9,0xc4,0x00}, false},
+    {"-4.1", {0xfb,0xc0,0x10,0x66,0x66,0x66,0x66,0x66,0x66}, true},
+};
+
+INSTANTIATE_TEST_SUITE_P(CborRfc7049AppendixANumberTests, CborRoundtripTest,
+                        ::testing::ValuesIn(rfc7049_appendix_a_numbers));
+
+static const internal::CborRoundtripTestParam rfc7049_appendix_a_simple_values[] = {
+    {"false", {0xf4}, true},
+    {"true", {0xf5}, true},
+};
+
+INSTANTIATE_TEST_SUITE_P(CborRfc7049AppendixASimpleValueTests, CborRoundtripTest,
+                        ::testing::ValuesIn(rfc7049_appendix_a_simple_values));
+
+static const internal::CborRoundtripTestParam rfc7049_appendix_a_strings[] = {
+    {"\"\"", {0x60}, true},
+    {"\"a\"", {0x61,0x61}, true},
+    {"\"IETF\"", {0x64,0x49,0x45,0x54,0x46}, true},
+    {"\"\\u00fc\"", {0x62,0xc3,0xbc}, true},
+    {"\"\\u6c34\"", {0x63,0xe6,0xb0,0xb4}, true},
+    {"\"\\ud800\\udd51\"", {0x64,0xf0,0x90,0x85,0x91}, true},
+    // indefinite length strings
+    {"\"streaming\"", {0x7f,0x65,0x73,0x74,0x72,0x65,0x61,0x64,0x6d,0x69,0x6e,0x67,0xff}, false},
+};
+
+INSTANTIATE_TEST_SUITE_P(CborRfc7049AppendixAStringTests, CborRoundtripTest,
+                        ::testing::ValuesIn(rfc7049_appendix_a_strings));
+
+static const internal::CborRoundtripTestParam rfc7049_appendix_a_arrays[] = {
+    {"[]", {0x80}, true},
+    {"[1, 2, 3]", {0x83,0x01,0x02,0x03}, true},
+    {"[1, [2, 3], [4, 5]]", {0x83,0x01,0x82,0x02,0x03,0x82,0x04,0x05}, true},
+    {"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]", {0x98,0x19,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x18,0x18,0x19}, true},
+    // indefinite length arrays
+    {"[]", {0x9f,0xff}, false},
+    {"[1, [2, 3], [4, 5]] ", {0x9f,0x01,0x82,0x02,0x03,0x9f,0x04,0x05,0xff,0xff}, false},
+    {"[1, [2, 3], [4, 5]]", {0x9f,0x01,0x82,0x02,0x03,0x82,0x04,0x05,0xff}, false},
+    {"[1, [2, 3], [4, 5]]", {0x83,0x01,0x82,0x02,0x03,0x9f,0x04,0x05,0xff}, false},
+    {"[1, [2, 3], [4, 5]]", {0x83,0x01,0x9f,0x02,0x03,0xff,0x82,0x04,0x05}, false},
+    {"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]", {0x9f,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x18,0x18,0x19,0xff}, false},
+};
+
+INSTANTIATE_TEST_SUITE_P(CborRfc7049AppendixAArrayTests, CborRoundtripTest,
+                        ::testing::ValuesIn(rfc7049_appendix_a_arrays));
+
+static const internal::CborRoundtripTestParam rfc7049_appendix_a_objects[] = {
+    {"{}", {0xa0}, true},
+    {"{\"a\": 1, \"b\": [2, 3]}", {0xa2,0x61,0x61,0x01,0x61,0x62,0x82,0x02,0x03}, true},
+    {"[\"a\", {\"b\": \"c\"}]", {0x82,0x61,0x61,0xa1,0x61,0x62,0x61,0x63}, true},
+    {"{\"a\": \"A\", \"b\": \"B\", \"c\": \"C\", \"d\": \"D\", \"e\": \"E\"}", {0xa5,0x61,0x61,0x61,0x41,0x61,0x62,0x61,0x42,0x61,0x63,0x61,0x43,0x61,0x64,0x61,0x44,0x61,0x65,0x61,0x45}, true},
+    // indefinite length objects
+    {"{\"a\": 1, \"b\": [2, 3]}", {0xbf,0x61,0x61,0x01,0x61,0x62,0x9f,0x02,0x03,0xff,0xff}, false},
+    {"[\"a\", {\"b\": \"c\"}]", {0x82,0x61,0x61,0xbf,0x61,0x62,0x61,0x63,0xff}, false},
+    {"{\"Fun\": true, \"Amt\": -2}", {0xbf,0x63,0x46,0x75,0x6e,0xf5,0x63,0x41,0x6d,0x74,0x21,0xff}, false},
+};
+
+INSTANTIATE_TEST_SUITE_P(CborRfc7049AppendixAObjectTests, CborRoundtripTest,
+                        ::testing::ValuesIn(rfc7049_appendix_a_objects));
diff --git a/wpiutil/src/test/native/cpp/json/unit-comparison.cpp b/wpiutil/src/test/native/cpp/json/unit-comparison.cpp
new file mode 100644
index 0000000..18934a2
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-comparison.cpp
@@ -0,0 +1,250 @@
+/*----------------------------------------------------------------------------*/
+/* Modifications Copyright (c) FIRST 2017. All Rights Reserved.               */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include "unit-json.h"
+using wpi::json;
+
+class JsonComparisonTypesTest : public ::testing::Test {
+ protected:
+    std::vector<json::value_t> j_types =
+    {
+        json::value_t::null,
+        json::value_t::boolean,
+        json::value_t::number_integer,
+        json::value_t::number_unsigned,
+        json::value_t::number_float,
+        json::value_t::object,
+        json::value_t::array,
+        json::value_t::string
+    };
+};
+
+TEST_F(JsonComparisonTypesTest, Less)
+{
+    static const std::vector<std::vector<bool>> expected =
+    {
+        {false, true, true, true, true, true, true, true},
+        {false, false, true, true, true, true, true, true},
+        {false, false, false, false, false, true, true, true},
+        {false, false, false, false, false, true, true, true},
+        {false, false, false, false, false, true, true, true},
+        {false, false, false, false, false, false, true, true},
+        {false, false, false, false, false, false, false, true},
+        {false, false, false, false, false, false, false, false}
+    };
+
+    for (size_t i = 0; i < j_types.size(); ++i)
+    {
+        SCOPED_TRACE(i);
+        for (size_t j = 0; j < j_types.size(); ++j)
+        {
+            SCOPED_TRACE(j);
+            // check precomputed values
+            EXPECT_EQ(operator<(j_types[i], j_types[j]), expected[i][j]);
+        }
+    }
+}
+
+class JsonComparisonValuesTest : public ::testing::Test {
+ protected:
+    json j_values =
+    {
+        nullptr, nullptr,
+        17, 42,
+        8u, 13u,
+        3.14159, 23.42,
+        "foo", "bar",
+        true, false,
+        {1, 2, 3}, {"one", "two", "three"},
+        {{"first", 1}, {"second", 2}}, {{"a", "A"}, {"b", {"B"}}}
+    };
+};
+
+TEST_F(JsonComparisonValuesTest, Equal)
+{
+    static const std::vector<std::vector<bool>> expected =
+    {
+        {true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false},
+        {true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false},
+        {false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false},
+        {false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false},
+        {false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false},
+        {false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false},
+        {false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false},
+        {false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false},
+        {false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false},
+        {false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false},
+        {false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false},
+        {false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false},
+        {false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false},
+        {false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false},
+        {false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false},
+        {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true}
+    };
+
+    for (size_t i = 0; i < j_values.size(); ++i)
+    {
+        SCOPED_TRACE(i);
+        for (size_t j = 0; j < j_values.size(); ++j)
+        {
+            SCOPED_TRACE(j);
+            // check precomputed values
+            EXPECT_EQ(j_values[i] == j_values[j], expected[i][j]);
+        }
+    }
+
+    // comparison with discarded elements
+    json j_discarded(json::value_t::discarded);
+    for (size_t i = 0; i < j_values.size(); ++i)
+    {
+        SCOPED_TRACE(i);
+        EXPECT_FALSE(j_values[i] == j_discarded);
+        EXPECT_FALSE(j_discarded == j_values[i]);
+        EXPECT_FALSE(j_discarded == j_discarded);
+    }
+
+    // compare with null pointer
+    json j_null;
+    EXPECT_TRUE(j_null == nullptr);
+    EXPECT_TRUE(nullptr == j_null);
+}
+
+TEST_F(JsonComparisonValuesTest, NotEqual)
+{
+    for (size_t i = 0; i < j_values.size(); ++i)
+    {
+        SCOPED_TRACE(i);
+        for (size_t j = 0; j < j_values.size(); ++j)
+        {
+            SCOPED_TRACE(j);
+            // check definition
+            EXPECT_EQ(j_values[i] != j_values[j], !(j_values[i] == j_values[j]));
+        }
+    }
+
+    // compare with null pointer
+    json j_null;
+    EXPECT_FALSE(j_null != nullptr);
+    EXPECT_FALSE(nullptr != j_null);
+    EXPECT_EQ(j_null != nullptr, !(j_null == nullptr));
+    EXPECT_EQ(nullptr != j_null, !(nullptr == j_null));
+}
+
+TEST_F(JsonComparisonValuesTest, Less)
+{
+    static const std::vector<std::vector<bool>> expected =
+    {
+        {false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true},
+        {false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true},
+        {false, false, false, true, false, false, false, true, true, true, false, false, true, true, true, true},
+        {false, false, false, false, false, false, false, false, true, true, false, false, true, true, true, true},
+        {false, false, true, true, false, true, false, true, true, true, false, false, true, true, true, true},
+        {false, false, true, true, false, false, false, true, true, true, false, false, true, true, true, true},
+        {false, false, true, true, true, true, false, true, true, true, false, false, true, true, true, true},
+        {false, false, false, true, false, false, false, false, true, true, false, false, true, true, true, true},
+        {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false},
+        {false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false},
+        {false, false, true, true, true, true, true, true, true, true, false, false, true, true, true, true},
+        {false, false, true, true, true, true, true, true, true, true, true, false, true, true, true, true},
+        {false, false, false, false, false, false, false, false, true, true, false, false, false, true, false, false},
+        {false, false, false, false, false, false, false, false, true, true, false, false, false, false, false, false},
+        {false, false, false, false, false, false, false, false, true, true, false, false, true, true, false, false},
+        {false, false, false, false, false, false, false, false, true, true, false, false, true, true, true, false}
+    };
+
+    for (size_t i = 0; i < j_values.size(); ++i)
+    {
+        SCOPED_TRACE(i);
+        for (size_t j = 0; j < j_values.size(); ++j)
+        {
+            SCOPED_TRACE(j);
+            // check precomputed values
+            EXPECT_EQ(j_values[i] < j_values[j], expected[i][j]);
+        }
+    }
+
+    // comparison with discarded elements
+    json j_discarded(json::value_t::discarded);
+    for (size_t i = 0; i < j_values.size(); ++i)
+    {
+        SCOPED_TRACE(i);
+        EXPECT_FALSE(j_values[i] < j_discarded);
+        EXPECT_FALSE(j_discarded < j_values[i]);
+        EXPECT_FALSE(j_discarded < j_discarded);
+    }
+}
+
+TEST_F(JsonComparisonValuesTest, LessEqual)
+{
+    for (size_t i = 0; i < j_values.size(); ++i)
+    {
+        SCOPED_TRACE(i);
+        for (size_t j = 0; j < j_values.size(); ++j)
+        {
+            SCOPED_TRACE(j);
+            // check definition
+            EXPECT_EQ(j_values[i] <= j_values[j], !(j_values[j] < j_values[i]));
+        }
+    }
+}
+
+TEST_F(JsonComparisonValuesTest, Greater)
+{
+    for (size_t i = 0; i < j_values.size(); ++i)
+    {
+        SCOPED_TRACE(i);
+        for (size_t j = 0; j < j_values.size(); ++j)
+        {
+            SCOPED_TRACE(j);
+            // check definition
+            EXPECT_EQ(j_values[i] > j_values[j], j_values[j] < j_values[i]);
+        }
+    }
+}
+
+TEST_F(JsonComparisonValuesTest, GreaterEqual)
+{
+    for (size_t i = 0; i < j_values.size(); ++i)
+    {
+        SCOPED_TRACE(i);
+        for (size_t j = 0; j < j_values.size(); ++j)
+        {
+            SCOPED_TRACE(j);
+            // check definition
+            EXPECT_EQ(j_values[i] >= j_values[j], !(j_values[i] < j_values[j]));
+        }
+    }
+}
diff --git a/wpiutil/src/test/native/cpp/json/unit-concepts.cpp b/wpiutil/src/test/native/cpp/json/unit-concepts.cpp
new file mode 100644
index 0000000..4ce5a5b
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-concepts.cpp
@@ -0,0 +1,166 @@
+/*----------------------------------------------------------------------------*/
+/* Modifications Copyright (c) FIRST 2017. All Rights Reserved.               */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include "unit-json.h"
+using wpi::json;
+
+TEST(JsonConceptsTest, ContainerRequirements)
+{
+    // X: container class: json
+    // T: type of objects: json
+    // a, b: values of type X: json
+
+    // TABLE 96 - Container Requirements
+
+    // X::value_type must return T
+    EXPECT_TRUE((std::is_same<json::value_type, json>::value));
+
+    // X::reference must return lvalue of T
+    EXPECT_TRUE((std::is_same<json::reference, json&>::value));
+
+    // X::const_reference must return const lvalue of T
+    EXPECT_TRUE((std::is_same<json::const_reference, const json&>::value));
+
+    // X::iterator must return iterator whose value_type is T
+    EXPECT_TRUE((std::is_same<json::iterator::value_type, json>::value));
+    // X::iterator must meet the forward iterator requirements
+    EXPECT_TRUE((std::is_base_of<std::forward_iterator_tag, typename std::iterator_traits<json::iterator>::iterator_category>::value));
+    // X::iterator must be convertible to X::const_iterator
+    EXPECT_TRUE((std::is_convertible<json::iterator, json::const_iterator>::value));
+
+    // X::const_iterator must return iterator whose value_type is T
+    EXPECT_TRUE((std::is_same<json::const_iterator::value_type, json>::value));
+    // X::const_iterator must meet the forward iterator requirements
+    EXPECT_TRUE((std::is_base_of<std::forward_iterator_tag, typename std::iterator_traits<json::const_iterator>::iterator_category>::value));
+
+    // X::difference_type must return a signed integer
+    EXPECT_TRUE((std::is_signed<json::difference_type>::value));
+    // X::difference_type must be identical to X::iterator::difference_type
+    EXPECT_TRUE((std::is_same<json::difference_type, json::iterator::difference_type>::value));
+    // X::difference_type must be identical to X::const_iterator::difference_type
+    EXPECT_TRUE((std::is_same<json::difference_type, json::const_iterator::difference_type>::value));
+
+    // X::size_type must return an unsigned integer
+    EXPECT_TRUE((std::is_unsigned<json::size_type>::value));
+    // X::size_type can represent any non-negative value of X::difference_type
+    EXPECT_TRUE(static_cast<json::size_type>(std::numeric_limits<json::difference_type>::max()) <=
+          std::numeric_limits<json::size_type>::max());
+
+    // the expression "X u" has the post-condition "u.empty()"
+    {
+        json u;
+        EXPECT_TRUE(u.empty());
+    }
+
+    // the expression "X()" has the post-condition "X().empty()"
+    EXPECT_TRUE(json().empty());
+}
+
+TEST(JsonConceptsTest, DefaultConstructible)
+{
+    EXPECT_TRUE(std::is_nothrow_default_constructible<json>::value);
+}
+
+TEST(JsonConceptsTest, MoveConstructible)
+{
+    EXPECT_TRUE(std::is_nothrow_move_constructible<json>::value);
+}
+
+TEST(JsonConceptsTest, CopyConstructible)
+{
+    EXPECT_TRUE(std::is_copy_constructible<json>::value);
+}
+
+TEST(JsonConceptsTest, MoveAssignable)
+{
+    EXPECT_TRUE(std::is_nothrow_move_assignable<json>::value);
+}
+
+TEST(JsonConceptsTest, CopyAssignable)
+{
+    EXPECT_TRUE(std::is_copy_assignable<json>::value);
+}
+
+TEST(JsonConceptsTest, Destructible)
+{
+    EXPECT_TRUE(std::is_nothrow_destructible<json>::value);
+}
+
+TEST(JsonConceptsTest, StandardLayoutType)
+{
+    EXPECT_TRUE(std::is_standard_layout<json>::value);
+}
+
+TEST(JsonIteratorConceptsTest, CopyConstructible)
+{
+    EXPECT_TRUE(std::is_nothrow_copy_constructible<json::iterator>::value);
+    EXPECT_TRUE(std::is_nothrow_copy_constructible<json::const_iterator>::value);
+}
+
+TEST(JsonIteratorConceptsTest, CopyAssignable)
+{
+    // STL iterators used by json::iterator don't pass this test in Debug mode
+#if !defined(_MSC_VER) || (_ITERATOR_DEBUG_LEVEL == 0)
+    EXPECT_TRUE(std::is_nothrow_copy_assignable<json::iterator>::value);
+    EXPECT_TRUE(std::is_nothrow_copy_assignable<json::const_iterator>::value);
+#endif
+}
+
+TEST(JsonIteratorConceptsTest, Destructible)
+{
+    EXPECT_TRUE(std::is_nothrow_destructible<json::iterator>::value);
+    EXPECT_TRUE(std::is_nothrow_destructible<json::const_iterator>::value);
+}
+
+TEST(JsonIteratorConceptsTest, Swappable)
+{
+    json j {1, 2, 3};
+    json::iterator it1 = j.begin();
+    json::iterator it2 = j.end();
+    std::swap(it1, it2);
+    EXPECT_EQ(it1, j.end());
+    EXPECT_EQ(it2, j.begin());
+}
+
+TEST(JsonIteratorConceptsTest, SwappableConst)
+{
+    json j {1, 2, 3};
+    json::const_iterator it1 = j.cbegin();
+    json::const_iterator it2 = j.cend();
+    std::swap(it1, it2);
+    EXPECT_EQ(it1, j.end());
+    EXPECT_EQ(it2, j.begin());
+}
diff --git a/wpiutil/src/test/native/cpp/json/unit-constructor1.cpp b/wpiutil/src/test/native/cpp/json/unit-constructor1.cpp
new file mode 100644
index 0000000..85b9b91
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-constructor1.cpp
@@ -0,0 +1,1068 @@
+/*----------------------------------------------------------------------------*/
+/* Modifications Copyright (c) FIRST 2017. All Rights Reserved.               */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include <array>
+#include <deque>
+#include <forward_list>
+#include <list>
+#include <map>
+#include <set>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "unit-json.h"
+using wpi::json;
+using wpi::JsonTest;
+
+class JsonConstructTypeTest : public ::testing::TestWithParam<json::value_t> {};
+TEST_P(JsonConstructTypeTest, Case)
+{
+    auto t = GetParam();
+    json j(t);
+    EXPECT_EQ(j.type(), t);
+}
+
+static const json::value_t construct_type_cases[] = {
+    json::value_t::null,
+    json::value_t::discarded,
+    json::value_t::object,
+    json::value_t::array,
+    json::value_t::boolean,
+    json::value_t::string,
+    json::value_t::number_integer,
+    json::value_t::number_unsigned,
+    json::value_t::number_float,
+};
+
+INSTANTIATE_TEST_SUITE_P(JsonConstructTypeTests, JsonConstructTypeTest,
+                        ::testing::ValuesIn(construct_type_cases));
+
+
+TEST(JsonConstructNullTest, NoParameter)
+{
+    json j{};
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonConstructNullTest, Parameter)
+{
+    json j(nullptr);
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonConstructObjectExplicitTest, Empty)
+{
+    json::object_t o;
+    json j(o);
+    EXPECT_EQ(j.type(), json::value_t::object);
+}
+
+TEST(JsonConstructObjectExplicitTest, Filled)
+{
+    json::object_t o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}};
+    json j(o);
+    EXPECT_EQ(j.type(), json::value_t::object);
+}
+
+class JsonConstructObjectImplicitTest : public ::testing::Test {
+ public:
+    JsonConstructObjectImplicitTest() : j_reference(o_reference) {}
+
+ protected:
+    json::object_t o_reference {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}};
+    json j_reference;
+};
+
+// std::map<std::string, json>
+TEST_F(JsonConstructObjectImplicitTest, StdMapStringJson)
+{
+    std::map<std::string, json> o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}};
+    json j(o);
+    EXPECT_EQ(j.type(), json::value_t::object);
+    EXPECT_EQ(j, j_reference);
+}
+
+// std::pair<CompatibleString, T>
+TEST_F(JsonConstructObjectImplicitTest, StdPairStringT)
+{
+    std::pair<std::string, std::string> p{"first", "second"};
+    json j(p);
+
+    EXPECT_EQ(j.get<decltype(p)>(), p);
+
+    std::pair<std::string, int> p2{"first", 1};
+    // use char const*
+    json j2(std::make_pair("first", 1));
+
+    EXPECT_EQ(j2.get<decltype(p2)>(), p2);
+}
+
+// std::map<std::string, std::string>
+TEST_F(JsonConstructObjectImplicitTest, StdMapStringString)
+{
+    std::map<std::string, std::string> m;
+    m["a"] = "b";
+    m["c"] = "d";
+    m["e"] = "f";
+
+    json j(m);
+    EXPECT_EQ(j.get<decltype(m)>(), m);
+}
+
+// std::map<const char*, json>
+TEST_F(JsonConstructObjectImplicitTest, StdMapCharPointerJson)
+{
+    std::map<const char*, json> o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}};
+    json j(o);
+    EXPECT_EQ(j.type(), json::value_t::object);
+    EXPECT_EQ(j, j_reference);
+}
+
+// std::multimap<std::string, json>
+TEST_F(JsonConstructObjectImplicitTest, StdMultiMapStringJson)
+{
+    std::multimap<std::string, json> o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}};
+    json j(o);
+    EXPECT_EQ(j.type(), json::value_t::object);
+    EXPECT_EQ(j, j_reference);
+}
+
+// std::unordered_map<std::string, json>
+TEST_F(JsonConstructObjectImplicitTest, StdUnorderedMapStringJson)
+{
+    std::unordered_map<std::string, json> o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}};
+    json j(o);
+    EXPECT_EQ(j.type(), json::value_t::object);
+    EXPECT_EQ(j, j_reference);
+}
+
+// std::unordered_multimap<std::string, json>
+TEST_F(JsonConstructObjectImplicitTest, StdUnorderedMultiMapStringJson)
+{
+    std::unordered_multimap<std::string, json> o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}};
+    json j(o);
+    EXPECT_EQ(j.type(), json::value_t::object);
+    EXPECT_EQ(j, j_reference);
+}
+
+// associative container literal
+TEST_F(JsonConstructObjectImplicitTest, AssociativeContainerLiteral)
+{
+    json j({{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}});
+    EXPECT_EQ(j.type(), json::value_t::object);
+    EXPECT_EQ(j, j_reference);
+}
+
+TEST(JsonConstructArrayExplicitTest, Empty)
+{
+    json::array_t a;
+    json j(a);
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+TEST(JsonConstructArrayExplicitTest, Filled)
+{
+    json::array_t a {json(1), json(1u), json(2.2), json(false), json("string"), json()};
+    json j(a);
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+template <typename T>
+class JsonConstructArrayTest : public ::testing::Test {
+ public:
+    JsonConstructArrayTest() : j_reference(a_reference) {}
+
+ protected:
+    json::array_t a_reference {json(1), json(1u), json(2.2), json(false), json("string"), json()};
+    json j_reference;
+};
+
+typedef ::testing::Types<std::list<json>, std::forward_list<json>,
+                         std::array<json, 6>, std::vector<json>,
+                         std::deque<json>>
+    JsonConstructArrayTestTypes;
+TYPED_TEST_SUITE(JsonConstructArrayTest, JsonConstructArrayTestTypes, );
+
+// clang warns on missing braces on the TypeParam initializer line below.
+// Suppress this warning.
+#if defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmissing-braces"
+#endif
+TYPED_TEST(JsonConstructArrayTest, Implicit)
+{
+    TypeParam a {json(1), json(1u), json(2.2), json(false), json("string"), json()};
+    json j(a);
+    EXPECT_EQ(j.type(), json::value_t::array);
+    EXPECT_EQ(j, this->j_reference);
+}
+#if defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+// std::set<json>
+TEST(JsonConstructArraySetTest, StdSet)
+{
+    std::set<json> a {json(1), json(1u), json(2.2), json(false), json("string"), json()};
+    json j(a);
+    EXPECT_EQ(j.type(), json::value_t::array);
+    // we cannot really check for equality here
+}
+
+// std::unordered_set<json>
+TEST(JsonConstructArraySetTest, StdUnorderedSet)
+{
+    std::unordered_set<json> a {json(1), json(1u), json(2.2), json(false), json("string"), json()};
+    json j(a);
+    EXPECT_EQ(j.type(), json::value_t::array);
+    // we cannot really check for equality here
+}
+
+// sequence container literal
+TEST(JsonConstructArrayContainerTest, Case)
+{
+    json::array_t a_reference {json(1), json(1u), json(2.2), json(false), json("string"), json()};
+    json j_reference(a_reference);
+
+    json j({json(1), json(1u), json(2.2), json(false), json("string"), json()});
+    EXPECT_EQ(j.type(), json::value_t::array);
+    EXPECT_EQ(j, j_reference);
+}
+
+TEST(JsonConstructStringExplicitTest, Empty)
+{
+    std::string s;
+    json j(s);
+    EXPECT_EQ(j.type(), json::value_t::string);
+}
+
+TEST(JsonConstructStringExplicitTest, Filled)
+{
+    std::string s {"Hello world"};
+    json j(s);
+    EXPECT_EQ(j.type(), json::value_t::string);
+}
+
+class JsonConstructStringTest : public ::testing::Test {
+ public:
+    JsonConstructStringTest() : j_reference(s_reference) {}
+
+ protected:
+    std::string s_reference {"Hello world"};
+    json j_reference;
+};
+
+// std::string
+TEST_F(JsonConstructStringTest, StdString)
+{
+    std::string s {"Hello world"};
+    json j(s);
+    EXPECT_EQ(j.type(), json::value_t::string);
+    EXPECT_EQ(j, j_reference);
+}
+
+// char[]
+TEST_F(JsonConstructStringTest, CharArray)
+{
+    char s[] {"Hello world"};
+    json j(s);
+    EXPECT_EQ(j.type(), json::value_t::string);
+    EXPECT_EQ(j, j_reference);
+}
+
+// const char*
+TEST_F(JsonConstructStringTest, ConstCharPointer)
+{
+    const char* s {"Hello world"};
+    json j(s);
+    EXPECT_EQ(j.type(), json::value_t::string);
+    EXPECT_EQ(j, j_reference);
+}
+
+// string literal
+TEST_F(JsonConstructStringTest, StringLiteral)
+{
+    json j("Hello world");
+    EXPECT_EQ(j.type(), json::value_t::string);
+    EXPECT_EQ(j, j_reference);
+}
+
+TEST(JsonConstructBooleanExplicitTest, Empty)
+{
+    bool b{};
+    json j(b);
+    EXPECT_EQ(j.type(), json::value_t::boolean);
+}
+
+TEST(JsonConstructBooleanExplicitTest, True)
+{
+    json j(true);
+    EXPECT_EQ(j.type(), json::value_t::boolean);
+}
+
+TEST(JsonConstructBooleanExplicitTest, False)
+{
+    json j(false);
+    EXPECT_EQ(j.type(), json::value_t::boolean);
+}
+
+TEST(JsonConstructIntegerExplicitTest, Uninitialized)
+{
+    int64_t n{};
+    json j(n);
+    EXPECT_EQ(j.type(), json::value_t::number_integer);
+}
+
+TEST(JsonConstructIntegerExplicitTest, Initialized)
+{
+    int64_t n(42);
+    json j(n);
+    EXPECT_EQ(j.type(), json::value_t::number_integer);
+}
+
+template <typename T>
+class JsonConstructIntegerTest : public ::testing::Test {
+ public:
+    JsonConstructIntegerTest()
+        : j_reference(n_reference), j_unsigned_reference(n_unsigned_reference) {}
+
+ protected:
+    int64_t n_reference = 42;
+    json j_reference;
+    uint64_t n_unsigned_reference = 42u;
+    json j_unsigned_reference;
+};
+
+typedef ::testing::Types<
+      short
+    , unsigned short
+    , int
+    , unsigned int
+    , long
+    , unsigned long
+    , long long
+    , unsigned long long
+    , int8_t
+    , int16_t
+    , int32_t
+    , int64_t
+#if 0
+    , int8_fast_t
+    , int16_fast_t
+    , int32_fast_t
+    , int64_fast_t
+    , int8_least_t
+    , int16_least_t
+    , int32_least_t
+    , int64_least_t
+#endif
+    , uint8_t
+    , uint16_t
+    , uint32_t
+    , uint64_t
+#if 0
+    , uint8_fast_t
+    , uint16_fast_t
+    , uint32_fast_t
+    , uint64_fast_t
+    , uint8_least_t
+    , uint16_least_t
+    , uint32_least_t
+    , uint64_least_t
+#endif
+    > JsonConstructIntegerTestTypes;
+
+TYPED_TEST_SUITE(JsonConstructIntegerTest, JsonConstructIntegerTestTypes, );
+
+TYPED_TEST(JsonConstructIntegerTest, Implicit)
+{
+    TypeParam n = 42;
+    json j(n);
+    if (std::is_unsigned<TypeParam>::value)
+    {
+        EXPECT_EQ(j.type(), json::value_t::number_unsigned);
+        EXPECT_EQ(j, this->j_unsigned_reference);
+    }
+    else
+    {
+        EXPECT_EQ(j.type(), json::value_t::number_integer);
+        EXPECT_EQ(j, this->j_reference);
+    }
+}
+
+class JsonConstructIntegerLiteralTest : public ::testing::Test {
+ public:
+    JsonConstructIntegerLiteralTest()
+        : j_reference(n_reference), j_unsigned_reference(n_unsigned_reference) {}
+
+ protected:
+    int64_t n_reference = 42;
+    json j_reference;
+    uint64_t n_unsigned_reference = 42u;
+    json j_unsigned_reference;
+};
+
+TEST_F(JsonConstructIntegerLiteralTest, None)
+{
+    json j(42);
+    EXPECT_EQ(j.type(), json::value_t::number_integer);
+    EXPECT_EQ(j, j_reference);
+}
+
+TEST_F(JsonConstructIntegerLiteralTest, U)
+{
+    json j(42u);
+    EXPECT_EQ(j.type(), json::value_t::number_unsigned);
+    EXPECT_EQ(j, j_unsigned_reference);
+}
+
+TEST_F(JsonConstructIntegerLiteralTest, L)
+{
+    json j(42l);
+    EXPECT_EQ(j.type(), json::value_t::number_integer);
+    EXPECT_EQ(j, j_reference);
+}
+
+TEST_F(JsonConstructIntegerLiteralTest, UL)
+{
+    json j(42ul);
+    EXPECT_EQ(j.type(), json::value_t::number_unsigned);
+    EXPECT_EQ(j, j_unsigned_reference);
+}
+
+TEST_F(JsonConstructIntegerLiteralTest, LL)
+{
+    json j(42ll);
+    EXPECT_EQ(j.type(), json::value_t::number_integer);
+    EXPECT_EQ(j, j_reference);
+}
+
+TEST_F(JsonConstructIntegerLiteralTest, ULL)
+{
+    json j(42ull);
+    EXPECT_EQ(j.type(), json::value_t::number_unsigned);
+    EXPECT_EQ(j, j_unsigned_reference);
+}
+
+TEST(JsonConstructFloatExplicitTest, Uninitialized)
+{
+    double n{};
+    json j(n);
+    EXPECT_EQ(j.type(), json::value_t::number_float);
+}
+
+TEST(JsonConstructFloatExplicitTest, Initialized)
+{
+    double n(42.23);
+    json j(n);
+    EXPECT_EQ(j.type(), json::value_t::number_float);
+}
+
+TEST(JsonConstructFloatExplicitTest, Infinity)
+{
+    // infinity is stored properly, but serialized to null
+    double n(std::numeric_limits<double>::infinity());
+    json j(n);
+    EXPECT_EQ(j.type(), json::value_t::number_float);
+
+    // check round trip of infinity
+    double d = j;
+    EXPECT_EQ(d, n);
+
+    // check that inf is serialized to null
+    EXPECT_EQ(j.dump(), "null");
+}
+
+template <typename T>
+class JsonConstructFloatTest : public ::testing::Test {
+ public:
+    JsonConstructFloatTest() : j_reference(n_reference) {}
+
+ protected:
+    double n_reference {42.23};
+    json j_reference;
+};
+
+typedef ::testing::Types<float, double
+#if 0
+                         , long double
+#endif
+                         >
+    JsonConstructFloatTestTypes;
+
+TYPED_TEST_SUITE(JsonConstructFloatTest, JsonConstructFloatTestTypes, );
+
+TYPED_TEST(JsonConstructFloatTest, Implicit)
+{
+    TypeParam n = 42.23f;
+    json j(n);
+    EXPECT_EQ(j.type(), json::value_t::number_float);
+    EXPECT_LT(std::fabs(JsonTest::GetValue(j).number_float -
+                        JsonTest::GetValue(this->j_reference).number_float),
+              0.001);
+}
+
+class JsonConstructFloatLiteralTest : public ::testing::Test {
+ public:
+    JsonConstructFloatLiteralTest() : j_reference(n_reference) {}
+
+ protected:
+    double n_reference {42.23};
+    json j_reference;
+};
+
+TEST_F(JsonConstructFloatLiteralTest, None)
+{
+    json j(42.23);
+    EXPECT_EQ(j.type(), json::value_t::number_float);
+    EXPECT_LT(std::fabs(JsonTest::GetValue(j).number_float -
+                        JsonTest::GetValue(this->j_reference).number_float),
+              0.001);
+}
+
+TEST_F(JsonConstructFloatLiteralTest, F)
+{
+    json j(42.23f);
+    EXPECT_EQ(j.type(), json::value_t::number_float);
+    EXPECT_LT(std::fabs(JsonTest::GetValue(j).number_float -
+                        JsonTest::GetValue(this->j_reference).number_float),
+              0.001);
+}
+
+#if 0
+TEST_F(JsonConstructFloatLiteralTest, L)
+{
+    json j(42.23l);
+    EXPECT_EQ(j.type(), json::value_t::number_float);
+    EXPECT_LT(std::fabs(JsonTest::GetValue(j).number_float -
+                        JsonTest::GetValue(this->j_reference).number_float),
+              0.001);
+}
+#endif
+
+TEST(JsonConstructInitializerEmptyTest, Explicit)
+{
+    json j(json::initializer_list_t{});
+    EXPECT_EQ(j.type(), json::value_t::object);
+}
+
+TEST(JsonConstructInitializerEmptyTest, Implicit)
+{
+    json j {};
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonConstructInitializerOneTest, ExplicitArray)
+{
+    std::initializer_list<json> l = {json(json::array_t())};
+    json j(l);
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+TEST(JsonConstructInitializerOneTest, ImplicitArray)
+{
+    json j {json::array_t()};
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+TEST(JsonConstructInitializerOneTest, ExplicitObject)
+{
+    std::initializer_list<json> l = {json(json::object_t())};
+    json j(l);
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+TEST(JsonConstructInitializerOneTest, ImplicitObject)
+{
+    json j {json::object_t()};
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+TEST(JsonConstructInitializerOneTest, ExplicitString)
+{
+    std::initializer_list<json> l = {json("Hello world")};
+    json j(l);
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+TEST(JsonConstructInitializerOneTest, ImplicitString)
+{
+    json j {"Hello world"};
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+TEST(JsonConstructInitializerOneTest, ExplicitBoolean)
+{
+    std::initializer_list<json> l = {json(true)};
+    json j(l);
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+TEST(JsonConstructInitializerOneTest, ImplicitBoolean)
+{
+    json j {true};
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+TEST(JsonConstructInitializerOneTest, ExplicitInteger)
+{
+    std::initializer_list<json> l = {json(1)};
+    json j(l);
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+TEST(JsonConstructInitializerOneTest, ImplicitInteger)
+{
+    json j {1};
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+TEST(JsonConstructInitializerOneTest, ExplicitUnsigned)
+{
+    std::initializer_list<json> l = {json(1u)};
+    json j(l);
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+TEST(JsonConstructInitializerOneTest, ImplicitUnsigned)
+{
+    json j {1u};
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+TEST(JsonConstructInitializerOneTest, ExplicitFloat)
+{
+    std::initializer_list<json> l = {json(42.23)};
+    json j(l);
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+TEST(JsonConstructInitializerOneTest, ImplicitFloat)
+{
+    json j {42.23};
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+TEST(JsonConstructInitializerManyTest, Explicit)
+{
+    std::initializer_list<json> l = {1, 1u, 42.23, true, nullptr, json::object_t(), json::array_t()};
+    json j(l);
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+TEST(JsonConstructInitializerManyTest, Implicit)
+{
+    json j {1, 1u, 42.23, true, nullptr, json::object_t(), json::array_t()};
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+TEST(JsonConstructInitializerImplicitTest, Object)
+{
+    json j { {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false} };
+    EXPECT_EQ(j.type(), json::value_t::object);
+}
+
+TEST(JsonConstructInitializerImplicitTest, Array)
+{
+    json j { {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false}, 13 };
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+TEST(JsonConstructInitializerExplicitTest, EmptyObject)
+{
+    json j = json::object();
+    EXPECT_EQ(j.type(), json::value_t::object);
+}
+
+TEST(JsonConstructInitializerExplicitTest, Object)
+{
+    json j = json::object({ {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false} });
+    EXPECT_EQ(j.type(), json::value_t::object);
+}
+
+TEST(JsonConstructInitializerExplicitTest, ObjectError)
+{
+    EXPECT_THROW_MSG(json::object({ {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false}, 13 }),
+    json::type_error,
+    "[json.exception.type_error.301] cannot create object from initializer list");
+}
+
+// std::pair<CompatibleString, T> with error
+TEST(JsonConstructInitializerPairErrorTest, WrongFieldNumber)
+{
+    json j{{"too", "much"}, {"string", "fields"}};
+    EXPECT_THROW_MSG((j.get<std::pair<std::string, std::string>>()), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with object");
+}
+
+TEST(JsonConstructInitializerPairErrorTest, WrongJsonType)
+{
+    json j(42);
+    EXPECT_THROW_MSG((j.get<std::pair<std::string, std::string>>()), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with number");
+}
+
+TEST(JsonConstructInitializerTest, EmptyArray)
+{
+    json j = json::array();
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+TEST(JsonConstructInitializerTest, Array)
+{
+    json j = json::array({ {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false} });
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+// create an array of n copies of a given value
+TEST(JsonConstructArrayCopyTest, Case)
+{
+    json v = {1, "foo", 34.23, {1, 2, 3}, {{"A", 1}, {"B", 2u}}};
+    json arr(3, v);
+    EXPECT_EQ(arr.size(), 3u);
+    for (auto& x : arr)
+    {
+        EXPECT_EQ(x, v);
+    }
+}
+
+// create a JSON container from an iterator range
+TEST(JsonConstructIteratorTest, ObjectBeginEnd)
+{
+    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+#if 0
+    json j_new(jobject.begin(), jobject.end());
+    EXPECT_EQ(j_new, jobject);
+#else
+    EXPECT_THROW(json(jobject.begin(), jobject.end()), json::invalid_iterator);
+#endif
+}
+
+TEST(JsonConstructIteratorTest, ObjectBeginEndConst)
+{
+    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+#if 0
+    json j_new(jobject.cbegin(), jobject.cend());
+    EXPECT_EQ(j_new, jobject);
+#else
+    EXPECT_THROW(json(jobject.cbegin(), jobject.cend()), json::invalid_iterator);
+#endif
+}
+
+TEST(JsonConstructIteratorTest, ObjectBeginBegin)
+{
+    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+#if 0
+    json j_new(jobject.begin(), jobject.begin());
+    EXPECT_EQ(j_new, json::object());
+#else
+    EXPECT_THROW(json(jobject.begin(), jobject.end()), json::invalid_iterator);
+#endif
+}
+
+TEST(JsonConstructIteratorTest, ObjectBeginBeginConst)
+{
+    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+#if 0
+    json j_new(jobject.cbegin(), jobject.cbegin());
+    EXPECT_EQ(j_new, json::object());
+#else
+    EXPECT_THROW(json(jobject.cbegin(), jobject.cend()), json::invalid_iterator);
+#endif
+}
+#if 0
+TEST(JsonConstructIteratorTest, ObjectSubrange)
+{
+    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
+    json j_new(jobject.find("b"), jobject.find("e"));
+    EXPECT_EQ(j_new, json({{"b", 1}, {"c", 17u}, {"d", false}}));
+}
+#endif
+TEST(JsonConstructIteratorTest, ObjectIncompatibleIterators)
+{
+    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
+    json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+    EXPECT_THROW_MSG(json(jobject.begin(), jobject2.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.201] iterators are not compatible");
+    EXPECT_THROW_MSG(json(jobject2.begin(), jobject.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.201] iterators are not compatible");
+}
+
+TEST(JsonConstructIteratorTest, ObjectIncompatibleIteratorsConst)
+{
+    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
+    json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+    EXPECT_THROW_MSG(json(jobject.cbegin(), jobject2.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.201] iterators are not compatible");
+    EXPECT_THROW_MSG(json(jobject2.cbegin(), jobject.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.201] iterators are not compatible");
+}
+
+TEST(JsonConstructIteratorTest, ArrayBeginEnd)
+{
+    json jarray = {1, 2, 3, 4, 5};
+    json j_new(jarray.begin(), jarray.end());
+    EXPECT_EQ(j_new, jarray);
+}
+
+TEST(JsonConstructIteratorTest, ArrayBeginEndConst)
+{
+    json jarray = {1, 2, 3, 4, 5};
+    json j_new(jarray.cbegin(), jarray.cend());
+    EXPECT_EQ(j_new, jarray);
+}
+
+TEST(JsonConstructIteratorTest, ArrayBeginBegin)
+{
+    json jarray = {1, 2, 3, 4, 5};
+    json j_new(jarray.begin(), jarray.begin());
+    EXPECT_EQ(j_new, json::array());
+}
+
+TEST(JsonConstructIteratorTest, ArrayBeginBeginConst)
+{
+    json jarray = {1, 2, 3, 4, 5};
+    json j_new(jarray.cbegin(), jarray.cbegin());
+    EXPECT_EQ(j_new, json::array());
+}
+
+TEST(JsonConstructIteratorTest, ArraySubrange)
+{
+    json jarray = {1, 2, 3, 4, 5};
+    json j_new(jarray.begin() + 1, jarray.begin() + 3);
+    EXPECT_EQ(j_new, json({2, 3}));
+}
+
+TEST(JsonConstructIteratorTest, ArraySubrangeConst)
+{
+    json jarray = {1, 2, 3, 4, 5};
+    json j_new(jarray.cbegin() + 1, jarray.cbegin() + 3);
+    EXPECT_EQ(j_new, json({2, 3}));
+}
+
+TEST(JsonConstructIteratorTest, ArrayIncompatibleIterators)
+{
+    json jarray = {1, 2, 3, 4};
+    json jarray2 = {2, 3, 4, 5};
+    EXPECT_THROW_MSG(json(jarray.begin(), jarray2.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.201] iterators are not compatible");
+    EXPECT_THROW_MSG(json(jarray2.begin(), jarray.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.201] iterators are not compatible");
+}
+
+TEST(JsonConstructIteratorTest, ArrayIncompatibleIteratorsConst)
+{
+    json jarray = {1, 2, 3, 4};
+    json jarray2 = {2, 3, 4, 5};
+    EXPECT_THROW_MSG(json(jarray.cbegin(), jarray2.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.201] iterators are not compatible");
+    EXPECT_THROW_MSG(json(jarray2.cbegin(), jarray.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.201] iterators are not compatible");
+}
+
+TEST(JsonConstructTwoValidIteratorTest, Null)
+{
+    json j;
+    EXPECT_THROW_MSG(json(j.begin(), j.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.206] cannot construct with iterators from null");
+}
+
+TEST(JsonConstructTwoValidIteratorTest, NullConst)
+{
+    json j;
+    EXPECT_THROW_MSG(json(j.cbegin(), j.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.206] cannot construct with iterators from null");
+}
+
+TEST(JsonConstructTwoValidIteratorTest, String)
+{
+    json j = "foo";
+    json j_new(j.begin(), j.end());
+    EXPECT_EQ(j, j_new);
+}
+
+TEST(JsonConstructTwoValidIteratorTest, StringConst)
+{
+    json j = "bar";
+    json j_new(j.cbegin(), j.cend());
+    EXPECT_EQ(j, j_new);
+}
+
+TEST(JsonConstructTwoValidIteratorTest, Boolean)
+{
+    json j = false;
+    json j_new(j.begin(), j.end());
+    EXPECT_EQ(j, j_new);
+}
+
+TEST(JsonConstructTwoValidIteratorTest, BooleanConst)
+{
+    json j = true;
+    json j_new(j.cbegin(), j.cend());
+    EXPECT_EQ(j, j_new);
+}
+
+TEST(JsonConstructTwoValidIteratorTest, Integer)
+{
+    json j = 17;
+    json j_new(j.begin(), j.end());
+    EXPECT_EQ(j, j_new);
+}
+
+TEST(JsonConstructTwoValidIteratorTest, IntegerConst)
+{
+    json j = 17;
+    json j_new(j.cbegin(), j.cend());
+    EXPECT_EQ(j, j_new);
+}
+
+TEST(JsonConstructTwoValidIteratorTest, Unsigned)
+{
+    json j = 17u;
+    json j_new(j.begin(), j.end());
+    EXPECT_EQ(j, j_new);
+}
+
+TEST(JsonConstructTwoValidIteratorTest, UnsignedConst)
+{
+    json j = 17u;
+    json j_new(j.cbegin(), j.cend());
+    EXPECT_EQ(j, j_new);
+}
+
+TEST(JsonConstructTwoValidIteratorTest, Float)
+{
+    json j = 23.42;
+    json j_new(j.begin(), j.end());
+    EXPECT_EQ(j, j_new);
+}
+
+TEST(JsonConstructTwoValidIteratorTest, FloatConst)
+{
+    json j = 23.42;
+    json j_new(j.cbegin(), j.cend());
+    EXPECT_EQ(j, j_new);
+}
+
+TEST(JsonConstructTwoInvalidIteratorTest, String)
+{
+    json j = "foo";
+    EXPECT_THROW_MSG(json(j.end(), j.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+    EXPECT_THROW_MSG(json(j.begin(), j.begin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+}
+
+TEST(JsonConstructTwoInvalidIteratorTest, StringConst)
+{
+    json j = "bar";
+    EXPECT_THROW_MSG(json(j.cend(), j.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+    EXPECT_THROW_MSG(json(j.cbegin(), j.cbegin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+}
+
+TEST(JsonConstructTwoInvalidIteratorTest, Boolean)
+{
+    json j = false;
+    EXPECT_THROW_MSG(json(j.end(), j.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+    EXPECT_THROW_MSG(json(j.begin(), j.begin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+}
+
+TEST(JsonConstructTwoInvalidIteratorTest, BooleanConst)
+{
+    json j = true;
+    EXPECT_THROW_MSG(json(j.cend(), j.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+    EXPECT_THROW_MSG(json(j.cbegin(), j.cbegin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+}
+
+TEST(JsonConstructTwoInvalidIteratorTest, Integer)
+{
+    json j = 17;
+    EXPECT_THROW_MSG(json(j.end(), j.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+    EXPECT_THROW_MSG(json(j.begin(), j.begin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+}
+
+TEST(JsonConstructTwoInvalidIteratorTest, IntegerConst)
+{
+    json j = 17;
+    EXPECT_THROW_MSG(json(j.cend(), j.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+    EXPECT_THROW_MSG(json(j.cbegin(), j.cbegin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+}
+
+TEST(JsonConstructTwoInvalidIteratorTest, Unsigned)
+{
+    json j = 17u;
+    EXPECT_THROW_MSG(json(j.end(), j.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+    EXPECT_THROW_MSG(json(j.begin(), j.begin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+}
+
+TEST(JsonConstructTwoInvalidIteratorTest, UnsignedConst)
+{
+    json j = 17u;
+    EXPECT_THROW_MSG(json(j.cend(), j.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+    EXPECT_THROW_MSG(json(j.cbegin(), j.cbegin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+}
+
+TEST(JsonConstructTwoInvalidIteratorTest, Float)
+{
+    json j = 23.42;
+    EXPECT_THROW_MSG(json(j.end(), j.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+    EXPECT_THROW_MSG(json(j.begin(), j.begin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+}
+
+TEST(JsonConstructTwoInvalidIteratorTest, FloatConst)
+{
+    json j = 23.42;
+    EXPECT_THROW_MSG(json(j.cend(), j.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+    EXPECT_THROW_MSG(json(j.cbegin(), j.cbegin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+}
diff --git a/wpiutil/src/test/native/cpp/json/unit-constructor2.cpp b/wpiutil/src/test/native/cpp/json/unit-constructor2.cpp
new file mode 100644
index 0000000..39f1301
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-constructor2.cpp
@@ -0,0 +1,185 @@
+/*----------------------------------------------------------------------------*/
+/* Modifications Copyright (c) FIRST 2017. All Rights Reserved.               */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include "unit-json.h"
+using wpi::json;
+
+TEST(JsonCopyConstructorTest, Object)
+{
+    json j {{"foo", 1}, {"bar", false}};
+    json k(j);
+    EXPECT_EQ(j, k);
+}
+
+TEST(JsonCopyConstructorTest, Array)
+{
+    json j {"foo", 1, 42.23, false};
+    json k(j);
+    EXPECT_EQ(j, k);
+}
+
+TEST(JsonCopyConstructorTest, Null)
+{
+    json j(nullptr);
+    json k(j);
+    EXPECT_EQ(j, k);
+}
+
+TEST(JsonCopyConstructorTest, Boolean)
+{
+    json j(true);
+    json k(j);
+    EXPECT_EQ(j, k);
+}
+
+TEST(JsonCopyConstructorTest, String)
+{
+    json j("Hello world");
+    json k(j);
+    EXPECT_EQ(j, k);
+}
+
+TEST(JsonCopyConstructorTest, Integer)
+{
+    json j(42);
+    json k(j);
+    EXPECT_EQ(j, k);
+}
+
+TEST(JsonCopyConstructorTest, Unsigned)
+{
+    json j(42u);
+    json k(j);
+    EXPECT_EQ(j, k);
+}
+
+TEST(JsonCopyConstructorTest, Float)
+{
+    json j(42.23);
+    json k(j);
+    EXPECT_EQ(j, k);
+}
+
+TEST(JsonMoveConstructorTest, Case)
+{
+    json j {{"foo", "bar"}, {"baz", {1, 2, 3, 4}}, {"a", 42u}, {"b", 42.23}, {"c", nullptr}};
+    EXPECT_EQ(j.type(), json::value_t::object);
+    json k(std::move(j));
+    EXPECT_EQ(k.type(), json::value_t::object);
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonCopyAssignmentTest, Object)
+{
+    json j {{"foo", 1}, {"bar", false}};
+    json k;
+    k = j;
+    EXPECT_EQ(j, k);
+}
+
+TEST(JsonCopyAssignmentTest, Array)
+{
+    json j {"foo", 1, 42.23, false};
+    json k;
+    k = j;
+    EXPECT_EQ(j, k);
+}
+
+TEST(JsonCopyAssignmentTest, Null)
+{
+    json j(nullptr);
+    json k;
+    k = j;
+    EXPECT_EQ(j, k);
+}
+
+TEST(JsonCopyAssignmentTest, Boolean)
+{
+    json j(true);
+    json k;
+    k = j;
+    EXPECT_EQ(j, k);
+}
+
+TEST(JsonCopyAssignmentTest, String)
+{
+    json j("Hello world");
+    json k;
+    k = j;
+    EXPECT_EQ(j, k);
+}
+
+TEST(JsonCopyAssignmentTest, Integer)
+{
+    json j(42);
+    json k;
+    k = j;
+    EXPECT_EQ(j, k);
+}
+
+TEST(JsonCopyAssignmentTest, Unsigned)
+{
+    json j(42u);
+    json k;
+    k = j;
+    EXPECT_EQ(j, k);
+}
+
+TEST(JsonCopyAssignmentTest, Float)
+{
+    json j(42.23);
+    json k;
+    k = j;
+    EXPECT_EQ(j, k);
+}
+
+TEST(JsonDestructorTest, Object)
+{
+    auto j = new json {{"foo", 1}, {"bar", false}};
+    delete j;
+}
+
+TEST(JsonDestructorTest, Array)
+{
+    auto j = new json {"foo", 1, 1u, false, 23.42};
+    delete j;
+}
+
+TEST(JsonDestructorTest, String)
+{
+    auto j = new json("Hello world");
+    delete j;
+}
diff --git a/wpiutil/src/test/native/cpp/json/unit-conversions.cpp b/wpiutil/src/test/native/cpp/json/unit-conversions.cpp
new file mode 100644
index 0000000..b60e5ac
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-conversions.cpp
@@ -0,0 +1,560 @@
+/*----------------------------------------------------------------------------*/
+/* Modifications Copyright (c) FIRST 2017. All Rights Reserved.               */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include "unit-json.h"
+using wpi::json;
+using wpi::JsonTest;
+
+#include <deque>
+//#include <forward_list>
+#include <list>
+#include <map>
+#include <unordered_map>
+#include <unordered_set>
+
+template <typename T>
+class JsonGetObjectTest : public ::testing::Test {
+ public:
+    JsonGetObjectTest() : j(o_reference) {}
+
+ protected:
+    json::object_t o_reference = {{"object", json::object()}, {"array", {1, 2, 3, 4}}, {"number", 42}, {"boolean", false}, {"null", nullptr}, {"string", "Hello world"} };
+    json j;
+};
+
+typedef ::testing::Types<
+      json::object_t
+    , std::map<std::string, json>
+    , std::multimap<std::string, json>
+    , std::unordered_map<std::string, json>
+    , std::unordered_multimap<std::string, json>
+    > JsonGetObjectTestTypes;
+TYPED_TEST_SUITE(JsonGetObjectTest, JsonGetObjectTestTypes, );
+
+TYPED_TEST(JsonGetObjectTest, Explicit)
+{
+    TypeParam o = (this->j).template get<TypeParam>();
+    EXPECT_EQ(json(o), this->j);
+}
+
+TYPED_TEST(JsonGetObjectTest, Implicit)
+{
+    TypeParam o = this->j;
+    EXPECT_EQ(json(o), this->j);
+}
+
+// exception in case of a non-object type
+TEST(JsonGetObjectExceptionTest, TypeError)
+{
+    EXPECT_THROW_MSG(json(json::value_t::null).get<json::object_t>(), json::type_error,
+                     "[json.exception.type_error.302] type must be object, but is null");
+    EXPECT_THROW_MSG(json(json::value_t::array).get<json::object_t>(), json::type_error,
+                     "[json.exception.type_error.302] type must be object, but is array");
+    EXPECT_THROW_MSG(json(json::value_t::string).get<json::object_t>(), json::type_error,
+                     "[json.exception.type_error.302] type must be object, but is string");
+    EXPECT_THROW_MSG(json(json::value_t::boolean).get<json::object_t>(), json::type_error,
+                     "[json.exception.type_error.302] type must be object, but is boolean");
+    EXPECT_THROW_MSG(json(json::value_t::number_integer).get<json::object_t>(), json::type_error,
+                     "[json.exception.type_error.302] type must be object, but is number");
+    EXPECT_THROW_MSG(json(json::value_t::number_unsigned).get<json::object_t>(), json::type_error,
+                     "[json.exception.type_error.302] type must be object, but is number");
+    EXPECT_THROW_MSG(json(json::value_t::number_float).get<json::object_t>(), json::type_error,
+                     "[json.exception.type_error.302] type must be object, but is number");
+}
+
+template <typename T>
+class JsonGetArrayTest : public ::testing::Test {
+ public:
+    JsonGetArrayTest() : j(a_reference) {}
+
+ protected:
+    json::array_t a_reference {json(1), json(1u), json(2.2), json(false), json("string"), json()};
+    json j;
+};
+
+typedef ::testing::Types<json::array_t, std::list<json>,
+                         /*std::forward_list<json>,*/ std::vector<json>,
+                         std::deque<json>>
+    JsonGetArrayTestTypes;
+TYPED_TEST_SUITE(JsonGetArrayTest, JsonGetArrayTestTypes, );
+
+TYPED_TEST(JsonGetArrayTest, Explicit)
+{
+    TypeParam a = (this->j).template get<TypeParam>();
+    EXPECT_EQ(json(a), this->j);
+}
+
+TYPED_TEST(JsonGetArrayTest, Implicit)
+{
+    TypeParam a = this->j;
+    EXPECT_EQ(json(a), this->j);
+}
+
+#if !defined(JSON_NOEXCEPTION)
+// reserve is called on containers that supports it
+TEST(JsonGetArrayAdditionalTest, ExplicitStdVectorReserve)
+{
+    json::array_t a_reference {json(1), json(1u), json(2.2), json(false), json("string"), json()};
+    json j(a_reference);
+
+    // making the call to from_json throw in order to check capacity
+    std::vector<float> v;
+    EXPECT_THROW(wpi::from_json(j, v), json::type_error);
+    EXPECT_EQ(v.capacity(), j.size());
+
+    // make sure all values are properly copied
+    std::vector<int> v2 = json({1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
+    EXPECT_EQ(v2.size(), 10u);
+}
+#endif
+
+// built-in arrays
+TEST(JsonGetArrayAdditionalTest, ExplicitBuiltinArray)
+{
+    const char str[] = "a string";
+    const int nbs[] = {0, 1, 2};
+
+    json j2 = nbs;
+    json j3 = str;
+
+    auto v = j2.get<std::vector<int>>();
+    auto s = j3.get<std::string>();
+    EXPECT_TRUE(std::equal(v.begin(), v.end(), std::begin(nbs)));
+    EXPECT_EQ(s, str);
+}
+#if 0
+TEST(JsonGetArrayExceptionTest, ForwardList)
+{
+    EXPECT_THROW_MSG(json(json::value_t::null).get<std::forward_list<json>>(), json::type_error,
+                     "[json.exception.type_error.302] type must be array, but is null");
+}
+#endif
+TEST(JsonGetArrayExceptionTest, StdVector)
+{
+    EXPECT_THROW_MSG(json(json::value_t::null).get<std::vector<json>>(), json::type_error,
+                     "[json.exception.type_error.302] type must be array, but is null");
+}
+
+// exception in case of a non-array type
+TEST(JsonGetArrayExceptionTest, TypeError)
+{
+    EXPECT_THROW_MSG(json(json::value_t::object).get<std::vector<int>>(), json::type_error,
+                     "[json.exception.type_error.302] type must be array, but is object");
+    EXPECT_THROW_MSG(json(json::value_t::null).get<json::array_t>(), json::type_error,
+                     "[json.exception.type_error.302] type must be array, but is null");
+    EXPECT_THROW_MSG(json(json::value_t::object).get<json::array_t>(), json::type_error,
+                     "[json.exception.type_error.302] type must be array, but is object");
+    EXPECT_THROW_MSG(json(json::value_t::string).get<json::array_t>(), json::type_error,
+                     "[json.exception.type_error.302] type must be array, but is string");
+    EXPECT_THROW_MSG(json(json::value_t::boolean).get<json::array_t>(), json::type_error,
+                     "[json.exception.type_error.302] type must be array, but is boolean");
+    EXPECT_THROW_MSG(json(json::value_t::number_integer).get<json::array_t>(), json::type_error,
+                     "[json.exception.type_error.302] type must be array, but is number");
+    EXPECT_THROW_MSG(json(json::value_t::number_unsigned).get<json::array_t>(), json::type_error,
+                     "[json.exception.type_error.302] type must be array, but is number");
+    EXPECT_THROW_MSG(json(json::value_t::number_float).get<json::array_t>(), json::type_error,
+                     "[json.exception.type_error.302] type must be array, but is number");
+}
+
+template <typename T>
+class JsonGetStringTest : public ::testing::Test {
+ public:
+    JsonGetStringTest() : j(s_reference) {}
+
+ protected:
+    std::string s_reference {"Hello world"};
+    json j;
+};
+
+typedef ::testing::Types<std::string, std::string> JsonGetStringTestTypes;
+TYPED_TEST_SUITE(JsonGetStringTest, JsonGetStringTestTypes, );
+
+TYPED_TEST(JsonGetStringTest, Explicit)
+{
+    TypeParam s = (this->j).template get<TypeParam>();
+    EXPECT_EQ(json(s), this->j);
+}
+
+TYPED_TEST(JsonGetStringTest, Implicit)
+{
+    TypeParam s = this->j;
+    EXPECT_EQ(json(s), this->j);
+}
+
+// exception in case of a non-string type
+TEST(JsonGetStringExceptionTest, TypeError)
+{
+    EXPECT_THROW_MSG(json(json::value_t::null).get<std::string>(), json::type_error,
+                     "[json.exception.type_error.302] type must be string, but is null");
+    EXPECT_THROW_MSG(json(json::value_t::object).get<std::string>(), json::type_error,
+                     "[json.exception.type_error.302] type must be string, but is object");
+    EXPECT_THROW_MSG(json(json::value_t::array).get<std::string>(), json::type_error,
+                     "[json.exception.type_error.302] type must be string, but is array");
+    EXPECT_THROW_MSG(json(json::value_t::boolean).get<std::string>(), json::type_error,
+                     "[json.exception.type_error.302] type must be string, but is boolean");
+    EXPECT_THROW_MSG(json(json::value_t::number_integer).get<std::string>(), json::type_error,
+                     "[json.exception.type_error.302] type must be string, but is number");
+    EXPECT_THROW_MSG(json(json::value_t::number_unsigned).get<std::string>(), json::type_error,
+                     "[json.exception.type_error.302] type must be string, but is number");
+    EXPECT_THROW_MSG(json(json::value_t::number_float).get<std::string>(), json::type_error,
+                     "[json.exception.type_error.302] type must be string, but is number");
+}
+
+template <typename T>
+class JsonGetBooleanTest : public ::testing::Test {
+ public:
+    JsonGetBooleanTest() : j(b_reference) {}
+
+ protected:
+    bool b_reference {true};
+    json j;
+};
+
+typedef ::testing::Types<bool, bool> JsonGetBooleanTestTypes;
+TYPED_TEST_SUITE(JsonGetBooleanTest, JsonGetBooleanTestTypes, );
+
+TYPED_TEST(JsonGetBooleanTest, Explicit)
+{
+    TypeParam b = (this->j).template get<TypeParam>();
+    EXPECT_EQ(json(b), this->j);
+}
+
+TYPED_TEST(JsonGetBooleanTest, Implicit)
+{
+    TypeParam b = this->j;
+    EXPECT_EQ(json(b), this->j);
+}
+
+// exception in case of a non-string type
+TEST(JsonGetBooleanExceptionTest, TypeError)
+{
+    EXPECT_THROW_MSG(json(json::value_t::null).get<bool>(), json::type_error,
+                     "[json.exception.type_error.302] type must be boolean, but is null");
+    EXPECT_THROW_MSG(json(json::value_t::object).get<bool>(), json::type_error,
+                     "[json.exception.type_error.302] type must be boolean, but is object");
+    EXPECT_THROW_MSG(json(json::value_t::array).get<bool>(), json::type_error,
+                     "[json.exception.type_error.302] type must be boolean, but is array");
+    EXPECT_THROW_MSG(json(json::value_t::string).get<bool>(), json::type_error,
+                     "[json.exception.type_error.302] type must be boolean, but is string");
+    EXPECT_THROW_MSG(json(json::value_t::number_integer).get<bool>(), json::type_error,
+                     "[json.exception.type_error.302] type must be boolean, but is number");
+    EXPECT_THROW_MSG(json(json::value_t::number_unsigned).get<bool>(), json::type_error,
+                     "[json.exception.type_error.302] type must be boolean, but is number");
+    EXPECT_THROW_MSG(json(json::value_t::number_float).get<bool>(), json::type_error,
+                     "[json.exception.type_error.302] type must be boolean, but is number");
+}
+
+template <typename T>
+class JsonGetIntegerTest : public ::testing::Test {
+ public:
+    JsonGetIntegerTest() : j(n_reference), j_unsigned(n_unsigned_reference) {}
+
+ protected:
+    int64_t n_reference {42};
+    json j;
+    uint64_t n_unsigned_reference {42u};
+    json j_unsigned;
+};
+
+typedef ::testing::Types<
+      short
+    , unsigned short
+    , int
+    , unsigned int
+    , long
+    , unsigned long
+    , long long
+    , unsigned long long
+    , int8_t
+    , int16_t
+    , int32_t
+    , int64_t
+#if 0
+    , int8_fast_t
+    , int16_fast_t
+    , int32_fast_t
+    , int64_fast_t
+    , int8_least_t
+    , int16_least_t
+    , int32_least_t
+    , int64_least_t
+#endif
+    , uint8_t
+    , uint16_t
+    , uint32_t
+    , uint64_t
+#if 0
+    , uint8_fast_t
+    , uint16_fast_t
+    , uint32_fast_t
+    , uint64_fast_t
+    , uint8_least_t
+    , uint16_least_t
+    , uint32_least_t
+    , uint64_least_t
+#endif
+    > JsonGetIntegerTestTypes;
+
+TYPED_TEST_SUITE(JsonGetIntegerTest, JsonGetIntegerTestTypes, );
+
+TYPED_TEST(JsonGetIntegerTest, Explicit)
+{
+    TypeParam n = (this->j).template get<TypeParam>();
+    EXPECT_EQ(json(n), this->j);
+}
+
+TYPED_TEST(JsonGetIntegerTest, Implicit)
+{
+    if (std::is_unsigned<TypeParam>::value)
+    {
+        TypeParam n = this->j_unsigned;
+        EXPECT_EQ(json(n), this->j_unsigned);
+    }
+    else
+    {
+        TypeParam n = this->j;
+        EXPECT_EQ(json(n), this->j);
+    }
+}
+
+// exception in case of a non-number type
+TEST(JsonGetIntegerExceptionTest, TypeError)
+{
+    EXPECT_THROW_MSG(json(json::value_t::null).get<int64_t>(), json::type_error,
+                     "[json.exception.type_error.302] type must be number, but is null");
+    EXPECT_THROW_MSG(json(json::value_t::object).get<int64_t>(), json::type_error,
+                     "[json.exception.type_error.302] type must be number, but is object");
+    EXPECT_THROW_MSG(json(json::value_t::array).get<int64_t>(), json::type_error,
+                     "[json.exception.type_error.302] type must be number, but is array");
+    EXPECT_THROW_MSG(json(json::value_t::string).get<int64_t>(), json::type_error,
+                     "[json.exception.type_error.302] type must be number, but is string");
+    EXPECT_THROW_MSG(json(json::value_t::boolean).get<int64_t>(), json::type_error,
+                     "[json.exception.type_error.302] type must be number, but is boolean");
+
+    EXPECT_NO_THROW(json(json::value_t::number_float).get<int64_t>());
+    EXPECT_NO_THROW(json(json::value_t::number_float).get<uint64_t>());
+}
+
+template <typename T>
+class JsonGetFloatTest : public ::testing::Test {
+ public:
+    JsonGetFloatTest() : j(n_reference) {}
+
+ protected:
+    double n_reference {42.23};
+    json j;
+};
+
+typedef ::testing::Types<double, float, double>
+    JsonGetFloatTestTypes;
+
+TYPED_TEST_SUITE(JsonGetFloatTest, JsonGetFloatTestTypes, );
+
+TYPED_TEST(JsonGetFloatTest, Explicit)
+{
+    TypeParam n = (this->j).template get<TypeParam>();
+    EXPECT_LT(std::fabs(JsonTest::GetValue(json(n)).number_float -
+                        JsonTest::GetValue(this->j).number_float), 0.001);
+}
+
+TYPED_TEST(JsonGetFloatTest, Implicit)
+{
+    TypeParam n = this->j;
+    EXPECT_LT(std::fabs(JsonTest::GetValue(json(n)).number_float -
+                        JsonTest::GetValue(this->j).number_float), 0.001);
+}
+
+// exception in case of a non-string type
+TEST(JsonGetFloatExceptionTest, TypeError)
+{
+    EXPECT_THROW_MSG(json(json::value_t::null).get<double>(), json::type_error,
+                     "[json.exception.type_error.302] type must be number, but is null");
+    EXPECT_THROW_MSG(json(json::value_t::object).get<double>(), json::type_error,
+                     "[json.exception.type_error.302] type must be number, but is object");
+    EXPECT_THROW_MSG(json(json::value_t::array).get<double>(), json::type_error,
+                     "[json.exception.type_error.302] type must be number, but is array");
+    EXPECT_THROW_MSG(json(json::value_t::string).get<double>(), json::type_error,
+                     "[json.exception.type_error.302] type must be number, but is string");
+    EXPECT_THROW_MSG(json(json::value_t::boolean).get<double>(), json::type_error,
+                     "[json.exception.type_error.302] type must be number, but is boolean");
+
+    EXPECT_NO_THROW(json(json::value_t::number_integer).get<double>());
+    EXPECT_NO_THROW(json(json::value_t::number_unsigned).get<double>());
+}
+
+TEST(JsonGetEnumTest, Case)
+{
+    enum c_enum { value_1, value_2 };
+    enum class cpp_enum { value_1, value_2 };
+
+    EXPECT_EQ(json(value_1).get<c_enum>(), value_1);
+    EXPECT_EQ(json(cpp_enum::value_1).get<cpp_enum>(), cpp_enum::value_1);
+}
+
+class JsonObjectConversionTest : public ::testing::Test {
+ protected:
+    json j1 = {{"one", 1}, {"two", 2}, {"three", 3}};
+    json j2 = {{"one", 1u}, {"two", 2u}, {"three", 3u}};
+    json j3 = {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}};
+    json j4 = {{"one", true}, {"two", false}, {"three", true}};
+    json j5 = {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}};
+};
+
+TEST_F(JsonObjectConversionTest, StdMap)
+{
+    auto m1 = j1.get<std::map<std::string, int>>();
+    auto m2 = j2.get<std::map<std::string, unsigned int>>();
+    auto m3 = j3.get<std::map<std::string, double>>();
+    auto m4 = j4.get<std::map<std::string, bool>>();
+    //auto m5 = j5.get<std::map<std::string, std::string>>();
+}
+
+TEST_F(JsonObjectConversionTest, StdUnorderedMap)
+{
+    auto m1 = j1.get<std::unordered_map<std::string, int>>();
+    auto m2 = j2.get<std::unordered_map<std::string, unsigned int>>();
+    auto m3 = j3.get<std::unordered_map<std::string, double>>();
+    auto m4 = j4.get<std::unordered_map<std::string, bool>>();
+    //auto m5 = j5.get<std::unordered_map<std::string, std::string>>();
+    //CHECK(m5["one"] == "eins");
+}
+
+TEST_F(JsonObjectConversionTest, StdMultiMap)
+{
+    auto m1 = j1.get<std::multimap<std::string, int>>();
+    auto m2 = j2.get<std::multimap<std::string, unsigned int>>();
+    auto m3 = j3.get<std::multimap<std::string, double>>();
+    auto m4 = j4.get<std::multimap<std::string, bool>>();
+    //auto m5 = j5.get<std::multimap<std::string, std::string>>();
+    //CHECK(m5["one"] == "eins");
+}
+
+TEST_F(JsonObjectConversionTest, StdUnorderedMultiMap)
+{
+    auto m1 = j1.get<std::unordered_multimap<std::string, int>>();
+    auto m2 = j2.get<std::unordered_multimap<std::string, unsigned int>>();
+    auto m3 = j3.get<std::unordered_multimap<std::string, double>>();
+    auto m4 = j4.get<std::unordered_multimap<std::string, bool>>();
+    //auto m5 = j5.get<std::unordered_multimap<std::string, std::string>>();
+    //CHECK(m5["one"] == "eins");
+}
+
+// exception in case of a non-object type
+TEST_F(JsonObjectConversionTest, Exception)
+{
+    EXPECT_THROW_MSG((json().get<std::map<std::string, int>>()), json::type_error,
+                     "[json.exception.type_error.302] type must be object, but is null");
+}
+
+class JsonArrayConversionTest : public ::testing::Test {
+ protected:
+    json j1 = {1, 2, 3, 4};
+    json j2 = {1u, 2u, 3u, 4u};
+    json j3 = {1.2, 2.3, 3.4, 4.5};
+    json j4 = {true, false, true};
+    json j5 = {"one", "two", "three"};
+};
+
+TEST_F(JsonArrayConversionTest, StdList)
+{
+    auto m1 = j1.get<std::list<int>>();
+    auto m2 = j2.get<std::list<unsigned int>>();
+    auto m3 = j3.get<std::list<double>>();
+    auto m4 = j4.get<std::list<bool>>();
+    auto m5 = j5.get<std::list<std::string>>();
+}
+
+#if 0
+TEST_F(JsonArrayConversionTest, StdForwardList)
+{
+    auto m1 = j1.get<std::forward_list<int>>();
+    auto m2 = j2.get<std::forward_list<unsigned int>>();
+    auto m3 = j3.get<std::forward_list<double>>();
+    auto m4 = j4.get<std::forward_list<bool>>();
+    auto m5 = j5.get<std::forward_list<std::string>>();
+}
+#endif
+
+TEST_F(JsonArrayConversionTest, StdVector)
+{
+    auto m1 = j1.get<std::vector<int>>();
+    auto m2 = j2.get<std::vector<unsigned int>>();
+    auto m3 = j3.get<std::vector<double>>();
+    auto m4 = j4.get<std::vector<bool>>();
+    auto m5 = j5.get<std::vector<std::string>>();
+}
+
+TEST_F(JsonArrayConversionTest, StdDeque)
+{
+    auto m1 = j1.get<std::deque<int>>();
+    auto m2 = j2.get<std::deque<unsigned int>>();
+    auto m3 = j2.get<std::deque<double>>();
+    auto m4 = j4.get<std::deque<bool>>();
+    auto m5 = j5.get<std::deque<std::string>>();
+}
+
+TEST_F(JsonArrayConversionTest, StdSet)
+{
+    auto m1 = j1.get<std::set<int>>();
+    auto m2 = j2.get<std::set<unsigned int>>();
+    auto m3 = j3.get<std::set<double>>();
+    auto m4 = j4.get<std::set<bool>>();
+    auto m5 = j5.get<std::set<std::string>>();
+}
+
+TEST_F(JsonArrayConversionTest, StdUnorderedSet)
+{
+    auto m1 = j1.get<std::unordered_set<int>>();
+    auto m2 = j2.get<std::unordered_set<unsigned int>>();
+    auto m3 = j3.get<std::unordered_set<double>>();
+    auto m4 = j4.get<std::unordered_set<bool>>();
+    auto m5 = j5.get<std::unordered_set<std::string>>();
+}
+
+// exception in case of a non-object type
+TEST_F(JsonArrayConversionTest, Exception)
+{
+    EXPECT_THROW_MSG((json().get<std::list<int>>()), json::type_error,
+                     "[json.exception.type_error.302] type must be array, but is null");
+    EXPECT_THROW_MSG((json().get<std::vector<int>>()), json::type_error,
+                     "[json.exception.type_error.302] type must be array, but is null");
+    EXPECT_THROW_MSG((json().get<std::vector<json>>()), json::type_error,
+                     "[json.exception.type_error.302] type must be array, but is null");
+    EXPECT_THROW_MSG((json().get<std::list<json>>()), json::type_error,
+                     "[json.exception.type_error.302] type must be array, but is null");
+    // does type really must be an array? or it rather must not be null?
+    // that's what I thought when other test like this one broke
+}
diff --git a/wpiutil/src/test/native/cpp/json/unit-deserialization.cpp b/wpiutil/src/test/native/cpp/json/unit-deserialization.cpp
new file mode 100644
index 0000000..2505ef5
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-deserialization.cpp
@@ -0,0 +1,138 @@
+/*----------------------------------------------------------------------------*/
+/* Modifications Copyright (c) FIRST 2017. All Rights Reserved.               */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include "unit-json.h"
+#include "wpi/raw_istream.h"
+using wpi::json;
+
+#include <valarray>
+
+TEST(JsonDeserializationTest, SuccessfulStream)
+{
+    std::string s = "[\"foo\",1,2,3,false,{\"one\":1}]";
+    wpi::raw_mem_istream ss(s.data(), s.size());
+    json j = json::parse(ss);
+    ASSERT_EQ(j, json({"foo", 1, 2, 3, false, {{"one", 1}}}));
+}
+
+TEST(JsonDeserializationTest, SuccessfulStringLiteral)
+{
+    auto s = "[\"foo\",1,2,3,false,{\"one\":1}]";
+    json j = json::parse(s);
+    ASSERT_EQ(j, json({"foo", 1, 2, 3, false, {{"one", 1}}}));
+}
+
+TEST(JsonDeserializationTest, SuccessfulStdString)
+{
+    std::string s = "[\"foo\",1,2,3,false,{\"one\":1}]";
+    json j = json::parse(s);
+    ASSERT_EQ(j, json({"foo", 1, 2, 3, false, {{"one", 1}}}));
+}
+
+TEST(JsonDeserializationTest, SuccessfulStreamOperator)
+{
+    std::string s = "[\"foo\",1,2,3,false,{\"one\":1}]";
+    wpi::raw_mem_istream ss(s.data(), s.size());
+    json j;
+    ss >> j;
+    ASSERT_EQ(j, json({"foo", 1, 2, 3, false, {{"one", 1}}}));
+}
+
+TEST(JsonDeserializationTest, SuccessfulUserStringLiteral)
+{
+    ASSERT_EQ("[\"foo\",1,2,3,false,{\"one\":1}]"_json, json({"foo", 1, 2, 3, false, {{"one", 1}}}));
+}
+
+TEST(JsonDeserializationTest, UnsuccessfulStream)
+{
+    std::string s = "[\"foo\",1,2,3,false,{\"one\":1}";
+    wpi::raw_mem_istream ss(s.data(), s.size());
+    ASSERT_THROW_MSG(json::parse(ss), json::parse_error,
+                     "[json.exception.parse_error.101] parse error at 29: syntax error - unexpected end of input; expected ']'");
+}
+
+TEST(JsonDeserializationTest, UnsuccessfulStdString)
+{
+    std::string s = "[\"foo\",1,2,3,false,{\"one\":1}";
+    ASSERT_THROW_MSG(json::parse(s), json::parse_error,
+                     "[json.exception.parse_error.101] parse error at 29: syntax error - unexpected end of input; expected ']'");
+}
+
+TEST(JsonDeserializationTest, UnsuccessfulStreamOperator)
+{
+    std::string s = "[\"foo\",1,2,3,false,{\"one\":1}";
+    wpi::raw_mem_istream ss(s.data(), s.size());
+    json j;
+    ASSERT_THROW_MSG(ss >> j, json::parse_error,
+                     "[json.exception.parse_error.101] parse error at 29: syntax error - unexpected end of input; expected ']'");
+}
+
+TEST(JsonDeserializationTest, UnsuccessfulUserStringLiteral)
+{
+    ASSERT_THROW_MSG("[\"foo\",1,2,3,false,{\"one\":1}"_json, json::parse_error,
+                     "[json.exception.parse_error.101] parse error at 29: syntax error - unexpected end of input; expected ']'");
+}
+
+// these cases are required for 100% line coverage
+class JsonDeserializationErrorTest
+    : public ::testing::TestWithParam<const char*> {};
+
+TEST_P(JsonDeserializationErrorTest, ErrorCase)
+{
+    ASSERT_THROW(json::parse(GetParam()), json::parse_error);
+}
+
+static const char* error_cases[] = {
+    "\"aaaaaa\\u",
+    "\"aaaaaa\\u1",
+    "\"aaaaaa\\u11111111",
+    "\"aaaaaau11111111\\",
+    "\"\x7F\xC1",
+    "\"\x7F\xDF\x7F",
+    "\"\x7F\xDF\xC0",
+    "\"\x7F\xE0\x9F",
+    "\"\x7F\xEF\xC0",
+    "\"\x7F\xED\x7F",
+    "\"\x7F\xF0\x8F",
+    "\"\x7F\xF0\xC0",
+    "\"\x7F\xF3\x7F",
+    "\"\x7F\xF3\xC0",
+    "\"\x7F\xF4\x7F",
+};
+
+INSTANTIATE_TEST_SUITE_P(JsonDeserializationErrorTests,
+                        JsonDeserializationErrorTest,
+                        ::testing::ValuesIn(error_cases));
diff --git a/wpiutil/src/test/native/cpp/json/unit-element_access1.cpp b/wpiutil/src/test/native/cpp/json/unit-element_access1.cpp
new file mode 100644
index 0000000..a97d6b6
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-element_access1.cpp
@@ -0,0 +1,873 @@
+/*----------------------------------------------------------------------------*/
+/* Modifications Copyright (c) FIRST 2017. All Rights Reserved.               */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include "unit-json.h"
+using wpi::json;
+
+class JsonElementArrayAccessTestBase {
+ public:
+    JsonElementArrayAccessTestBase() : j_const(j) {}
+
+ protected:
+    json j = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+    const json j_const;
+};
+
+class JsonElementArrayAccessTest : public ::testing::Test,
+                                   public JsonElementArrayAccessTestBase {};
+
+TEST_F(JsonElementArrayAccessTest, AtWithinBounds)
+{
+    EXPECT_EQ(j.at(0), json(1));
+    EXPECT_EQ(j.at(1), json(1u));
+    EXPECT_EQ(j.at(2), json(true));
+    EXPECT_EQ(j.at(3), json(nullptr));
+    EXPECT_EQ(j.at(4), json("string"));
+    EXPECT_EQ(j.at(5), json(42.23));
+    EXPECT_EQ(j.at(6), json::object());
+    EXPECT_EQ(j.at(7), json({1, 2, 3}));
+
+    EXPECT_EQ(j_const.at(0), json(1));
+    EXPECT_EQ(j_const.at(1), json(1u));
+    EXPECT_EQ(j_const.at(2), json(true));
+    EXPECT_EQ(j_const.at(3), json(nullptr));
+    EXPECT_EQ(j_const.at(4), json("string"));
+    EXPECT_EQ(j_const.at(5), json(42.23));
+    EXPECT_EQ(j_const.at(6), json::object());
+    EXPECT_EQ(j_const.at(7), json({1, 2, 3}));
+}
+
+TEST_F(JsonElementArrayAccessTest, AtOutsideBounds)
+{
+    EXPECT_THROW_MSG(j.at(8), json::out_of_range,
+                     "[json.exception.out_of_range.401] array index 8 is out of range");
+    EXPECT_THROW_MSG(j_const.at(8), json::out_of_range,
+                     "[json.exception.out_of_range.401] array index 8 is out of range");
+}
+
+TEST(JsonElementNonArrayAtAccessTest, Null)
+{
+    json j_nonarray(json::value_t::null);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_THROW_MSG(j_nonarray.at(0), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with null");
+    EXPECT_THROW_MSG(j_nonarray_const.at(0), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with null");
+}
+
+TEST(JsonElementNonArrayAtAccessTest, Boolean)
+{
+    json j_nonarray(json::value_t::boolean);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_THROW_MSG(j_nonarray.at(0), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with boolean");
+    EXPECT_THROW_MSG(j_nonarray_const.at(0), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with boolean");
+}
+
+TEST(JsonElementNonArrayAtAccessTest, String)
+{
+    json j_nonarray(json::value_t::string);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_THROW_MSG(j_nonarray.at(0), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with string");
+    EXPECT_THROW_MSG(j_nonarray_const.at(0), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with string");
+}
+
+TEST(JsonElementNonArrayAtAccessTest, Object)
+{
+    json j_nonarray(json::value_t::object);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_THROW_MSG(j_nonarray.at(0), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with object");
+    EXPECT_THROW_MSG(j_nonarray_const.at(0), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with object");
+}
+
+TEST(JsonElementNonArrayAtAccessTest, Integer)
+{
+    json j_nonarray(json::value_t::number_integer);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_THROW_MSG(j_nonarray.at(0), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with number");
+    EXPECT_THROW_MSG(j_nonarray_const.at(0), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with number");
+}
+
+TEST(JsonElementNonArrayAtAccessTest, Unsigned)
+{
+    json j_nonarray(json::value_t::number_unsigned);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_THROW_MSG(j_nonarray.at(0), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with number");
+    EXPECT_THROW_MSG(j_nonarray_const.at(0), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with number");
+}
+
+TEST(JsonElementNonArrayAtAccessTest, Float)
+{
+    json j_nonarray(json::value_t::number_float);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_THROW_MSG(j_nonarray.at(0), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with number");
+    EXPECT_THROW_MSG(j_nonarray_const.at(0), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with number");
+}
+
+TEST_F(JsonElementArrayAccessTest, FrontAndBack)
+{
+    EXPECT_EQ(j.front(), json(1));
+    EXPECT_EQ(j_const.front(), json(1));
+    EXPECT_EQ(j.back(), json({1, 2, 3}));
+    EXPECT_EQ(j_const.back(), json({1, 2, 3}));
+}
+
+TEST_F(JsonElementArrayAccessTest, OperatorWithinBounds)
+{
+    EXPECT_EQ(j[0], json(1));
+    EXPECT_EQ(j[1], json(1u));
+    EXPECT_EQ(j[2], json(true));
+    EXPECT_EQ(j[3], json(nullptr));
+    EXPECT_EQ(j[4], json("string"));
+    EXPECT_EQ(j[5], json(42.23));
+    EXPECT_EQ(j[6], json::object());
+    EXPECT_EQ(j[7], json({1, 2, 3}));
+
+    EXPECT_EQ(j_const[0], json(1));
+    EXPECT_EQ(j_const[1], json(1u));
+    EXPECT_EQ(j_const[2], json(true));
+    EXPECT_EQ(j_const[3], json(nullptr));
+    EXPECT_EQ(j_const[4], json("string"));
+    EXPECT_EQ(j_const[5], json(42.23));
+    EXPECT_EQ(j_const[6], json::object());
+    EXPECT_EQ(j_const[7], json({1, 2, 3}));
+}
+
+TEST(JsonElementNonArrayOperatorAccessTest, NullStandard)
+{
+    json j_nonarray(json::value_t::null);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_NO_THROW(j_nonarray[0]);
+    EXPECT_THROW_MSG(j_nonarray_const[0], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with null");
+}
+
+// implicit transformation to properly filled array
+TEST(JsonElementNonArrayOperatorAccessTest, NullImplicitFilled)
+{
+    json j_nonarray;
+    j_nonarray[3] = 42;
+    EXPECT_EQ(j_nonarray, json({nullptr, nullptr, nullptr, 42}));
+}
+
+TEST(JsonElementNonArrayOperatorAccessTest, Boolean)
+{
+    json j_nonarray(json::value_t::boolean);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_THROW_MSG(j_nonarray[0], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with boolean");
+    EXPECT_THROW_MSG(j_nonarray_const[0], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with boolean");
+}
+
+TEST(JsonElementNonArrayOperatorAccessTest, String)
+{
+    json j_nonarray(json::value_t::string);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_THROW_MSG(j_nonarray[0], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with string");
+    EXPECT_THROW_MSG(j_nonarray_const[0], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with string");
+}
+
+TEST(JsonElementNonArrayOperatorAccessTest, Object)
+{
+    json j_nonarray(json::value_t::object);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_THROW_MSG(j_nonarray[0], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with object");
+    EXPECT_THROW_MSG(j_nonarray_const[0], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with object");
+}
+
+TEST(JsonElementNonArrayOperatorAccessTest, Integer)
+{
+    json j_nonarray(json::value_t::number_integer);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_THROW_MSG(j_nonarray[0], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with number");
+    EXPECT_THROW_MSG(j_nonarray_const[0], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with number");
+}
+
+TEST(JsonElementNonArrayOperatorAccessTest, Unsigned)
+{
+    json j_nonarray(json::value_t::number_unsigned);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_THROW_MSG(j_nonarray[0], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with number");
+    EXPECT_THROW_MSG(j_nonarray_const[0], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with number");
+}
+
+TEST(JsonElementNonArrayOperatorAccessTest, Float)
+{
+    json j_nonarray(json::value_t::number_float);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_THROW_MSG(j_nonarray[0], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with number");
+    EXPECT_THROW_MSG(j_nonarray_const[0], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with number");
+}
+
+class JsonElementArrayRemoveTest : public ::testing::Test,
+                                   public JsonElementArrayAccessTestBase {};
+
+
+// remove element by index
+TEST_F(JsonElementArrayRemoveTest, Index0)
+{
+    j.erase(0);
+    EXPECT_EQ(j, json({1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}));
+}
+
+TEST_F(JsonElementArrayRemoveTest, Index1)
+{
+    j.erase(1);
+    EXPECT_EQ(j, json({1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}));
+}
+
+TEST_F(JsonElementArrayRemoveTest, Index2)
+{
+    j.erase(2);
+    EXPECT_EQ(j, json({1, 1u, nullptr, "string", 42.23, json::object(), {1, 2, 3}}));
+}
+
+TEST_F(JsonElementArrayRemoveTest, Index3)
+{
+    j.erase(3);
+    EXPECT_EQ(j, json({1, 1u, true, "string", 42.23, json::object(), {1, 2, 3}}));
+}
+
+TEST_F(JsonElementArrayRemoveTest, Index4)
+{
+    j.erase(4);
+    EXPECT_EQ(j, json({1, 1u, true, nullptr, 42.23, json::object(), {1, 2, 3}}));
+}
+
+TEST_F(JsonElementArrayRemoveTest, Index5)
+{
+    j.erase(5);
+    EXPECT_EQ(j, json({1, 1u, true, nullptr, "string", json::object(), {1, 2, 3}}));
+}
+
+TEST_F(JsonElementArrayRemoveTest, Index6)
+{
+    j.erase(6);
+    EXPECT_EQ(j, json({1, 1u, true, nullptr, "string", 42.23, {1, 2, 3}}));
+}
+
+TEST_F(JsonElementArrayRemoveTest, Index7)
+{
+    j.erase(7);
+    EXPECT_EQ(j, json({1, 1u, true, nullptr, "string", 42.23, json::object()}));
+}
+
+TEST_F(JsonElementArrayRemoveTest, Index8)
+{
+    EXPECT_THROW_MSG(j.erase(8), json::out_of_range,
+                     "[json.exception.out_of_range.401] array index 8 is out of range");
+}
+
+// erase(begin())
+TEST_F(JsonElementArrayRemoveTest, Begin)
+{
+    j.erase(j.begin());
+    EXPECT_EQ(j, json({1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}));
+}
+
+TEST_F(JsonElementArrayRemoveTest, BeginConst)
+{
+    j.erase(j.cbegin());
+    EXPECT_EQ(j, json({1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}));
+}
+
+// erase(begin(), end())
+TEST_F(JsonElementArrayRemoveTest, BeginEnd)
+{
+    j.erase(j.begin(), j.end());
+    EXPECT_EQ(j, json::array());
+}
+TEST_F(JsonElementArrayRemoveTest, BeginEndConst)
+{
+    j.erase(j.cbegin(), j.cend());
+    EXPECT_EQ(j, json::array());
+}
+
+// erase(begin(), begin())
+TEST_F(JsonElementArrayRemoveTest, BeginBegin)
+{
+    j.erase(j.begin(), j.begin());
+    EXPECT_EQ(j, json({1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}));
+}
+
+TEST_F(JsonElementArrayRemoveTest, BeginBeginConst)
+{
+    j.erase(j.cbegin(), j.cbegin());
+    EXPECT_EQ(j, json({1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}));
+}
+
+// erase at offset
+TEST_F(JsonElementArrayRemoveTest, Offset)
+{
+    json::iterator it = j.begin() + 4;
+    j.erase(it);
+    EXPECT_EQ(j, json({1, 1u, true, nullptr, 42.23, json::object(), {1, 2, 3}}));
+}
+
+TEST_F(JsonElementArrayRemoveTest, OffsetConst)
+{
+    json::const_iterator it = j.cbegin() + 4;
+    j.erase(it);
+    EXPECT_EQ(j, json({1, 1u, true, nullptr, 42.23, json::object(), {1, 2, 3}}));
+}
+
+// erase subrange
+TEST_F(JsonElementArrayRemoveTest, Subrange)
+{
+    j.erase(j.begin() + 3, j.begin() + 6);
+    EXPECT_EQ(j, json({1, 1u, true, json::object(), {1, 2, 3}}));
+}
+
+TEST_F(JsonElementArrayRemoveTest, SubrangeConst)
+{
+    j.erase(j.cbegin() + 3, j.cbegin() + 6);
+    EXPECT_EQ(j, json({1, 1u, true, json::object(), {1, 2, 3}}));
+}
+
+// different arrays
+TEST_F(JsonElementArrayRemoveTest, Different)
+{
+    json j2 = {"foo", "bar"};
+    EXPECT_THROW_MSG(j.erase(j2.begin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.202] iterator does not fit current value");
+    EXPECT_THROW_MSG(j.erase(j.begin(), j2.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.203] iterators do not fit current value");
+    EXPECT_THROW_MSG(j.erase(j2.begin(), j.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.203] iterators do not fit current value");
+    EXPECT_THROW_MSG(j.erase(j2.begin(), j2.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.203] iterators do not fit current value");
+}
+
+TEST_F(JsonElementArrayRemoveTest, DifferentConst)
+{
+    json j2 = {"foo", "bar"};
+    EXPECT_THROW_MSG(j.erase(j2.cbegin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.202] iterator does not fit current value");
+    EXPECT_THROW_MSG(j.erase(j.cbegin(), j2.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.203] iterators do not fit current value");
+    EXPECT_THROW_MSG(j.erase(j2.cbegin(), j.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.203] iterators do not fit current value");
+    EXPECT_THROW_MSG(j.erase(j2.cbegin(), j2.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.203] iterators do not fit current value");
+}
+
+// remove element by index in non-array type
+TEST(JsonElementNonArrayIndexRemoveTest, Null)
+{
+    json j_nonobject(json::value_t::null);
+    EXPECT_THROW_MSG(j_nonobject.erase(0), json::type_error,
+                     "[json.exception.type_error.307] cannot use erase() with null");
+}
+
+TEST(JsonElementNonArrayIndexRemoveTest, Boolean)
+{
+    json j_nonobject(json::value_t::boolean);
+    EXPECT_THROW_MSG(j_nonobject.erase(0), json::type_error,
+                     "[json.exception.type_error.307] cannot use erase() with boolean");
+}
+
+TEST(JsonElementNonArrayIndexRemoveTest, String)
+{
+    json j_nonobject(json::value_t::string);
+    EXPECT_THROW_MSG(j_nonobject.erase(0), json::type_error,
+                     "[json.exception.type_error.307] cannot use erase() with string");
+}
+
+TEST(JsonElementNonArrayIndexRemoveTest, Object)
+{
+    json j_nonobject(json::value_t::object);
+    EXPECT_THROW_MSG(j_nonobject.erase(0), json::type_error,
+                     "[json.exception.type_error.307] cannot use erase() with object");
+}
+
+TEST(JsonElementNonArrayIndexRemoveTest, Integer)
+{
+    json j_nonobject(json::value_t::number_integer);
+    EXPECT_THROW_MSG(j_nonobject.erase(0), json::type_error,
+                     "[json.exception.type_error.307] cannot use erase() with number");
+}
+
+TEST(JsonElementNonArrayIndexRemoveTest, Unsigned)
+{
+    json j_nonobject(json::value_t::number_unsigned);
+    EXPECT_THROW_MSG(j_nonobject.erase(0), json::type_error,
+                     "[json.exception.type_error.307] cannot use erase() with number");
+}
+
+TEST(JsonElementNonArrayIndexRemoveTest, Float)
+{
+    json j_nonobject(json::value_t::number_float);
+    EXPECT_THROW_MSG(j_nonobject.erase(0), json::type_error,
+                     "[json.exception.type_error.307] cannot use erase() with number");
+}
+
+TEST(JsonElementNonArrayFrontBackAccessTest, Null)
+{
+    json j;
+    EXPECT_THROW_MSG(j.front(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+    EXPECT_THROW_MSG(j.back(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+}
+
+TEST(JsonElementNonArrayFrontBackAccessTest, NullConst)
+{
+    const json j{};
+    EXPECT_THROW_MSG(j.front(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+    EXPECT_THROW_MSG(j.back(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+}
+
+TEST(JsonElementNonArrayFrontBackAccessTest, String)
+{
+    json j = "foo";
+    EXPECT_EQ(j.front(), j);
+    EXPECT_EQ(j.back(), j);
+}
+
+TEST(JsonElementNonArrayFrontBackAccessTest, StringConst)
+{
+    const json j = "bar";
+    EXPECT_EQ(j.front(), j);
+    EXPECT_EQ(j.back(), j);
+}
+
+TEST(JsonElementNonArrayFrontBackAccessTest, Boolean)
+{
+    json j = false;
+    EXPECT_EQ(j.front(), j);
+    EXPECT_EQ(j.back(), j);
+}
+
+TEST(JsonElementNonArrayFrontBackAccessTest, BooleanConst)
+{
+    const json j = true;
+    EXPECT_EQ(j.front(), j);
+    EXPECT_EQ(j.back(), j);
+}
+
+TEST(JsonElementNonArrayFrontBackAccessTest, Integer)
+{
+    json j = 17;
+    EXPECT_EQ(j.front(), j);
+    EXPECT_EQ(j.back(), j);
+}
+
+TEST(JsonElementNonArrayFrontBackAccessTest, IntegerConst)
+{
+    const json j = 17;
+    EXPECT_EQ(j.front(), j);
+    EXPECT_EQ(j.back(), j);
+}
+
+TEST(JsonElementNonArrayFrontBackAccessTest, Unsigned)
+{
+    json j = 17u;
+    EXPECT_EQ(j.front(), j);
+    EXPECT_EQ(j.back(), j);
+}
+
+TEST(JsonElementNonArrayFrontBackAccessTest, UnsignedConst)
+{
+    const json j = 17u;
+    EXPECT_EQ(j.front(), j);
+    EXPECT_EQ(j.back(), j);
+}
+
+TEST(JsonElementNonArrayFrontBackAccessTest, Float)
+{
+    json j = 23.42;
+    EXPECT_EQ(j.front(), j);
+    EXPECT_EQ(j.back(), j);
+}
+
+TEST(JsonElementNonArrayFrontBackAccessTest, FloatConst)
+{
+    const json j = 23.42;
+    EXPECT_EQ(j.front(), j);
+    EXPECT_EQ(j.back(), j);
+}
+
+TEST(JsonElementNonArrayOneValidIteratorRemoveTest, Null)
+{
+    json j;
+    EXPECT_THROW_MSG(j.erase(j.begin()), json::type_error,
+                     "[json.exception.type_error.307] cannot use erase() with null");
+}
+
+TEST(JsonElementNonArrayOneValidIteratorRemoveTest, NullConst)
+{
+    json j;
+    EXPECT_THROW_MSG(j.erase(j.cbegin()), json::type_error,
+                     "[json.exception.type_error.307] cannot use erase() with null");
+}
+
+TEST(JsonElementNonArrayOneValidIteratorRemoveTest, String)
+{
+    json j = "foo";
+    j.erase(j.begin());
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonElementNonArrayOneValidIteratorRemoveTest, StringConst)
+{
+    json j = "bar";
+    j.erase(j.cbegin());
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonElementNonArrayOneValidIteratorRemoveTest, Boolean)
+{
+    json j = false;
+    j.erase(j.begin());
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonElementNonArrayOneValidIteratorRemoveTest, BooleanConst)
+{
+    json j = true;
+    j.erase(j.cbegin());
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonElementNonArrayOneValidIteratorRemoveTest, Integer)
+{
+    json j = 17;
+    j.erase(j.begin());
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonElementNonArrayOneValidIteratorRemoveTest, IntegerConst)
+{
+    json j = 17;
+    j.erase(j.cbegin());
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonElementNonArrayOneValidIteratorRemoveTest, Unsigned)
+{
+    json j = 17u;
+    j.erase(j.begin());
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonElementNonArrayOneValidIteratorRemoveTest, UnsignedConst)
+{
+    json j = 17u;
+    j.erase(j.cbegin());
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonElementNonArrayOneValidIteratorRemoveTest, Float)
+{
+    json j = 23.42;
+    j.erase(j.begin());
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonElementNonArrayOneValidIteratorRemoveTest, FloatConst)
+{
+    json j = 23.42;
+    j.erase(j.cbegin());
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonElementNonArrayOneInvalidIteratorRemoveTest, String)
+{
+    json j = "foo";
+    EXPECT_THROW_MSG(j.erase(j.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.205] iterator out of range");
+}
+
+TEST(JsonElementNonArrayOneInvalidIteratorRemoveTest, StringConst)
+{
+    json j = "bar";
+    EXPECT_THROW_MSG(j.erase(j.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.205] iterator out of range");
+}
+
+TEST(JsonElementNonArrayOneInvalidIteratorRemoveTest, Boolean)
+{
+    json j = false;
+    EXPECT_THROW_MSG(j.erase(j.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.205] iterator out of range");
+}
+
+TEST(JsonElementNonArrayOneInvalidIteratorRemoveTest, BooleanConst)
+{
+    json j = true;
+    EXPECT_THROW_MSG(j.erase(j.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.205] iterator out of range");
+}
+
+TEST(JsonElementNonArrayOneInvalidIteratorRemoveTest, Integer)
+{
+    json j = 17;
+    EXPECT_THROW_MSG(j.erase(j.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.205] iterator out of range");
+}
+
+TEST(JsonElementNonArrayOneInvalidIteratorRemoveTest, IntegerConst)
+{
+    json j = 17;
+    EXPECT_THROW_MSG(j.erase(j.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.205] iterator out of range");
+}
+
+TEST(JsonElementNonArrayOneInvalidIteratorRemoveTest, Unsigned)
+{
+    json j = 17u;
+    EXPECT_THROW_MSG(j.erase(j.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.205] iterator out of range");
+}
+
+TEST(JsonElementNonArrayOneInvalidIteratorRemoveTest, UnsignedConst)
+{
+    json j = 17u;
+    EXPECT_THROW_MSG(j.erase(j.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.205] iterator out of range");
+}
+
+TEST(JsonElementNonArrayOneInvalidIteratorRemoveTest, Float)
+{
+    json j = 23.42;
+    EXPECT_THROW_MSG(j.erase(j.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.205] iterator out of range");
+}
+
+TEST(JsonElementNonArrayOneInvalidIteratorRemoveTest, FloatConst)
+{
+    json j = 23.42;
+    EXPECT_THROW_MSG(j.erase(j.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.205] iterator out of range");
+}
+
+TEST(JsonElementNonArrayTwoValidIteratorRemoveTest, Null)
+{
+    json j;
+    EXPECT_THROW_MSG(j.erase(j.begin(), j.end()), json::type_error,
+                     "[json.exception.type_error.307] cannot use erase() with null");
+}
+
+TEST(JsonElementNonArrayTwoValidIteratorRemoveTest, NullConst)
+{
+    json j;
+    EXPECT_THROW_MSG(j.erase(j.cbegin(), j.cend()), json::type_error,
+                     "[json.exception.type_error.307] cannot use erase() with null");
+}
+
+TEST(JsonElementNonArrayTwoValidIteratorRemoveTest, String)
+{
+    json j = "foo";
+    j.erase(j.begin(), j.end());
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonElementNonArrayTwoValidIteratorRemoveTest, StringConst)
+{
+    json j = "bar";
+    j.erase(j.cbegin(), j.cend());
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonElementNonArrayTwoValidIteratorRemoveTest, Boolean)
+{
+    json j = false;
+    j.erase(j.begin(), j.end());
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonElementNonArrayTwoValidIteratorRemoveTest, BooleanConst)
+{
+    json j = true;
+    j.erase(j.cbegin(), j.cend());
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonElementNonArrayTwoValidIteratorRemoveTest, Integer)
+{
+    json j = 17;
+    j.erase(j.begin(), j.end());
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonElementNonArrayTwoValidIteratorRemoveTest, IntegerConst)
+{
+    json j = 17;
+    j.erase(j.cbegin(), j.cend());
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonElementNonArrayTwoValidIteratorRemoveTest, Unsigned)
+{
+    json j = 17u;
+    j.erase(j.begin(), j.end());
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonElementNonArrayTwoValidIteratorRemoveTest, UnsignedConst)
+{
+    json j = 17u;
+    j.erase(j.cbegin(), j.cend());
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonElementNonArrayTwoValidIteratorRemoveTest, Float)
+{
+    json j = 23.42;
+    j.erase(j.begin(), j.end());
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonElementNonArrayTwoValidIteratorRemoveTest, FloatConst)
+{
+    json j = 23.42;
+    j.erase(j.cbegin(), j.cend());
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonElementNonArrayTwoInvalidIteratorRemoveTest, String)
+{
+    json j = "foo";
+    EXPECT_THROW_MSG(j.erase(j.end(), j.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+    EXPECT_THROW_MSG(j.erase(j.begin(), j.begin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+}
+
+TEST(JsonElementNonArrayTwoInvalidIteratorRemoveTest, StringConst)
+{
+    json j = "bar";
+    EXPECT_THROW_MSG(j.erase(j.cend(), j.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+    EXPECT_THROW_MSG(j.erase(j.cbegin(), j.cbegin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+}
+
+TEST(JsonElementNonArrayTwoInvalidIteratorRemoveTest, Boolean)
+{
+    json j = false;
+    EXPECT_THROW_MSG(j.erase(j.end(), j.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+    EXPECT_THROW_MSG(j.erase(j.begin(), j.begin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+}
+
+TEST(JsonElementNonArrayTwoInvalidIteratorRemoveTest, BooleanConst)
+{
+    json j = true;
+    EXPECT_THROW_MSG(j.erase(j.cend(), j.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+    EXPECT_THROW_MSG(j.erase(j.cbegin(), j.cbegin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+}
+
+TEST(JsonElementNonArrayTwoInvalidIteratorRemoveTest, Integer)
+{
+    json j = 17;
+    EXPECT_THROW_MSG(j.erase(j.end(), j.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+    EXPECT_THROW_MSG(j.erase(j.begin(), j.begin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+}
+
+TEST(JsonElementNonArrayTwoInvalidIteratorRemoveTest, IntegerConst)
+{
+    json j = 17;
+    EXPECT_THROW_MSG(j.erase(j.cend(), j.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+    EXPECT_THROW_MSG(j.erase(j.cbegin(), j.cbegin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+}
+
+TEST(JsonElementNonArrayTwoInvalidIteratorRemoveTest, Unsigned)
+{
+    json j = 17u;
+    EXPECT_THROW_MSG(j.erase(j.end(), j.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+    EXPECT_THROW_MSG(j.erase(j.begin(), j.begin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+}
+
+TEST(JsonElementNonArrayTwoInvalidIteratorRemoveTest, UnsignedConst)
+{
+    json j = 17u;
+    EXPECT_THROW_MSG(j.erase(j.cend(), j.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+    EXPECT_THROW_MSG(j.erase(j.cbegin(), j.cbegin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+}
+
+TEST(JsonElementNonArrayTwoInvalidIteratorRemoveTest, Float)
+{
+    json j = 23.42;
+    EXPECT_THROW_MSG(j.erase(j.end(), j.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+    EXPECT_THROW_MSG(j.erase(j.begin(), j.begin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+}
+
+TEST(JsonElementNonArrayTwoInvalidIteratorRemoveTest, FloatConst)
+{
+    json j = 23.42;
+    EXPECT_THROW_MSG(j.erase(j.cend(), j.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+    EXPECT_THROW_MSG(j.erase(j.cbegin(), j.cbegin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.204] iterators out of range");
+}
diff --git a/wpiutil/src/test/native/cpp/json/unit-element_access2.cpp b/wpiutil/src/test/native/cpp/json/unit-element_access2.cpp
new file mode 100644
index 0000000..4b64123
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-element_access2.cpp
@@ -0,0 +1,923 @@
+/*----------------------------------------------------------------------------*/
+/* Modifications Copyright (c) FIRST 2017. All Rights Reserved.               */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include "unit-json.h"
+using wpi::json;
+
+class JsonElementObjectAccessTestBase {
+ public:
+    JsonElementObjectAccessTestBase() : j_const(j) {}
+
+ protected:
+    json j = {{"integer", 1}, {"unsigned", 1u}, {"floating", 42.23}, {"null", nullptr}, {"string", "hello world"}, {"boolean", true}, {"object", json::object()}, {"array", {1, 2, 3}}};
+    const json j_const;
+};
+
+class JsonElementObjectAccessTest : public ::testing::Test,
+                                    public JsonElementObjectAccessTestBase {};
+
+TEST_F(JsonElementObjectAccessTest, AtWithinBounds)
+{
+    EXPECT_EQ(j.at("integer"), json(1));
+    EXPECT_EQ(j.at("unsigned"), json(1u));
+    EXPECT_EQ(j.at("boolean"), json(true));
+    EXPECT_EQ(j.at("null"), json(nullptr));
+    EXPECT_EQ(j.at("string"), json("hello world"));
+    EXPECT_EQ(j.at("floating"), json(42.23));
+    EXPECT_EQ(j.at("object"), json::object());
+    EXPECT_EQ(j.at("array"), json({1, 2, 3}));
+
+    EXPECT_EQ(j_const.at("integer"), json(1));
+    EXPECT_EQ(j_const.at("unsigned"), json(1u));
+    EXPECT_EQ(j_const.at("boolean"), json(true));
+    EXPECT_EQ(j_const.at("null"), json(nullptr));
+    EXPECT_EQ(j_const.at("string"), json("hello world"));
+    EXPECT_EQ(j_const.at("floating"), json(42.23));
+    EXPECT_EQ(j_const.at("object"), json::object());
+    EXPECT_EQ(j_const.at("array"), json({1, 2, 3}));
+}
+
+TEST_F(JsonElementObjectAccessTest, AtOutsideBounds)
+{
+    EXPECT_THROW_MSG(j.at("foo"), json::out_of_range,
+                     "[json.exception.out_of_range.403] key 'foo' not found");
+    EXPECT_THROW_MSG(j_const.at("foo"), json::out_of_range,
+                     "[json.exception.out_of_range.403] key 'foo' not found");
+}
+
+TEST(JsonElementNonObjectAtAccessTest, Null)
+{
+    json j_nonobject(json::value_t::null);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject.at("foo"), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with null");
+    EXPECT_THROW_MSG(j_nonobject_const.at("foo"), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with null");
+}
+
+TEST(JsonElementNonObjectAtAccessTest, Boolean)
+{
+    json j_nonobject(json::value_t::boolean);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject.at("foo"), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with boolean");
+    EXPECT_THROW_MSG(j_nonobject_const.at("foo"), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with boolean");
+}
+
+TEST(JsonElementNonObjectAtAccessTest, String)
+{
+    json j_nonobject(json::value_t::string);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject.at("foo"), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with string");
+    EXPECT_THROW_MSG(j_nonobject_const.at("foo"), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with string");
+}
+
+TEST(JsonElementNonObjectAtAccessTest, Array)
+{
+    json j_nonobject(json::value_t::array);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject.at("foo"), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with array");
+    EXPECT_THROW_MSG(j_nonobject_const.at("foo"), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with array");
+}
+
+TEST(JsonElementNonObjectAtAccessTest, Integer)
+{
+    json j_nonobject(json::value_t::number_integer);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject.at("foo"), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with number");
+    EXPECT_THROW_MSG(j_nonobject_const.at("foo"), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with number");
+}
+
+TEST(JsonElementNonObjectAtAccessTest, Unsigned)
+{
+    json j_nonobject(json::value_t::number_unsigned);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject.at("foo"), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with number");
+    EXPECT_THROW_MSG(j_nonobject_const.at("foo"), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with number");
+}
+
+TEST(JsonElementNonObjectAtAccessTest, Float)
+{
+    json j_nonobject(json::value_t::number_float);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject.at("foo"), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with number");
+    EXPECT_THROW_MSG(j_nonobject_const.at("foo"), json::type_error,
+                     "[json.exception.type_error.304] cannot use at() with number");
+}
+
+TEST_F(JsonElementObjectAccessTest, KeyValueExist)
+{
+    EXPECT_EQ(j.value("integer", 2), 1);
+    EXPECT_LT(std::fabs(j.value("integer", 1.0) - 1), 0.001);
+    EXPECT_EQ(j.value("unsigned", 2), 1);
+    EXPECT_LT(std::fabs(j.value("unsigned", 1.0) - 1), 0.001);
+    EXPECT_EQ(j.value("null", json(1)), json());
+    EXPECT_EQ(j.value("boolean", false), true);
+    EXPECT_EQ(j.value("string", "bar"), "hello world");
+    EXPECT_EQ(j.value("string", std::string("bar")), "hello world");
+    EXPECT_LT(std::fabs(j.value("floating", 12.34) - 42.23), 0.001);
+    EXPECT_EQ(j.value("floating", 12), 42);
+    EXPECT_EQ(j.value("object", json({{"foo", "bar"}})), json::object());
+    EXPECT_EQ(j.value("array", json({10, 100})), json({1, 2, 3}));
+
+    EXPECT_EQ(j_const.value("integer", 2), 1);
+    EXPECT_LT(std::fabs(j_const.value("integer", 1.0) - 1), 0.001);
+    EXPECT_EQ(j_const.value("unsigned", 2), 1);
+    EXPECT_LT(std::fabs(j_const.value("unsigned", 1.0) - 1), 0.001);
+    EXPECT_EQ(j_const.value("boolean", false), true);
+    EXPECT_EQ(j_const.value("string", "bar"), "hello world");
+    EXPECT_EQ(j_const.value("string", std::string("bar")), "hello world");
+    EXPECT_LT(std::fabs(j_const.value("floating", 12.34) - 42.23), 0.001);
+    EXPECT_EQ(j_const.value("floating", 12), 42);
+    EXPECT_EQ(j_const.value("object", json({{"foo", "bar"}})), json::object());
+    EXPECT_EQ(j_const.value("array", json({10, 100})), json({1, 2, 3}));
+}
+
+TEST_F(JsonElementObjectAccessTest, KeyValueNotExist)
+{
+    EXPECT_EQ(j.value("_", 2), 2);
+    EXPECT_EQ(j.value("_", 2u), 2u);
+    EXPECT_EQ(j.value("_", false), false);
+    EXPECT_EQ(j.value("_", "bar"), "bar");
+    EXPECT_LT(std::fabs(j.value("_", 12.34) - 12.34), 0.001);
+    EXPECT_EQ(j.value("_", json({{"foo", "bar"}})), json({{"foo", "bar"}}));
+    EXPECT_EQ(j.value("_", json({10, 100})), json({10, 100}));
+
+    EXPECT_EQ(j_const.value("_", 2), 2);
+    EXPECT_EQ(j_const.value("_", 2u), 2u);
+    EXPECT_EQ(j_const.value("_", false), false);
+    EXPECT_EQ(j_const.value("_", "bar"), "bar");
+    EXPECT_LT(std::fabs(j_const.value("_", 12.34) - 12.34), 0.001);
+    EXPECT_EQ(j_const.value("_", json({{"foo", "bar"}})), json({{"foo", "bar"}}));
+    EXPECT_EQ(j_const.value("_", json({10, 100})), json({10, 100}));
+}
+
+TEST(JsonElementNonObjectKeyValueAccessTest, Null)
+{
+    json j_nonobject(json::value_t::null);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject.value("foo", 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with null");
+    EXPECT_THROW_MSG(j_nonobject_const.value("foo", 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with null");
+}
+
+TEST(JsonElementNonObjectKeyValueAccessTest, Boolean)
+{
+    json j_nonobject(json::value_t::boolean);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject.value("foo", 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with boolean");
+    EXPECT_THROW_MSG(j_nonobject_const.value("foo", 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with boolean");
+}
+
+TEST(JsonElementNonObjectKeyValueAccessTest, String)
+{
+    json j_nonobject(json::value_t::string);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject.value("foo", 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with string");
+    EXPECT_THROW_MSG(j_nonobject_const.value("foo", 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with string");
+}
+
+TEST(JsonElementNonObjectKeyValueAccessTest, Array)
+{
+    json j_nonobject(json::value_t::array);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject.value("foo", 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with array");
+    EXPECT_THROW_MSG(j_nonobject_const.value("foo", 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with array");
+}
+
+TEST(JsonElementNonObjectKeyValueAccessTest, Integer)
+{
+    json j_nonobject(json::value_t::number_integer);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject.value("foo", 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with number");
+    EXPECT_THROW_MSG(j_nonobject_const.value("foo", 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with number");
+}
+
+TEST(JsonElementNonObjectKeyValueAccessTest, Unsigned)
+{
+    json j_nonobject(json::value_t::number_unsigned);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject.value("foo", 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with number");
+    EXPECT_THROW_MSG(j_nonobject_const.value("foo", 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with number");
+}
+
+TEST(JsonElementNonObjectKeyValueAccessTest, Float)
+{
+    json j_nonobject(json::value_t::number_float);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject.value("foo", 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with number");
+    EXPECT_THROW_MSG(j_nonobject_const.value("foo", 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with number");
+}
+
+TEST_F(JsonElementObjectAccessTest, PointerValueExist)
+{
+    EXPECT_EQ(j.value("/integer"_json_pointer, 2), 1);
+    EXPECT_LT(std::fabs(j.value("/integer"_json_pointer, 1.0) - 1), 0.001);
+    EXPECT_EQ(j.value("/unsigned"_json_pointer, 2), 1);
+    EXPECT_LT(std::fabs(j.value("/unsigned"_json_pointer, 1.0) - 1), 0.001);
+    EXPECT_EQ(j.value("/null"_json_pointer, json(1)), json());
+    EXPECT_EQ(j.value("/boolean"_json_pointer, false), true);
+    EXPECT_EQ(j.value("/string"_json_pointer, "bar"), "hello world");
+    EXPECT_EQ(j.value("/string"_json_pointer, std::string("bar")), "hello world");
+    EXPECT_LT(std::fabs(j.value("/floating"_json_pointer, 12.34) - 42.23), 0.001);
+    EXPECT_EQ(j.value("/floating"_json_pointer, 12), 42);
+    EXPECT_EQ(j.value("/object"_json_pointer, json({{"foo", "bar"}})), json::object());
+    EXPECT_EQ(j.value("/array"_json_pointer, json({10, 100})), json({1, 2, 3}));
+
+    EXPECT_EQ(j_const.value("/integer"_json_pointer, 2), 1);
+    EXPECT_LT(std::fabs(j_const.value("/integer"_json_pointer, 1.0) - 1), 0.001);
+    EXPECT_EQ(j_const.value("/unsigned"_json_pointer, 2), 1);
+    EXPECT_LT(std::fabs(j_const.value("/unsigned"_json_pointer, 1.0) - 1), 0.001);
+    EXPECT_EQ(j_const.value("/boolean"_json_pointer, false), true);
+    EXPECT_EQ(j_const.value("/string"_json_pointer, "bar"), "hello world");
+    EXPECT_EQ(j_const.value("/string"_json_pointer, std::string("bar")), "hello world");
+    EXPECT_LT(std::fabs(j_const.value("/floating"_json_pointer, 12.34) - 42.23), 0.001);
+    EXPECT_EQ(j_const.value("/floating"_json_pointer, 12), 42);
+    EXPECT_EQ(j_const.value("/object"_json_pointer, json({{"foo", "bar"}})), json::object());
+    EXPECT_EQ(j_const.value("/array"_json_pointer, json({10, 100})), json({1, 2, 3}));
+}
+
+TEST(JsonElementNonObjectPointerValueAccessTest, Null)
+{
+    json j_nonobject(json::value_t::null);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject.value("/foo"_json_pointer, 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with null");
+    EXPECT_THROW_MSG(j_nonobject_const.value("/foo"_json_pointer, 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with null");
+}
+
+TEST(JsonElementNonObjectPointerValueAccessTest, Boolean)
+{
+    json j_nonobject(json::value_t::boolean);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject.value("/foo"_json_pointer, 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with boolean");
+    EXPECT_THROW_MSG(j_nonobject_const.value("/foo"_json_pointer, 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with boolean");
+}
+
+TEST(JsonElementNonObjectPointerValueAccessTest, String)
+{
+    json j_nonobject(json::value_t::string);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject.value("/foo"_json_pointer, 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with string");
+    EXPECT_THROW_MSG(j_nonobject_const.value("/foo"_json_pointer, 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with string");
+}
+
+TEST(JsonElementNonObjectPointerValueAccessTest, Array)
+{
+    json j_nonobject(json::value_t::array);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject.value("/foo"_json_pointer, 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with array");
+    EXPECT_THROW_MSG(j_nonobject_const.value("/foo"_json_pointer, 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with array");
+}
+
+TEST(JsonElementNonObjectPointerValueAccessTest, Integer)
+{
+    json j_nonobject(json::value_t::number_integer);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject.value("/foo"_json_pointer, 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with number");
+    EXPECT_THROW_MSG(j_nonobject_const.value("/foo"_json_pointer, 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with number");
+}
+
+TEST(JsonElementNonObjectPointerValueAccessTest, Unsigned)
+{
+    json j_nonobject(json::value_t::number_unsigned);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject.value("/foo"_json_pointer, 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with number");
+    EXPECT_THROW_MSG(j_nonobject_const.value("/foo"_json_pointer, 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with number");
+}
+
+TEST(JsonElementNonObjectPointerValueAccessTest, Float)
+{
+    json j_nonobject(json::value_t::number_float);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject.value("/foo"_json_pointer, 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with number");
+    EXPECT_THROW_MSG(j_nonobject_const.value("/foo"_json_pointer, 1), json::type_error,
+                     "[json.exception.type_error.306] cannot use value() with number");
+}
+#if 0
+TEST_F(JsonElementObjectAccessTest, FrontAndBack)
+{
+    // "array" is the smallest key
+    EXPECT_EQ(j.front(), json({1, 2, 3}));
+    EXPECT_EQ(j_const.front(), json({1, 2, 3}));
+    // "unsigned" is the largest key
+    EXPECT_EQ(j.back(), json(1u));
+    EXPECT_EQ(j_const.back(), json(1u));
+}
+#endif
+TEST_F(JsonElementObjectAccessTest, OperatorWithinBounds)
+{
+    EXPECT_EQ(j["integer"], json(1));
+    EXPECT_EQ(j[json::object_t::key_type("integer")], j["integer"]);
+
+    EXPECT_EQ(j["unsigned"], json(1u));
+    EXPECT_EQ(j[json::object_t::key_type("unsigned")], j["unsigned"]);
+
+    EXPECT_EQ(j["boolean"], json(true));
+    EXPECT_EQ(j[json::object_t::key_type("boolean")], j["boolean"]);
+
+    EXPECT_EQ(j["null"], json(nullptr));
+    EXPECT_EQ(j[json::object_t::key_type("null")], j["null"]);
+
+    EXPECT_EQ(j["string"], json("hello world"));
+    EXPECT_EQ(j[json::object_t::key_type("string")], j["string"]);
+
+    EXPECT_EQ(j["floating"], json(42.23));
+    EXPECT_EQ(j[json::object_t::key_type("floating")], j["floating"]);
+
+    EXPECT_EQ(j["object"], json::object());
+    EXPECT_EQ(j[json::object_t::key_type("object")], j["object"]);
+
+    EXPECT_EQ(j["array"], json({1, 2, 3}));
+    EXPECT_EQ(j[json::object_t::key_type("array")], j["array"]);
+
+    EXPECT_EQ(j_const["integer"], json(1));
+    EXPECT_EQ(j_const[json::object_t::key_type("integer")], j["integer"]);
+
+    EXPECT_EQ(j_const["boolean"], json(true));
+    EXPECT_EQ(j_const[json::object_t::key_type("boolean")], j["boolean"]);
+
+    EXPECT_EQ(j_const["null"], json(nullptr));
+    EXPECT_EQ(j_const[json::object_t::key_type("null")], j["null"]);
+
+    EXPECT_EQ(j_const["string"], json("hello world"));
+    EXPECT_EQ(j_const[json::object_t::key_type("string")], j["string"]);
+
+    EXPECT_EQ(j_const["floating"], json(42.23));
+    EXPECT_EQ(j_const[json::object_t::key_type("floating")], j["floating"]);
+
+    EXPECT_EQ(j_const["object"], json::object());
+    EXPECT_EQ(j_const[json::object_t::key_type("object")], j["object"]);
+
+    EXPECT_EQ(j_const["array"], json({1, 2, 3}));
+    EXPECT_EQ(j_const[json::object_t::key_type("array")], j["array"]);
+}
+
+TEST(JsonElementNonObjectOperatorAccessTest, Null)
+{
+    json j_nonobject(json::value_t::null);
+    json j_nonobject2(json::value_t::null);
+    const json j_const_nonobject(j_nonobject);
+    EXPECT_NO_THROW(j_nonobject["foo"]);
+    EXPECT_NO_THROW(j_nonobject2[json::object_t::key_type("foo")]);
+    EXPECT_THROW_MSG(j_const_nonobject["foo"], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with null");
+    EXPECT_THROW_MSG(j_const_nonobject[json::object_t::key_type("foo")], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with null");
+}
+
+TEST(JsonElementNonObjectOperatorAccessTest, Boolean)
+{
+    json j_nonobject(json::value_t::boolean);
+    const json j_const_nonobject(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject["foo"], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with boolean");
+    EXPECT_THROW_MSG(j_nonobject[json::object_t::key_type("foo")], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with boolean");
+    EXPECT_THROW_MSG(j_const_nonobject["foo"], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with boolean");
+    EXPECT_THROW_MSG(j_const_nonobject[json::object_t::key_type("foo")], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with boolean");
+}
+
+TEST(JsonElementNonObjectOperatorAccessTest, String)
+{
+    json j_nonobject(json::value_t::string);
+    const json j_const_nonobject(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject["foo"], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with string");
+    EXPECT_THROW_MSG(j_nonobject[json::object_t::key_type("foo")], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with string");
+    EXPECT_THROW_MSG(j_const_nonobject["foo"], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with string");
+    EXPECT_THROW_MSG(j_const_nonobject[json::object_t::key_type("foo")], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with string");
+}
+
+TEST(JsonElementNonObjectOperatorAccessTest, Array)
+{
+    json j_nonobject(json::value_t::array);
+    const json j_const_nonobject(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject["foo"], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with array");
+    EXPECT_THROW_MSG(j_nonobject[json::object_t::key_type("foo")], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with array");
+    EXPECT_THROW_MSG(j_const_nonobject["foo"], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with array");
+    EXPECT_THROW_MSG(j_const_nonobject[json::object_t::key_type("foo")], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with array");
+}
+
+TEST(JsonElementNonObjectOperatorAccessTest, Integer)
+{
+    json j_nonobject(json::value_t::number_integer);
+    const json j_const_nonobject(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject["foo"], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with number");
+    EXPECT_THROW_MSG(j_nonobject[json::object_t::key_type("foo")], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with number");
+    EXPECT_THROW_MSG(j_const_nonobject["foo"], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with number");
+    EXPECT_THROW_MSG(j_const_nonobject[json::object_t::key_type("foo")], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with number");
+}
+
+TEST(JsonElementNonObjectOperatorAccessTest, Unsigned)
+{
+    json j_nonobject(json::value_t::number_unsigned);
+    const json j_const_nonobject(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject["foo"], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with number");
+    EXPECT_THROW_MSG(j_nonobject[json::object_t::key_type("foo")], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with number");
+    EXPECT_THROW_MSG(j_const_nonobject["foo"], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with number");
+    EXPECT_THROW_MSG(j_const_nonobject[json::object_t::key_type("foo")], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with number");
+}
+
+TEST(JsonElementNonObjectOperatorAccessTest, Float)
+{
+    json j_nonobject(json::value_t::number_float);
+    const json j_const_nonobject(j_nonobject);
+    EXPECT_THROW_MSG(j_nonobject["foo"], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with number");
+    EXPECT_THROW_MSG(j_nonobject[json::object_t::key_type("foo")], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with number");
+    EXPECT_THROW_MSG(j_const_nonobject["foo"], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with number");
+    EXPECT_THROW_MSG(j_const_nonobject[json::object_t::key_type("foo")], json::type_error,
+                     "[json.exception.type_error.305] cannot use operator[] with number");
+}
+
+class JsonElementObjectRemoveTest : public ::testing::Test,
+                                    public JsonElementObjectAccessTestBase {};
+
+TEST_F(JsonElementObjectRemoveTest, Key)
+{
+    EXPECT_NE(j.find("integer"), j.end());
+    EXPECT_EQ(j.erase("integer"), 1u);
+    EXPECT_EQ(j.find("integer"), j.end());
+    EXPECT_EQ(j.erase("integer"), 0u);
+
+    EXPECT_NE(j.find("unsigned"), j.end());
+    EXPECT_EQ(j.erase("unsigned"), 1u);
+    EXPECT_EQ(j.find("unsigned"), j.end());
+    EXPECT_EQ(j.erase("unsigned"), 0u);
+
+    EXPECT_NE(j.find("boolean"), j.end());
+    EXPECT_EQ(j.erase("boolean"), 1u);
+    EXPECT_EQ(j.find("boolean"), j.end());
+    EXPECT_EQ(j.erase("boolean"), 0u);
+
+    EXPECT_NE(j.find("null"), j.end());
+    EXPECT_EQ(j.erase("null"), 1u);
+    EXPECT_EQ(j.find("null"), j.end());
+    EXPECT_EQ(j.erase("null"), 0u);
+
+    EXPECT_NE(j.find("string"), j.end());
+    EXPECT_EQ(j.erase("string"), 1u);
+    EXPECT_EQ(j.find("string"), j.end());
+    EXPECT_EQ(j.erase("string"), 0u);
+
+    EXPECT_NE(j.find("floating"), j.end());
+    EXPECT_EQ(j.erase("floating"), 1u);
+    EXPECT_EQ(j.find("floating"), j.end());
+    EXPECT_EQ(j.erase("floating"), 0u);
+
+    EXPECT_NE(j.find("object"), j.end());
+    EXPECT_EQ(j.erase("object"), 1u);
+    EXPECT_EQ(j.find("object"), j.end());
+    EXPECT_EQ(j.erase("object"), 0u);
+
+    EXPECT_NE(j.find("array"), j.end());
+    EXPECT_EQ(j.erase("array"), 1u);
+    EXPECT_EQ(j.find("array"), j.end());
+    EXPECT_EQ(j.erase("array"), 0u);
+}
+
+// erase(begin())
+TEST_F(JsonElementObjectRemoveTest, Begin)
+{
+    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+    jobject.erase(jobject.begin());
+    EXPECT_EQ(jobject, json({{"b", 1}, {"c", 17u}}));
+}
+
+TEST_F(JsonElementObjectRemoveTest, BeginConst)
+{
+    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+    jobject.erase(jobject.cbegin());
+    EXPECT_EQ(jobject, json({{"b", 1}, {"c", 17u}}));
+}
+
+// erase(begin(), end())
+TEST_F(JsonElementObjectRemoveTest, BeginEnd)
+{
+    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+#if 0
+    json::iterator it2 = jobject.erase(jobject.begin(), jobject.end());
+    EXPECT_EQ(jobject, json::object());
+    EXPECT_EQ(it2, jobject.end());
+#else
+    EXPECT_THROW(jobject.erase(jobject.begin(), jobject.end()), json::type_error);
+#endif
+}
+
+TEST_F(JsonElementObjectRemoveTest, BeginEndConst)
+{
+    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+#if 0
+    json::const_iterator it2 = jobject.erase(jobject.cbegin(), jobject.cend());
+    EXPECT_EQ(jobject, json::object());
+    EXPECT_EQ(it2, jobject.cend());
+#else
+    EXPECT_THROW(jobject.erase(jobject.cbegin(), jobject.cend()), json::type_error);
+#endif
+}
+
+TEST_F(JsonElementObjectRemoveTest, BeginBegin)
+{
+    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+#if 0
+    json::iterator it2 = jobject.erase(jobject.begin(), jobject.begin());
+    EXPECT_EQ(jobject, json({{"a", "a"}, {"b", 1}, {"c", 17u}}));
+    EXPECT_EQ(*it2, json("a"));
+#else
+    EXPECT_THROW(jobject.erase(jobject.begin(), jobject.end()), json::type_error);
+#endif
+}
+
+TEST_F(JsonElementObjectRemoveTest, BeginBeginConst)
+{
+    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+#if 0
+    json::const_iterator it2 = jobject.erase(jobject.cbegin(), jobject.cbegin());
+    EXPECT_EQ(jobject, json({{"a", "a"}, {"b", 1}, {"c", 17u}}));
+    EXPECT_EQ(*it2, json("a"));
+#else
+    EXPECT_THROW(jobject.erase(jobject.cbegin(), jobject.cbegin()), json::type_error);
+#endif
+}
+
+TEST_F(JsonElementObjectRemoveTest, Offset)
+{
+    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+    json::iterator it = jobject.find("b");
+    jobject.erase(it);
+    EXPECT_EQ(jobject, json({{"a", "a"}, {"c", 17u}}));
+}
+
+TEST_F(JsonElementObjectRemoveTest, OffsetConst)
+{
+    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+    json::const_iterator it = jobject.find("b");
+    jobject.erase(it);
+    EXPECT_EQ(jobject, json({{"a", "a"}, {"c", 17u}}));
+}
+
+TEST_F(JsonElementObjectRemoveTest, Subrange)
+{
+    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
+#if 0
+    json::iterator it2 = jobject.erase(jobject.find("b"), jobject.find("e"));
+    EXPECT_EQ(jobject, json({{"a", "a"}, {"e", true}}));
+    EXPECT_EQ(*it2, json(true));
+#else
+    EXPECT_THROW(jobject.erase(jobject.find("b"), jobject.find("e")), json::type_error);
+#endif
+}
+
+TEST_F(JsonElementObjectRemoveTest, SubrangeConst)
+{
+    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
+#if 0
+    json::const_iterator it2 = jobject.erase(jobject.find("b"), jobject.find("e"));
+    EXPECT_EQ(jobject, json({{"a", "a"}, {"e", true}}));
+    EXPECT_EQ(*it2, json(true));
+#else
+    EXPECT_THROW(jobject.erase(jobject.find("b"), jobject.find("e")), json::type_error);
+#endif
+}
+
+TEST_F(JsonElementObjectRemoveTest, Different)
+{
+    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
+    json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+    EXPECT_THROW_MSG(jobject.erase(jobject2.begin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.202] iterator does not fit current value");
+    EXPECT_THROW_MSG(jobject.erase(jobject.begin(), jobject2.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.203] iterators do not fit current value");
+    EXPECT_THROW_MSG(jobject.erase(jobject2.begin(), jobject.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.203] iterators do not fit current value");
+    EXPECT_THROW_MSG(jobject.erase(jobject2.begin(), jobject2.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.203] iterators do not fit current value");
+}
+
+TEST_F(JsonElementObjectRemoveTest, DifferentConst)
+{
+    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
+    json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+    EXPECT_THROW_MSG(jobject.erase(jobject2.cbegin()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.202] iterator does not fit current value");
+    EXPECT_THROW_MSG(jobject.erase(jobject.cbegin(), jobject2.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.203] iterators do not fit current value");
+    EXPECT_THROW_MSG(jobject.erase(jobject2.cbegin(), jobject.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.203] iterators do not fit current value");
+    EXPECT_THROW_MSG(jobject.erase(jobject2.cbegin(), jobject2.cend()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.203] iterators do not fit current value");
+}
+
+// remove element by key in non-object type
+TEST(JsonElementNonObjectKeyRemoveTest, Null)
+{
+    json j_nonobject(json::value_t::null);
+    EXPECT_THROW_MSG(j_nonobject.erase("foo"), json::type_error,
+                     "[json.exception.type_error.307] cannot use erase() with null");
+}
+
+TEST(JsonElementNonObjectKeyRemoveTest, Boolean)
+{
+    json j_nonobject(json::value_t::boolean);
+    EXPECT_THROW_MSG(j_nonobject.erase("foo"), json::type_error,
+                     "[json.exception.type_error.307] cannot use erase() with boolean");
+}
+
+TEST(JsonElementNonObjectKeyRemoveTest, String)
+{
+    json j_nonobject(json::value_t::string);
+    EXPECT_THROW_MSG(j_nonobject.erase("foo"), json::type_error,
+                     "[json.exception.type_error.307] cannot use erase() with string");
+}
+
+TEST(JsonElementNonObjectKeyRemoveTest, Array)
+{
+    json j_nonobject(json::value_t::array);
+    EXPECT_THROW_MSG(j_nonobject.erase("foo"), json::type_error,
+                     "[json.exception.type_error.307] cannot use erase() with array");
+}
+
+TEST(JsonElementNonObjectKeyRemoveTest, Integer)
+{
+    json j_nonobject(json::value_t::number_integer);
+    EXPECT_THROW_MSG(j_nonobject.erase("foo"), json::type_error,
+                     "[json.exception.type_error.307] cannot use erase() with number");
+}
+
+TEST(JsonElementNonObjectKeyRemoveTest, Float)
+{
+    json j_nonobject(json::value_t::number_float);
+    EXPECT_THROW_MSG(j_nonobject.erase("foo"), json::type_error,
+                     "[json.exception.type_error.307] cannot use erase() with number");
+}
+
+TEST_F(JsonElementObjectAccessTest, FindExist)
+{
+    for (auto key :
+            {"integer", "unsigned", "floating", "null", "string", "boolean", "object", "array"
+            })
+    {
+        EXPECT_NE(j.find(key), j.end());
+        EXPECT_EQ(*j.find(key), j.at(key));
+        EXPECT_NE(j_const.find(key), j_const.end());
+        EXPECT_EQ(*j_const.find(key), j_const.at(key));
+    }
+}
+
+TEST_F(JsonElementObjectAccessTest, FindNotExist)
+{
+    EXPECT_EQ(j.find("foo"), j.end());
+    EXPECT_EQ(j_const.find("foo"), j_const.end());
+}
+
+TEST(JsonElementNonObjectFindAccessTest, Null)
+{
+    json j_nonarray(json::value_t::null);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_EQ(j_nonarray.find("foo"), j_nonarray.end());
+    EXPECT_EQ(j_nonarray_const.find("foo"), j_nonarray_const.end());
+}
+
+TEST(JsonElementNonObjectFindAccessTest, String)
+{
+    json j_nonarray(json::value_t::string);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_EQ(j_nonarray.find("foo"), j_nonarray.end());
+    EXPECT_EQ(j_nonarray_const.find("foo"), j_nonarray_const.end());
+}
+
+TEST(JsonElementNonObjectFindAccessTest, Object)
+{
+    json j_nonarray(json::value_t::object);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_EQ(j_nonarray.find("foo"), j_nonarray.end());
+    EXPECT_EQ(j_nonarray_const.find("foo"), j_nonarray_const.end());
+}
+
+TEST(JsonElementNonObjectFindAccessTest, Array)
+{
+    json j_nonarray(json::value_t::array);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_EQ(j_nonarray.find("foo"), j_nonarray.end());
+    EXPECT_EQ(j_nonarray_const.find("foo"), j_nonarray_const.end());
+}
+
+TEST(JsonElementNonObjectFindAccessTest, Boolean)
+{
+    json j_nonarray(json::value_t::boolean);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_EQ(j_nonarray.find("foo"), j_nonarray.end());
+    EXPECT_EQ(j_nonarray_const.find("foo"), j_nonarray_const.end());
+}
+
+TEST(JsonElementNonObjectFindAccessTest, Integer)
+{
+    json j_nonarray(json::value_t::number_integer);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_EQ(j_nonarray.find("foo"), j_nonarray.end());
+    EXPECT_EQ(j_nonarray_const.find("foo"), j_nonarray_const.end());
+}
+
+TEST(JsonElementNonObjectFindAccessTest, Unsigned)
+{
+    json j_nonarray(json::value_t::number_unsigned);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_EQ(j_nonarray.find("foo"), j_nonarray.end());
+    EXPECT_EQ(j_nonarray_const.find("foo"), j_nonarray_const.end());
+}
+
+TEST(JsonElementNonObjectFindAccessTest, Float)
+{
+    json j_nonarray(json::value_t::number_float);
+    const json j_nonarray_const(j_nonarray);
+    EXPECT_EQ(j_nonarray.find("foo"), j_nonarray.end());
+    EXPECT_EQ(j_nonarray_const.find("foo"), j_nonarray_const.end());
+}
+
+TEST_F(JsonElementObjectAccessTest, CountExist)
+{
+    for (auto key :
+            {"integer", "unsigned", "floating", "null", "string", "boolean", "object", "array"
+            })
+    {
+        EXPECT_EQ(j.count(key), 1u);
+        EXPECT_EQ(j_const.count(key), 1u);
+    }
+}
+
+TEST_F(JsonElementObjectAccessTest, CountNotExist)
+{
+    EXPECT_EQ(j.count("foo"), 0u);
+    EXPECT_EQ(j_const.count("foo"), 0u);
+}
+
+TEST(JsonElementNonObjectCountAccessTest, Null)
+{
+    json j_nonobject(json::value_t::null);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_EQ(j_nonobject.count("foo"), 0u);
+    EXPECT_EQ(j_nonobject_const.count("foo"), 0u);
+}
+
+TEST(JsonElementNonObjectCountAccessTest, String)
+{
+    json j_nonobject(json::value_t::string);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_EQ(j_nonobject.count("foo"), 0u);
+    EXPECT_EQ(j_nonobject_const.count("foo"), 0u);
+}
+
+TEST(JsonElementNonObjectCountAccessTest, Object)
+{
+    json j_nonobject(json::value_t::object);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_EQ(j_nonobject.count("foo"), 0u);
+    EXPECT_EQ(j_nonobject_const.count("foo"), 0u);
+}
+
+TEST(JsonElementNonObjectCountAccessTest, Array)
+{
+    json j_nonobject(json::value_t::array);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_EQ(j_nonobject.count("foo"), 0u);
+    EXPECT_EQ(j_nonobject_const.count("foo"), 0u);
+}
+
+TEST(JsonElementNonObjectCountAccessTest, Boolean)
+{
+    json j_nonobject(json::value_t::boolean);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_EQ(j_nonobject.count("foo"), 0u);
+    EXPECT_EQ(j_nonobject_const.count("foo"), 0u);
+}
+
+TEST(JsonElementNonObjectCountAccessTest, Integer)
+{
+    json j_nonobject(json::value_t::number_integer);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_EQ(j_nonobject.count("foo"), 0u);
+    EXPECT_EQ(j_nonobject_const.count("foo"), 0u);
+}
+
+TEST(JsonElementNonObjectCountAccessTest, Unsigned)
+{
+    json j_nonobject(json::value_t::number_unsigned);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_EQ(j_nonobject.count("foo"), 0u);
+    EXPECT_EQ(j_nonobject_const.count("foo"), 0u);
+}
+
+TEST(JsonElementNonObjectCountAccessTest, Float)
+{
+    json j_nonobject(json::value_t::number_float);
+    const json j_nonobject_const(j_nonobject);
+    EXPECT_EQ(j_nonobject.count("foo"), 0u);
+    EXPECT_EQ(j_nonobject_const.count("foo"), 0u);
+}
+
+TEST_F(JsonElementObjectAccessTest, PointerValueNotExist)
+{
+    EXPECT_EQ(j.value("/not/existing"_json_pointer, 2), 2);
+    EXPECT_EQ(j.value("/not/existing"_json_pointer, 2u), 2u);
+    EXPECT_EQ(j.value("/not/existing"_json_pointer, false), false);
+    EXPECT_EQ(j.value("/not/existing"_json_pointer, "bar"), "bar");
+    EXPECT_LT(std::fabs(j.value("/not/existing"_json_pointer, 12.34) - 12.34), 0.001);
+    EXPECT_EQ(j.value("/not/existing"_json_pointer, json({{"foo", "bar"}})), json({{"foo", "bar"}}));
+    EXPECT_EQ(j.value("/not/existing"_json_pointer, json({10, 100})), json({10, 100}));
+
+    EXPECT_EQ(j_const.value("/not/existing"_json_pointer, 2), 2);
+    EXPECT_EQ(j_const.value("/not/existing"_json_pointer, 2u), 2u);
+    EXPECT_EQ(j_const.value("/not/existing"_json_pointer, false), false);
+    EXPECT_EQ(j_const.value("/not/existing"_json_pointer, "bar"), "bar");
+    EXPECT_LT(std::fabs(j_const.value("/not/existing"_json_pointer, 12.34) - 12.34), 0.001);
+    EXPECT_EQ(j_const.value("/not/existing"_json_pointer, json({{"foo", "bar"}})), json({{"foo", "bar"}}));
+    EXPECT_EQ(j_const.value("/not/existing"_json_pointer, json({10, 100})), json({10, 100}));
+}
diff --git a/wpiutil/src/test/native/cpp/json/unit-inspection.cpp b/wpiutil/src/test/native/cpp/json/unit-inspection.cpp
new file mode 100644
index 0000000..79c63f0
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-inspection.cpp
@@ -0,0 +1,385 @@
+/*----------------------------------------------------------------------------*/
+/* Modifications Copyright (c) FIRST 2017. All Rights Reserved.               */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include "unit-json.h"
+using wpi::json;
+
+TEST(JsonConvTypeCheckTest, Object)
+{
+    json j {{"foo", 1}, {"bar", false}};
+    EXPECT_FALSE(j.is_null());
+    EXPECT_FALSE(j.is_boolean());
+    EXPECT_FALSE(j.is_number());
+    EXPECT_FALSE(j.is_number_integer());
+    EXPECT_FALSE(j.is_number_unsigned());
+    EXPECT_FALSE(j.is_number_float());
+    EXPECT_TRUE(j.is_object());
+    EXPECT_FALSE(j.is_array());
+    EXPECT_FALSE(j.is_string());
+    EXPECT_FALSE(j.is_discarded());
+    EXPECT_FALSE(j.is_primitive());
+    EXPECT_TRUE(j.is_structured());
+}
+
+TEST(JsonConvTypeCheckTest, Array)
+{
+    json j {"foo", 1, 1u, 42.23, false};
+    EXPECT_FALSE(j.is_null());
+    EXPECT_FALSE(j.is_boolean());
+    EXPECT_FALSE(j.is_number());
+    EXPECT_FALSE(j.is_number_integer());
+    EXPECT_FALSE(j.is_number_unsigned());
+    EXPECT_FALSE(j.is_number_float());
+    EXPECT_FALSE(j.is_object());
+    EXPECT_TRUE(j.is_array());
+    EXPECT_FALSE(j.is_string());
+    EXPECT_FALSE(j.is_discarded());
+    EXPECT_FALSE(j.is_primitive());
+    EXPECT_TRUE(j.is_structured());
+}
+
+TEST(JsonConvTypeCheckTest, Null)
+{
+    json j(nullptr);
+    EXPECT_TRUE(j.is_null());
+    EXPECT_FALSE(j.is_boolean());
+    EXPECT_FALSE(j.is_number());
+    EXPECT_FALSE(j.is_number_integer());
+    EXPECT_FALSE(j.is_number_unsigned());
+    EXPECT_FALSE(j.is_number_float());
+    EXPECT_FALSE(j.is_object());
+    EXPECT_FALSE(j.is_array());
+    EXPECT_FALSE(j.is_string());
+    EXPECT_FALSE(j.is_discarded());
+    EXPECT_TRUE(j.is_primitive());
+    EXPECT_FALSE(j.is_structured());
+}
+
+TEST(JsonConvTypeCheckTest, Boolean)
+{
+    json j(true);
+    EXPECT_FALSE(j.is_null());
+    EXPECT_TRUE(j.is_boolean());
+    EXPECT_FALSE(j.is_number());
+    EXPECT_FALSE(j.is_number_integer());
+    EXPECT_FALSE(j.is_number_unsigned());
+    EXPECT_FALSE(j.is_number_float());
+    EXPECT_FALSE(j.is_object());
+    EXPECT_FALSE(j.is_array());
+    EXPECT_FALSE(j.is_string());
+    EXPECT_FALSE(j.is_discarded());
+    EXPECT_TRUE(j.is_primitive());
+    EXPECT_FALSE(j.is_structured());
+}
+
+TEST(JsonConvTypeCheckTest, String)
+{
+    json j("Hello world");
+    EXPECT_FALSE(j.is_null());
+    EXPECT_FALSE(j.is_boolean());
+    EXPECT_FALSE(j.is_number());
+    EXPECT_FALSE(j.is_number_integer());
+    EXPECT_FALSE(j.is_number_unsigned());
+    EXPECT_FALSE(j.is_number_float());
+    EXPECT_FALSE(j.is_object());
+    EXPECT_FALSE(j.is_array());
+    EXPECT_TRUE(j.is_string());
+    EXPECT_FALSE(j.is_discarded());
+    EXPECT_TRUE(j.is_primitive());
+    EXPECT_FALSE(j.is_structured());
+}
+
+TEST(JsonConvTypeCheckTest, Integer)
+{
+    json j(42);
+    EXPECT_FALSE(j.is_null());
+    EXPECT_FALSE(j.is_boolean());
+    EXPECT_TRUE(j.is_number());
+    EXPECT_TRUE(j.is_number_integer());
+    EXPECT_FALSE(j.is_number_unsigned());
+    EXPECT_FALSE(j.is_number_float());
+    EXPECT_FALSE(j.is_object());
+    EXPECT_FALSE(j.is_array());
+    EXPECT_FALSE(j.is_string());
+    EXPECT_FALSE(j.is_discarded());
+    EXPECT_TRUE(j.is_primitive());
+    EXPECT_FALSE(j.is_structured());
+}
+
+TEST(JsonConvTypeCheckTest, Unsigned)
+{
+    json j(42u);
+    EXPECT_FALSE(j.is_null());
+    EXPECT_FALSE(j.is_boolean());
+    EXPECT_TRUE(j.is_number());
+    EXPECT_TRUE(j.is_number_integer());
+    EXPECT_TRUE(j.is_number_unsigned());
+    EXPECT_FALSE(j.is_number_float());
+    EXPECT_FALSE(j.is_object());
+    EXPECT_FALSE(j.is_array());
+    EXPECT_FALSE(j.is_string());
+    EXPECT_FALSE(j.is_discarded());
+    EXPECT_TRUE(j.is_primitive());
+    EXPECT_FALSE(j.is_structured());
+}
+
+TEST(JsonConvTypeCheckTest, Float)
+{
+    json j(42.23);
+    EXPECT_FALSE(j.is_null());
+    EXPECT_FALSE(j.is_boolean());
+    EXPECT_TRUE(j.is_number());
+    EXPECT_FALSE(j.is_number_integer());
+    EXPECT_FALSE(j.is_number_unsigned());
+    EXPECT_TRUE(j.is_number_float());
+    EXPECT_FALSE(j.is_object());
+    EXPECT_FALSE(j.is_array());
+    EXPECT_FALSE(j.is_string());
+    EXPECT_FALSE(j.is_discarded());
+    EXPECT_TRUE(j.is_primitive());
+    EXPECT_FALSE(j.is_structured());
+}
+
+TEST(JsonConvTypeCheckTest, Discarded)
+{
+    json j(json::value_t::discarded);
+    EXPECT_FALSE(j.is_null());
+    EXPECT_FALSE(j.is_boolean());
+    EXPECT_FALSE(j.is_number());
+    EXPECT_FALSE(j.is_number_integer());
+    EXPECT_FALSE(j.is_number_unsigned());
+    EXPECT_FALSE(j.is_number_float());
+    EXPECT_FALSE(j.is_object());
+    EXPECT_FALSE(j.is_array());
+    EXPECT_FALSE(j.is_string());
+    EXPECT_TRUE(j.is_discarded());
+    EXPECT_FALSE(j.is_primitive());
+    EXPECT_FALSE(j.is_structured());
+}
+
+class JsonConvSerializationTest : public ::testing::Test {
+ protected:
+    json j {{"object", json::object()}, {"array", {1, 2, 3, 4}}, {"number", 42}, {"boolean", false}, {"null", nullptr}, {"string", "Hello world"} };
+};
+#if 0
+// no indent / indent=-1
+TEST_F(JsonConvSerializationTest, NoIndent)
+{
+    EXPECT_EQ(j.dump(),
+          "{\"array\":[1,2,3,4],\"boolean\":false,\"null\":null,\"number\":42,\"object\":{},\"string\":\"Hello world\"}");
+
+    EXPECT_EQ(j.dump(), j.dump(-1));
+}
+
+// indent=0
+TEST_F(JsonConvSerializationTest, Indent0)
+{
+    EXPECT_EQ(j.dump(0),
+          "{\n\"array\": [\n1,\n2,\n3,\n4\n],\n\"boolean\": false,\n\"null\": null,\n\"number\": 42,\n\"object\": {},\n\"string\": \"Hello world\"\n}");
+}
+
+// indent=1, space='\t'
+TEST_F(JsonConvSerializationTest, Indent1)
+{
+    EXPECT_EQ(j.dump(1, '\t'),
+          "{\n\t\"array\": [\n\t\t1,\n\t\t2,\n\t\t3,\n\t\t4\n\t],\n\t\"boolean\": false,\n\t\"null\": null,\n\t\"number\": 42,\n\t\"object\": {},\n\t\"string\": \"Hello world\"\n}");
+}
+
+// indent=4
+TEST_F(JsonConvSerializationTest, Indent4)
+{
+    EXPECT_EQ(j.dump(4),
+          "{\n    \"array\": [\n        1,\n        2,\n        3,\n        4\n    ],\n    \"boolean\": false,\n    \"null\": null,\n    \"number\": 42,\n    \"object\": {},\n    \"string\": \"Hello world\"\n}");
+}
+#endif
+// indent=x
+TEST_F(JsonConvSerializationTest, IndentX)
+{
+    EXPECT_EQ(j.dump().size(), 94u);
+    EXPECT_EQ(j.dump(1).size(), 127u);
+    EXPECT_EQ(j.dump(2).size(), 142u);
+    EXPECT_EQ(j.dump(512).size(), 7792u);
+}
+
+// dump and floating-point numbers
+TEST_F(JsonConvSerializationTest, Float)
+{
+    auto s = json(42.23).dump();
+    EXPECT_NE(s.find("42.23"), std::string::npos);
+}
+
+// dump and small floating-point numbers
+TEST_F(JsonConvSerializationTest, SmallFloat)
+{
+    auto s = json(1.23456e-78).dump();
+    EXPECT_NE(s.find("1.23456e-78"), std::string::npos);
+}
+
+// dump and non-ASCII characters
+TEST_F(JsonConvSerializationTest, NonAscii)
+{
+    EXPECT_EQ(json("ä").dump(), "\"ä\"");
+    EXPECT_EQ(json("Ö").dump(), "\"Ö\"");
+    EXPECT_EQ(json("❤️").dump(), "\"❤️\"");
+}
+
+// serialization of discarded element
+TEST_F(JsonConvSerializationTest, Discarded)
+{
+    json j_discarded(json::value_t::discarded);
+    EXPECT_EQ(j_discarded.dump(), "<discarded>");
+}
+
+TEST(JsonConvRoundTripTest, Case)
+{
+    for (const auto& s :
+{"3.141592653589793", "1000000000000000010E5"
+})
+    {
+        SCOPED_TRACE(s);
+        json j1 = json::parse(s);
+        std::string s1 = j1.dump();
+        json j2 = json::parse(s1);
+        std::string s2 = j2.dump();
+        EXPECT_EQ(s1, s2);
+    }
+}
+
+// return the type of the object (explicit)
+TEST(JsonConvTypeExplicitTest, Null)
+{
+    json j = nullptr;
+    EXPECT_EQ(j.type(), json::value_t::null);
+}
+
+TEST(JsonConvTypeExplicitTest, Object)
+{
+    json j = {{"foo", "bar"}};
+    EXPECT_EQ(j.type(), json::value_t::object);
+}
+
+TEST(JsonConvTypeExplicitTest, Array)
+{
+    json j = {1, 2, 3, 4};
+    EXPECT_EQ(j.type(), json::value_t::array);
+}
+
+TEST(JsonConvTypeExplicitTest, Boolean)
+{
+    json j = true;
+    EXPECT_EQ(j.type(), json::value_t::boolean);
+}
+
+TEST(JsonConvTypeExplicitTest, String)
+{
+    json j = "Hello world";
+    EXPECT_EQ(j.type(), json::value_t::string);
+}
+
+TEST(JsonConvTypeExplicitTest, Integer)
+{
+    json j = 23;
+    EXPECT_EQ(j.type(), json::value_t::number_integer);
+}
+
+TEST(JsonConvTypeExplicitTest, Unsigned)
+{
+    json j = 23u;
+    EXPECT_EQ(j.type(), json::value_t::number_unsigned);
+}
+
+TEST(JsonConvTypeExplicitTest, Float)
+{
+    json j = 42.23;
+    EXPECT_EQ(j.type(), json::value_t::number_float);
+}
+
+// return the type of the object (implicit)
+TEST(JsonConvTypeImplicitTest, Null)
+{
+    json j = nullptr;
+    json::value_t t = j;
+    EXPECT_EQ(t, j.type());
+}
+
+TEST(JsonConvTypeImplicitTest, Object)
+{
+    json j = {{"foo", "bar"}};
+    json::value_t t = j;
+    EXPECT_EQ(t, j.type());
+}
+
+TEST(JsonConvTypeImplicitTest, Array)
+{
+    json j = {1, 2, 3, 4};
+    json::value_t t = j;
+    EXPECT_EQ(t, j.type());
+}
+
+TEST(JsonConvTypeImplicitTest, Boolean)
+{
+    json j = true;
+    json::value_t t = j;
+    EXPECT_EQ(t, j.type());
+}
+
+TEST(JsonConvTypeImplicitTest, String)
+{
+    json j = "Hello world";
+    json::value_t t = j;
+    EXPECT_EQ(t, j.type());
+}
+
+TEST(JsonConvTypeImplicitTest, Integer)
+{
+    json j = 23;
+    json::value_t t = j;
+    EXPECT_EQ(t, j.type());
+}
+
+TEST(JsonConvTypeImplicitTest, Unsigned)
+{
+    json j = 23u;
+    json::value_t t = j;
+    EXPECT_EQ(t, j.type());
+}
+
+TEST(JsonConvTypeImplicitTest, Float)
+{
+    json j = 42.23;
+    json::value_t t = j;
+    EXPECT_EQ(t, j.type());
+}
diff --git a/wpiutil/src/test/native/cpp/json/unit-iterators1.cpp b/wpiutil/src/test/native/cpp/json/unit-iterators1.cpp
new file mode 100644
index 0000000..bae3862
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-iterators1.cpp
@@ -0,0 +1,1617 @@
+/*----------------------------------------------------------------------------*/
+/* Modifications Copyright (c) FIRST 2017. All Rights Reserved.               */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include "unit-json.h"
+using wpi::json;
+using wpi::JsonTest;
+
+TEST(JsonIteratorBasicTest, Uninitialized)
+{
+    json::iterator it;
+    EXPECT_EQ(JsonTest::GetObject(it), nullptr);
+
+    json::const_iterator cit;
+    EXPECT_EQ(JsonTest::GetObject(cit), nullptr);
+}
+
+class JsonIteratorBooleanTest : public ::testing::Test {
+ public:
+    JsonIteratorBooleanTest() : j_const(j) {}
+
+ protected:
+    json j = true;
+    json j_const;
+};
+
+TEST_F(JsonIteratorBooleanTest, BeginEnd)
+{
+    json::iterator it = j.begin();
+    EXPECT_NE(it, j.end());
+    EXPECT_EQ(*it, j);
+
+    it++;
+    EXPECT_NE(it, j.begin());
+    EXPECT_EQ(it, j.end());
+
+    it--;
+    EXPECT_EQ(it, j.begin());
+    EXPECT_NE(it, j.end());
+    EXPECT_EQ(*it, j);
+
+    ++it;
+    EXPECT_NE(it, j.begin());
+    EXPECT_EQ(it, j.end());
+
+    --it;
+    EXPECT_EQ(it, j.begin());
+    EXPECT_NE(it, j.end());
+    EXPECT_EQ(*it, j);
+}
+
+TEST_F(JsonIteratorBooleanTest, ConstBeginEnd)
+{
+    json::const_iterator it = j_const.begin();
+    EXPECT_NE(it, j_const.end());
+    EXPECT_EQ(*it, j_const);
+
+    it++;
+    EXPECT_NE(it, j_const.begin());
+    EXPECT_EQ(it, j_const.end());
+
+    it--;
+    EXPECT_EQ(it, j_const.begin());
+    EXPECT_NE(it, j_const.end());
+    EXPECT_EQ(*it, j_const);
+
+    ++it;
+    EXPECT_NE(it, j_const.begin());
+    EXPECT_EQ(it, j_const.end());
+
+    --it;
+    EXPECT_EQ(it, j_const.begin());
+    EXPECT_NE(it, j_const.end());
+    EXPECT_EQ(*it, j_const);
+}
+
+TEST_F(JsonIteratorBooleanTest, CBeginEnd)
+{
+    json::const_iterator it = j.cbegin();
+    EXPECT_NE(it, j.cend());
+    EXPECT_EQ(*it, j);
+
+    it++;
+    EXPECT_NE(it, j.cbegin());
+    EXPECT_EQ(it, j.cend());
+
+    it--;
+    EXPECT_EQ(it, j.cbegin());
+    EXPECT_NE(it, j.cend());
+    EXPECT_EQ(*it, j);
+
+    ++it;
+    EXPECT_NE(it, j.cbegin());
+    EXPECT_EQ(it, j.cend());
+
+    --it;
+    EXPECT_EQ(it, j.cbegin());
+    EXPECT_NE(it, j.cend());
+    EXPECT_EQ(*it, j);
+}
+
+TEST_F(JsonIteratorBooleanTest, ConstCBeginEnd)
+{
+    json::const_iterator it = j_const.cbegin();
+    EXPECT_NE(it, j_const.cend());
+    EXPECT_EQ(*it, j_const);
+
+    it++;
+    EXPECT_NE(it, j_const.cbegin());
+    EXPECT_EQ(it, j_const.cend());
+
+    it--;
+    EXPECT_EQ(it, j_const.cbegin());
+    EXPECT_NE(it, j_const.cend());
+    EXPECT_EQ(*it, j_const);
+
+    ++it;
+    EXPECT_NE(it, j_const.cbegin());
+    EXPECT_EQ(it, j_const.cend());
+
+    --it;
+    EXPECT_EQ(it, j_const.cbegin());
+    EXPECT_NE(it, j_const.cend());
+    EXPECT_EQ(*it, j_const);
+}
+#if 0
+TEST_F(JsonIteratorBooleanTest, RBeginEnd)
+{
+    json::reverse_iterator it = j.rbegin();
+    EXPECT_NE(it, j.rend());
+    EXPECT_EQ(*it, j);
+
+    it++;
+    EXPECT_NE(it, j.rbegin());
+    EXPECT_EQ(it, j.rend());
+
+    it--;
+    EXPECT_EQ(it, j.rbegin());
+    EXPECT_NE(it, j.rend());
+    EXPECT_EQ(*it, j);
+
+    ++it;
+    EXPECT_NE(it, j.rbegin());
+    EXPECT_EQ(it, j.rend());
+
+    --it;
+    EXPECT_EQ(it, j.rbegin());
+    EXPECT_NE(it, j.rend());
+    EXPECT_EQ(*it, j);
+}
+
+TEST_F(JsonIteratorBooleanTest, CRBeginEnd)
+{
+    json::const_reverse_iterator it = j.crbegin();
+    EXPECT_NE(it, j.crend());
+    EXPECT_EQ(*it, j);
+
+    it++;
+    EXPECT_NE(it, j.crbegin());
+    EXPECT_EQ(it, j.crend());
+
+    it--;
+    EXPECT_EQ(it, j.crbegin());
+    EXPECT_NE(it, j.crend());
+    EXPECT_EQ(*it, j);
+
+    ++it;
+    EXPECT_NE(it, j.crbegin());
+    EXPECT_EQ(it, j.crend());
+
+    --it;
+    EXPECT_EQ(it, j.crbegin());
+    EXPECT_NE(it, j.crend());
+    EXPECT_EQ(*it, j);
+}
+
+TEST_F(JsonIteratorBooleanTest, ConstCRBeginEnd)
+{
+    json::const_reverse_iterator it = j_const.crbegin();
+    EXPECT_NE(it, j_const.crend());
+    EXPECT_EQ(*it, j_const);
+
+    it++;
+    EXPECT_NE(it, j_const.crbegin());
+    EXPECT_EQ(it, j_const.crend());
+
+    it--;
+    EXPECT_EQ(it, j_const.crbegin());
+    EXPECT_NE(it, j_const.crend());
+    EXPECT_EQ(*it, j_const);
+
+    ++it;
+    EXPECT_NE(it, j_const.crbegin());
+    EXPECT_EQ(it, j_const.crend());
+
+    --it;
+    EXPECT_EQ(it, j_const.crbegin());
+    EXPECT_NE(it, j_const.crend());
+    EXPECT_EQ(*it, j_const);
+}
+#endif
+TEST_F(JsonIteratorBooleanTest, KeyValue)
+{
+    auto it = j.begin();
+    auto cit = j_const.cbegin();
+    EXPECT_THROW_MSG(it.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_EQ(it.value(), json(true));
+    EXPECT_THROW_MSG(cit.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_EQ(cit.value(), json(true));
+#if 0
+    auto rit = j.rend();
+    auto crit = j.crend();
+    EXPECT_THROW_MSG(rit.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_THROW_MSG(rit.value(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+    EXPECT_THROW_MSG(crit.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_THROW_MSG(crit.value(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+#endif
+}
+
+class JsonIteratorStringTest : public ::testing::Test {
+ public:
+    JsonIteratorStringTest() : j_const(j) {}
+
+ protected:
+    json j = "hello world";
+    json j_const;
+};
+
+TEST_F(JsonIteratorStringTest, BeginEnd)
+{
+    json::iterator it = j.begin();
+    EXPECT_NE(it, j.end());
+    EXPECT_EQ(*it, j);
+
+    it++;
+    EXPECT_NE(it, j.begin());
+    EXPECT_EQ(it, j.end());
+
+    it--;
+    EXPECT_EQ(it, j.begin());
+    EXPECT_NE(it, j.end());
+    EXPECT_EQ(*it, j);
+
+    ++it;
+    EXPECT_NE(it, j.begin());
+    EXPECT_EQ(it, j.end());
+
+    --it;
+    EXPECT_EQ(it, j.begin());
+    EXPECT_NE(it, j.end());
+    EXPECT_EQ(*it, j);
+}
+
+TEST_F(JsonIteratorStringTest, ConstBeginEnd)
+{
+    json::const_iterator it = j_const.begin();
+    EXPECT_NE(it, j_const.end());
+    EXPECT_EQ(*it, j_const);
+
+    it++;
+    EXPECT_NE(it, j_const.begin());
+    EXPECT_EQ(it, j_const.end());
+
+    it--;
+    EXPECT_EQ(it, j_const.begin());
+    EXPECT_NE(it, j_const.end());
+    EXPECT_EQ(*it, j_const);
+
+    ++it;
+    EXPECT_NE(it, j_const.begin());
+    EXPECT_EQ(it, j_const.end());
+
+    --it;
+    EXPECT_EQ(it, j_const.begin());
+    EXPECT_NE(it, j_const.end());
+    EXPECT_EQ(*it, j_const);
+}
+
+TEST_F(JsonIteratorStringTest, CBeginEnd)
+{
+    json::const_iterator it = j.cbegin();
+    EXPECT_NE(it, j.cend());
+    EXPECT_EQ(*it, j);
+
+    it++;
+    EXPECT_NE(it, j.cbegin());
+    EXPECT_EQ(it, j.cend());
+
+    it--;
+    EXPECT_EQ(it, j.cbegin());
+    EXPECT_NE(it, j.cend());
+    EXPECT_EQ(*it, j);
+
+    ++it;
+    EXPECT_NE(it, j.cbegin());
+    EXPECT_EQ(it, j.cend());
+
+    --it;
+    EXPECT_EQ(it, j.cbegin());
+    EXPECT_NE(it, j.cend());
+    EXPECT_EQ(*it, j);
+}
+
+TEST_F(JsonIteratorStringTest, ConstCBeginEnd)
+{
+    json::const_iterator it = j_const.cbegin();
+    EXPECT_NE(it, j_const.cend());
+    EXPECT_EQ(*it, j_const);
+
+    it++;
+    EXPECT_NE(it, j_const.cbegin());
+    EXPECT_EQ(it, j_const.cend());
+
+    it--;
+    EXPECT_EQ(it, j_const.cbegin());
+    EXPECT_NE(it, j_const.cend());
+    EXPECT_EQ(*it, j_const);
+
+    ++it;
+    EXPECT_NE(it, j_const.cbegin());
+    EXPECT_EQ(it, j_const.cend());
+
+    --it;
+    EXPECT_EQ(it, j_const.cbegin());
+    EXPECT_NE(it, j_const.cend());
+    EXPECT_EQ(*it, j_const);
+}
+#if 0
+TEST_F(JsonIteratorStringTest, RBeginEnd)
+{
+    json::reverse_iterator it = j.rbegin();
+    EXPECT_NE(it, j.rend());
+    EXPECT_EQ(*it, j);
+
+    it++;
+    EXPECT_NE(it, j.rbegin());
+    EXPECT_EQ(it, j.rend());
+
+    it--;
+    EXPECT_EQ(it, j.rbegin());
+    EXPECT_NE(it, j.rend());
+    EXPECT_EQ(*it, j);
+
+    ++it;
+    EXPECT_NE(it, j.rbegin());
+    EXPECT_EQ(it, j.rend());
+
+    --it;
+    EXPECT_EQ(it, j.rbegin());
+    EXPECT_NE(it, j.rend());
+    EXPECT_EQ(*it, j);
+}
+
+TEST_F(JsonIteratorStringTest, CRBeginEnd)
+{
+    json::const_reverse_iterator it = j.crbegin();
+    EXPECT_NE(it, j.crend());
+    EXPECT_EQ(*it, j);
+
+    it++;
+    EXPECT_NE(it, j.crbegin());
+    EXPECT_EQ(it, j.crend());
+
+    it--;
+    EXPECT_EQ(it, j.crbegin());
+    EXPECT_NE(it, j.crend());
+    EXPECT_EQ(*it, j);
+
+    ++it;
+    EXPECT_NE(it, j.crbegin());
+    EXPECT_EQ(it, j.crend());
+
+    --it;
+    EXPECT_EQ(it, j.crbegin());
+    EXPECT_NE(it, j.crend());
+    EXPECT_EQ(*it, j);
+}
+
+TEST_F(JsonIteratorStringTest, ConstCRBeginEnd)
+{
+    json::const_reverse_iterator it = j_const.crbegin();
+    EXPECT_NE(it, j_const.crend());
+    EXPECT_EQ(*it, j_const);
+
+    it++;
+    EXPECT_NE(it, j_const.crbegin());
+    EXPECT_EQ(it, j_const.crend());
+
+    it--;
+    EXPECT_EQ(it, j_const.crbegin());
+    EXPECT_NE(it, j_const.crend());
+    EXPECT_EQ(*it, j_const);
+
+    ++it;
+    EXPECT_NE(it, j_const.crbegin());
+    EXPECT_EQ(it, j_const.crend());
+
+    --it;
+    EXPECT_EQ(it, j_const.crbegin());
+    EXPECT_NE(it, j_const.crend());
+    EXPECT_EQ(*it, j_const);
+}
+#endif
+TEST_F(JsonIteratorStringTest, KeyValue)
+{
+    auto it = j.begin();
+    auto cit = j_const.cbegin();
+    EXPECT_THROW_MSG(it.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_EQ(it.value(), json("hello world"));
+    EXPECT_THROW_MSG(cit.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_EQ(cit.value(), json("hello world"));
+#if 0
+    auto rit = j.rend();
+    auto crit = j.crend();
+    EXPECT_THROW_MSG(rit.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_THROW_MSG(rit.value(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+    EXPECT_THROW_MSG(crit.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_THROW_MSG(crit.value(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+#endif
+}
+
+class JsonIteratorArrayTest : public ::testing::Test {
+ public:
+    JsonIteratorArrayTest() : j_const(j) {}
+
+ protected:
+    json j = {1, 2, 3};
+    json j_const;
+};
+
+TEST_F(JsonIteratorArrayTest, BeginEnd)
+{
+    json::iterator it_begin = j.begin();
+    json::iterator it_end = j.end();
+
+    auto it = it_begin;
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j[0]);
+
+    it++;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j[1]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j[2]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_EQ(it, it_end);
+}
+
+TEST_F(JsonIteratorArrayTest, ConstBeginEnd)
+{
+    json::const_iterator it_begin = j_const.begin();
+    json::const_iterator it_end = j_const.end();
+
+    auto it = it_begin;
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j_const[0]);
+
+    it++;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j_const[1]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j_const[2]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_EQ(it, it_end);
+}
+
+TEST_F(JsonIteratorArrayTest, CBeginEnd)
+{
+    json::const_iterator it_begin = j.cbegin();
+    json::const_iterator it_end = j.cend();
+
+    auto it = it_begin;
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j[0]);
+
+    it++;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j[1]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j[2]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_EQ(it, it_end);
+}
+
+TEST_F(JsonIteratorArrayTest, ConstCBeginEnd)
+{
+    json::const_iterator it_begin = j_const.cbegin();
+    json::const_iterator it_end = j_const.cend();
+
+    auto it = it_begin;
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j[0]);
+
+    it++;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j[1]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j[2]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_EQ(it, it_end);
+}
+#if 0
+TEST_F(JsonIteratorArrayTest, RBeginEnd)
+{
+    json::reverse_iterator it_begin = j.rbegin();
+    json::reverse_iterator it_end = j.rend();
+
+    auto it = it_begin;
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j[2]);
+
+    it++;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j[1]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j[0]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_EQ(it, it_end);
+}
+
+TEST_F(JsonIteratorArrayTest, CRBeginEnd)
+{
+    json::const_reverse_iterator it_begin = j.crbegin();
+    json::const_reverse_iterator it_end = j.crend();
+
+    auto it = it_begin;
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j[2]);
+
+    it++;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j[1]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j[0]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_EQ(it, it_end);
+}
+
+TEST_F(JsonIteratorArrayTest, ConstCRBeginEnd)
+{
+    json::const_reverse_iterator it_begin = j_const.crbegin();
+    json::const_reverse_iterator it_end = j_const.crend();
+
+    auto it = it_begin;
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j[2]);
+
+    it++;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j[1]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j[0]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_EQ(it, it_end);
+}
+#endif
+TEST_F(JsonIteratorArrayTest, KeyValue)
+{
+    auto it = j.begin();
+    auto cit = j_const.cbegin();
+    EXPECT_THROW_MSG(it.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_EQ(it.value(), json(1));
+    EXPECT_THROW_MSG(cit.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_EQ(cit.value(), json(1));
+}
+
+class JsonIteratorObjectTest : public ::testing::Test {
+ public:
+    JsonIteratorObjectTest() : j_const(j) {}
+
+ protected:
+    json j = {{"A", 1}, {"B", 2}, {"C", 3}};
+    json j_const;
+};
+
+TEST_F(JsonIteratorObjectTest, BeginEnd)
+{
+    json::iterator it_begin = j.begin();
+    json::iterator it_end = j.end();
+
+    auto it = it_begin;
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j["A"]);
+
+    it++;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j["B"]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j["C"]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_EQ(it, it_end);
+}
+
+TEST_F(JsonIteratorObjectTest, ConstBeginEnd)
+{
+    json::const_iterator it_begin = j_const.begin();
+    json::const_iterator it_end = j_const.end();
+
+    auto it = it_begin;
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j_const["A"]);
+
+    it++;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j_const["B"]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j_const["C"]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_EQ(it, it_end);
+}
+
+TEST_F(JsonIteratorObjectTest, CBeginEnd)
+{
+    json::const_iterator it_begin = j.cbegin();
+    json::const_iterator it_end = j.cend();
+
+    auto it = it_begin;
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j["A"]);
+
+    it++;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j["B"]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j["C"]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_EQ(it, it_end);
+}
+
+TEST_F(JsonIteratorObjectTest, ConstCBeginEnd)
+{
+    json::const_iterator it_begin = j_const.cbegin();
+    json::const_iterator it_end = j_const.cend();
+
+    auto it = it_begin;
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j_const["A"]);
+
+    it++;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j_const["B"]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j_const["C"]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_EQ(it, it_end);
+}
+#if 0
+TEST_F(JsonIteratorObjectTest, RBeginEnd)
+{
+    json::reverse_iterator it_begin = j.rbegin();
+    json::reverse_iterator it_end = j.rend();
+
+    auto it = it_begin;
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j["C"]);
+
+    it++;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j["B"]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j["A"]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_EQ(it, it_end);
+}
+
+TEST_F(JsonIteratorObjectTest, CRBeginEnd)
+{
+    json::const_reverse_iterator it_begin = j.crbegin();
+    json::const_reverse_iterator it_end = j.crend();
+
+    auto it = it_begin;
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j["C"]);
+
+    it++;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j["B"]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j["A"]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_EQ(it, it_end);
+}
+
+TEST_F(JsonIteratorObjectTest, ConstCRBeginEnd)
+{
+    json::const_reverse_iterator it_begin = j_const.crbegin();
+    json::const_reverse_iterator it_end = j_const.crend();
+
+    auto it = it_begin;
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j["C"]);
+
+    it++;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j["B"]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_NE(it, it_end);
+    EXPECT_EQ(*it, j["A"]);
+
+    ++it;
+    EXPECT_NE(it, it_begin);
+    EXPECT_EQ(it, it_end);
+}
+#endif
+
+TEST_F(JsonIteratorObjectTest, KeyValue)
+{
+    auto it = j.begin();
+    auto cit = j_const.cbegin();
+    EXPECT_EQ(it.key(), "A");
+    EXPECT_EQ(it.value(), json(1));
+    EXPECT_EQ(cit.key(), "A");
+    EXPECT_EQ(cit.value(), json(1));
+}
+
+class JsonIteratorIntegerTest : public ::testing::Test {
+ public:
+    JsonIteratorIntegerTest() : j_const(j) {}
+
+ protected:
+    json j = 23;
+    json j_const;
+};
+
+TEST_F(JsonIteratorIntegerTest, BeginEnd)
+{
+    json::iterator it = j.begin();
+    EXPECT_NE(it, j.end());
+    EXPECT_EQ(*it, j);
+
+    it++;
+    EXPECT_NE(it, j.begin());
+    EXPECT_EQ(it, j.end());
+
+    it--;
+    EXPECT_EQ(it, j.begin());
+    EXPECT_NE(it, j.end());
+    EXPECT_EQ(*it, j);
+
+    ++it;
+    EXPECT_NE(it, j.begin());
+    EXPECT_EQ(it, j.end());
+
+    --it;
+    EXPECT_EQ(it, j.begin());
+    EXPECT_NE(it, j.end());
+    EXPECT_EQ(*it, j);
+}
+
+TEST_F(JsonIteratorIntegerTest, ConstBeginEnd)
+{
+    json::const_iterator it = j_const.begin();
+    EXPECT_NE(it, j_const.end());
+    EXPECT_EQ(*it, j_const);
+
+    it++;
+    EXPECT_NE(it, j_const.begin());
+    EXPECT_EQ(it, j_const.end());
+
+    it--;
+    EXPECT_EQ(it, j_const.begin());
+    EXPECT_NE(it, j_const.end());
+    EXPECT_EQ(*it, j_const);
+
+    ++it;
+    EXPECT_NE(it, j_const.begin());
+    EXPECT_EQ(it, j_const.end());
+
+    --it;
+    EXPECT_EQ(it, j_const.begin());
+    EXPECT_NE(it, j_const.end());
+    EXPECT_EQ(*it, j_const);
+}
+
+TEST_F(JsonIteratorIntegerTest, CBeginEnd)
+{
+    json::const_iterator it = j.cbegin();
+    EXPECT_NE(it, j.cend());
+    EXPECT_EQ(*it, j);
+
+    it++;
+    EXPECT_NE(it, j.cbegin());
+    EXPECT_EQ(it, j.cend());
+
+    it--;
+    EXPECT_EQ(it, j.cbegin());
+    EXPECT_NE(it, j.cend());
+    EXPECT_EQ(*it, j);
+
+    ++it;
+    EXPECT_NE(it, j.cbegin());
+    EXPECT_EQ(it, j.cend());
+
+    --it;
+    EXPECT_EQ(it, j.cbegin());
+    EXPECT_NE(it, j.cend());
+    EXPECT_EQ(*it, j);
+}
+
+TEST_F(JsonIteratorIntegerTest, ConstCBeginEnd)
+{
+    json::const_iterator it = j_const.cbegin();
+    EXPECT_NE(it, j_const.cend());
+    EXPECT_EQ(*it, j_const);
+
+    it++;
+    EXPECT_NE(it, j_const.cbegin());
+    EXPECT_EQ(it, j_const.cend());
+
+    it--;
+    EXPECT_EQ(it, j_const.cbegin());
+    EXPECT_NE(it, j_const.cend());
+    EXPECT_EQ(*it, j_const);
+
+    ++it;
+    EXPECT_NE(it, j_const.cbegin());
+    EXPECT_EQ(it, j_const.cend());
+
+    --it;
+    EXPECT_EQ(it, j_const.cbegin());
+    EXPECT_NE(it, j_const.cend());
+    EXPECT_EQ(*it, j_const);
+}
+#if 0
+TEST_F(JsonIteratorIntegerTest, RBeginEnd)
+{
+    json::reverse_iterator it = j.rbegin();
+    EXPECT_NE(it, j.rend());
+    EXPECT_EQ(*it, j);
+
+    it++;
+    EXPECT_NE(it, j.rbegin());
+    EXPECT_EQ(it, j.rend());
+
+    it--;
+    EXPECT_EQ(it, j.rbegin());
+    EXPECT_NE(it, j.rend());
+    EXPECT_EQ(*it, j);
+
+    ++it;
+    EXPECT_NE(it, j.rbegin());
+    EXPECT_EQ(it, j.rend());
+
+    --it;
+    EXPECT_EQ(it, j.rbegin());
+    EXPECT_NE(it, j.rend());
+    EXPECT_EQ(*it, j);
+}
+
+TEST_F(JsonIteratorIntegerTest, CRBeginEnd)
+{
+    json::const_reverse_iterator it = j.crbegin();
+    EXPECT_NE(it, j.crend());
+    EXPECT_EQ(*it, j);
+
+    it++;
+    EXPECT_NE(it, j.crbegin());
+    EXPECT_EQ(it, j.crend());
+
+    it--;
+    EXPECT_EQ(it, j.crbegin());
+    EXPECT_NE(it, j.crend());
+    EXPECT_EQ(*it, j);
+
+    ++it;
+    EXPECT_NE(it, j.crbegin());
+    EXPECT_EQ(it, j.crend());
+
+    --it;
+    EXPECT_EQ(it, j.crbegin());
+    EXPECT_NE(it, j.crend());
+    EXPECT_EQ(*it, j);
+}
+
+TEST_F(JsonIteratorIntegerTest, ConstCRBeginEnd)
+{
+    json::const_reverse_iterator it = j_const.crbegin();
+    EXPECT_NE(it, j_const.crend());
+    EXPECT_EQ(*it, j_const);
+
+    it++;
+    EXPECT_NE(it, j_const.crbegin());
+    EXPECT_EQ(it, j_const.crend());
+
+    it--;
+    EXPECT_EQ(it, j_const.crbegin());
+    EXPECT_NE(it, j_const.crend());
+    EXPECT_EQ(*it, j_const);
+
+    ++it;
+    EXPECT_NE(it, j_const.crbegin());
+    EXPECT_EQ(it, j_const.crend());
+
+    --it;
+    EXPECT_EQ(it, j_const.crbegin());
+    EXPECT_NE(it, j_const.crend());
+    EXPECT_EQ(*it, j_const);
+}
+#endif
+TEST_F(JsonIteratorIntegerTest, KeyValue)
+{
+    auto it = j.begin();
+    auto cit = j_const.cbegin();
+    EXPECT_THROW_MSG(it.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_EQ(it.value(), json(23));
+    EXPECT_THROW_MSG(cit.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_EQ(cit.value(), json(23));
+#if 0
+    auto rit = j.rend();
+    auto crit = j.crend();
+    EXPECT_THROW_MSG(rit.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_THROW_MSG(rit.value(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+    EXPECT_THROW_MSG(crit.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_THROW_MSG(crit.value(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+#endif
+}
+
+class JsonIteratorUnsignedTest : public ::testing::Test {
+ public:
+    JsonIteratorUnsignedTest() : j_const(j) {}
+
+ protected:
+    json j = 23u;
+    json j_const;
+};
+
+TEST_F(JsonIteratorUnsignedTest, BeginEnd)
+{
+    json::iterator it = j.begin();
+    EXPECT_NE(it, j.end());
+    EXPECT_EQ(*it, j);
+
+    it++;
+    EXPECT_NE(it, j.begin());
+    EXPECT_EQ(it, j.end());
+
+    it--;
+    EXPECT_EQ(it, j.begin());
+    EXPECT_NE(it, j.end());
+    EXPECT_EQ(*it, j);
+
+    ++it;
+    EXPECT_NE(it, j.begin());
+    EXPECT_EQ(it, j.end());
+
+    --it;
+    EXPECT_EQ(it, j.begin());
+    EXPECT_NE(it, j.end());
+    EXPECT_EQ(*it, j);
+}
+
+TEST_F(JsonIteratorUnsignedTest, ConstBeginEnd)
+{
+    json::const_iterator it = j_const.begin();
+    EXPECT_NE(it, j_const.end());
+    EXPECT_EQ(*it, j_const);
+
+    it++;
+    EXPECT_NE(it, j_const.begin());
+    EXPECT_EQ(it, j_const.end());
+
+    it--;
+    EXPECT_EQ(it, j_const.begin());
+    EXPECT_NE(it, j_const.end());
+    EXPECT_EQ(*it, j_const);
+
+    ++it;
+    EXPECT_NE(it, j_const.begin());
+    EXPECT_EQ(it, j_const.end());
+
+    --it;
+    EXPECT_EQ(it, j_const.begin());
+    EXPECT_NE(it, j_const.end());
+    EXPECT_EQ(*it, j_const);
+}
+
+TEST_F(JsonIteratorUnsignedTest, CBeginEnd)
+{
+    json::const_iterator it = j.cbegin();
+    EXPECT_NE(it, j.cend());
+    EXPECT_EQ(*it, j);
+
+    it++;
+    EXPECT_NE(it, j.cbegin());
+    EXPECT_EQ(it, j.cend());
+
+    it--;
+    EXPECT_EQ(it, j.cbegin());
+    EXPECT_NE(it, j.cend());
+    EXPECT_EQ(*it, j);
+
+    ++it;
+    EXPECT_NE(it, j.cbegin());
+    EXPECT_EQ(it, j.cend());
+
+    --it;
+    EXPECT_EQ(it, j.cbegin());
+    EXPECT_NE(it, j.cend());
+    EXPECT_EQ(*it, j);
+}
+
+TEST_F(JsonIteratorUnsignedTest, ConstCBeginEnd)
+{
+    json::const_iterator it = j_const.cbegin();
+    EXPECT_NE(it, j_const.cend());
+    EXPECT_EQ(*it, j_const);
+
+    it++;
+    EXPECT_NE(it, j_const.cbegin());
+    EXPECT_EQ(it, j_const.cend());
+
+    it--;
+    EXPECT_EQ(it, j_const.cbegin());
+    EXPECT_NE(it, j_const.cend());
+    EXPECT_EQ(*it, j_const);
+
+    ++it;
+    EXPECT_NE(it, j_const.cbegin());
+    EXPECT_EQ(it, j_const.cend());
+
+    --it;
+    EXPECT_EQ(it, j_const.cbegin());
+    EXPECT_NE(it, j_const.cend());
+    EXPECT_EQ(*it, j_const);
+}
+#if 0
+TEST_F(JsonIteratorUnsignedTest, RBeginEnd)
+{
+    json::reverse_iterator it = j.rbegin();
+    EXPECT_NE(it, j.rend());
+    EXPECT_EQ(*it, j);
+
+    it++;
+    EXPECT_NE(it, j.rbegin());
+    EXPECT_EQ(it, j.rend());
+
+    it--;
+    EXPECT_EQ(it, j.rbegin());
+    EXPECT_NE(it, j.rend());
+    EXPECT_EQ(*it, j);
+
+    ++it;
+    EXPECT_NE(it, j.rbegin());
+    EXPECT_EQ(it, j.rend());
+
+    --it;
+    EXPECT_EQ(it, j.rbegin());
+    EXPECT_NE(it, j.rend());
+    EXPECT_EQ(*it, j);
+}
+
+TEST_F(JsonIteratorUnsignedTest, CRBeginEnd)
+{
+    json::const_reverse_iterator it = j.crbegin();
+    EXPECT_NE(it, j.crend());
+    EXPECT_EQ(*it, j);
+
+    it++;
+    EXPECT_NE(it, j.crbegin());
+    EXPECT_EQ(it, j.crend());
+
+    it--;
+    EXPECT_EQ(it, j.crbegin());
+    EXPECT_NE(it, j.crend());
+    EXPECT_EQ(*it, j);
+
+    ++it;
+    EXPECT_NE(it, j.crbegin());
+    EXPECT_EQ(it, j.crend());
+
+    --it;
+    EXPECT_EQ(it, j.crbegin());
+    EXPECT_NE(it, j.crend());
+    EXPECT_EQ(*it, j);
+}
+
+TEST_F(JsonIteratorUnsignedTest, ConstCRBeginEnd)
+{
+    json::const_reverse_iterator it = j_const.crbegin();
+    EXPECT_NE(it, j_const.crend());
+    EXPECT_EQ(*it, j_const);
+
+    it++;
+    EXPECT_NE(it, j_const.crbegin());
+    EXPECT_EQ(it, j_const.crend());
+
+    it--;
+    EXPECT_EQ(it, j_const.crbegin());
+    EXPECT_NE(it, j_const.crend());
+    EXPECT_EQ(*it, j_const);
+
+    ++it;
+    EXPECT_NE(it, j_const.crbegin());
+    EXPECT_EQ(it, j_const.crend());
+
+    --it;
+    EXPECT_EQ(it, j_const.crbegin());
+    EXPECT_NE(it, j_const.crend());
+    EXPECT_EQ(*it, j_const);
+}
+#endif
+TEST_F(JsonIteratorUnsignedTest, KeyValue)
+{
+    auto it = j.begin();
+    auto cit = j_const.cbegin();
+    EXPECT_THROW_MSG(it.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_EQ(it.value(), json(23));
+    EXPECT_THROW_MSG(cit.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_EQ(cit.value(), json(23));
+#if 0
+    auto rit = j.rend();
+    auto crit = j.crend();
+    EXPECT_THROW_MSG(rit.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_THROW_MSG(rit.value(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+    EXPECT_THROW_MSG(crit.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_THROW_MSG(crit.value(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+#endif
+}
+
+class JsonIteratorFloatTest : public ::testing::Test {
+ public:
+    JsonIteratorFloatTest() : j_const(j) {}
+
+ protected:
+    json j = 23.42;
+    json j_const;
+};
+
+TEST_F(JsonIteratorFloatTest, BeginEnd)
+{
+    json::iterator it = j.begin();
+    EXPECT_NE(it, j.end());
+    EXPECT_EQ(*it, j);
+
+    it++;
+    EXPECT_NE(it, j.begin());
+    EXPECT_EQ(it, j.end());
+
+    it--;
+    EXPECT_EQ(it, j.begin());
+    EXPECT_NE(it, j.end());
+    EXPECT_EQ(*it, j);
+
+    ++it;
+    EXPECT_NE(it, j.begin());
+    EXPECT_EQ(it, j.end());
+
+    --it;
+    EXPECT_EQ(it, j.begin());
+    EXPECT_NE(it, j.end());
+    EXPECT_EQ(*it, j);
+}
+
+TEST_F(JsonIteratorFloatTest, ConstBeginEnd)
+{
+    json::const_iterator it = j_const.begin();
+    EXPECT_NE(it, j_const.end());
+    EXPECT_EQ(*it, j_const);
+
+    it++;
+    EXPECT_NE(it, j_const.begin());
+    EXPECT_EQ(it, j_const.end());
+
+    it--;
+    EXPECT_EQ(it, j_const.begin());
+    EXPECT_NE(it, j_const.end());
+    EXPECT_EQ(*it, j_const);
+
+    ++it;
+    EXPECT_NE(it, j_const.begin());
+    EXPECT_EQ(it, j_const.end());
+
+    --it;
+    EXPECT_EQ(it, j_const.begin());
+    EXPECT_NE(it, j_const.end());
+    EXPECT_EQ(*it, j_const);
+}
+
+TEST_F(JsonIteratorFloatTest, CBeginEnd)
+{
+    json::const_iterator it = j.cbegin();
+    EXPECT_NE(it, j.cend());
+    EXPECT_EQ(*it, j);
+
+    it++;
+    EXPECT_NE(it, j.cbegin());
+    EXPECT_EQ(it, j.cend());
+
+    it--;
+    EXPECT_EQ(it, j.cbegin());
+    EXPECT_NE(it, j.cend());
+    EXPECT_EQ(*it, j);
+
+    ++it;
+    EXPECT_NE(it, j.cbegin());
+    EXPECT_EQ(it, j.cend());
+
+    --it;
+    EXPECT_EQ(it, j.cbegin());
+    EXPECT_NE(it, j.cend());
+    EXPECT_EQ(*it, j);
+}
+
+TEST_F(JsonIteratorFloatTest, ConstCBeginEnd)
+{
+    json::const_iterator it = j_const.cbegin();
+    EXPECT_NE(it, j_const.cend());
+    EXPECT_EQ(*it, j_const);
+
+    it++;
+    EXPECT_NE(it, j_const.cbegin());
+    EXPECT_EQ(it, j_const.cend());
+
+    it--;
+    EXPECT_EQ(it, j_const.cbegin());
+    EXPECT_NE(it, j_const.cend());
+    EXPECT_EQ(*it, j_const);
+
+    ++it;
+    EXPECT_NE(it, j_const.cbegin());
+    EXPECT_EQ(it, j_const.cend());
+
+    --it;
+    EXPECT_EQ(it, j_const.cbegin());
+    EXPECT_NE(it, j_const.cend());
+    EXPECT_EQ(*it, j_const);
+}
+#if 0
+TEST_F(JsonIteratorFloatTest, RBeginEnd)
+{
+    json::reverse_iterator it = j.rbegin();
+    EXPECT_NE(it, j.rend());
+    EXPECT_EQ(*it, j);
+
+    it++;
+    EXPECT_NE(it, j.rbegin());
+    EXPECT_EQ(it, j.rend());
+
+    it--;
+    EXPECT_EQ(it, j.rbegin());
+    EXPECT_NE(it, j.rend());
+    EXPECT_EQ(*it, j);
+
+    ++it;
+    EXPECT_NE(it, j.rbegin());
+    EXPECT_EQ(it, j.rend());
+
+    --it;
+    EXPECT_EQ(it, j.rbegin());
+    EXPECT_NE(it, j.rend());
+    EXPECT_EQ(*it, j);
+}
+
+TEST_F(JsonIteratorFloatTest, CRBeginEnd)
+{
+    json::const_reverse_iterator it = j.crbegin();
+    EXPECT_NE(it, j.crend());
+    EXPECT_EQ(*it, j);
+
+    it++;
+    EXPECT_NE(it, j.crbegin());
+    EXPECT_EQ(it, j.crend());
+
+    it--;
+    EXPECT_EQ(it, j.crbegin());
+    EXPECT_NE(it, j.crend());
+    EXPECT_EQ(*it, j);
+
+    ++it;
+    EXPECT_NE(it, j.crbegin());
+    EXPECT_EQ(it, j.crend());
+
+    --it;
+    EXPECT_EQ(it, j.crbegin());
+    EXPECT_NE(it, j.crend());
+    EXPECT_EQ(*it, j);
+}
+
+TEST_F(JsonIteratorFloatTest, ConstCRBeginEnd)
+{
+    json::const_reverse_iterator it = j_const.crbegin();
+    EXPECT_NE(it, j_const.crend());
+    EXPECT_EQ(*it, j_const);
+
+    it++;
+    EXPECT_NE(it, j_const.crbegin());
+    EXPECT_EQ(it, j_const.crend());
+
+    it--;
+    EXPECT_EQ(it, j_const.crbegin());
+    EXPECT_NE(it, j_const.crend());
+    EXPECT_EQ(*it, j_const);
+
+    ++it;
+    EXPECT_NE(it, j_const.crbegin());
+    EXPECT_EQ(it, j_const.crend());
+
+    --it;
+    EXPECT_EQ(it, j_const.crbegin());
+    EXPECT_NE(it, j_const.crend());
+    EXPECT_EQ(*it, j_const);
+}
+#endif
+TEST_F(JsonIteratorFloatTest, KeyValue)
+{
+    auto it = j.begin();
+    auto cit = j_const.cbegin();
+    EXPECT_THROW_MSG(it.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_EQ(it.value(), json(23.42));
+    EXPECT_THROW_MSG(cit.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_EQ(cit.value(), json(23.42));
+#if 0
+    auto rit = j.rend();
+    auto crit = j.crend();
+    EXPECT_THROW_MSG(rit.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_THROW_MSG(rit.value(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+    EXPECT_THROW_MSG(crit.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_THROW_MSG(crit.value(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+#endif
+}
+
+class JsonIteratorNullTest : public ::testing::Test {
+ public:
+    JsonIteratorNullTest() : j_const(j) {}
+
+ protected:
+    json j = nullptr;
+    json j_const;
+};
+
+TEST_F(JsonIteratorNullTest, BeginEnd)
+{
+    json::iterator it = j.begin();
+    EXPECT_EQ(it, j.end());
+}
+
+TEST_F(JsonIteratorNullTest, ConstBeginEnd)
+{
+    json::const_iterator it_begin = j_const.begin();
+    json::const_iterator it_end = j_const.end();
+    EXPECT_EQ(it_begin, it_end);
+}
+
+TEST_F(JsonIteratorNullTest, CBeginEnd)
+{
+    json::const_iterator it_begin = j.cbegin();
+    json::const_iterator it_end = j.cend();
+    EXPECT_EQ(it_begin, it_end);
+}
+
+TEST_F(JsonIteratorNullTest, ConstCBeginEnd)
+{
+    json::const_iterator it_begin = j_const.cbegin();
+    json::const_iterator it_end = j_const.cend();
+    EXPECT_EQ(it_begin, it_end);
+}
+#if 0
+TEST_F(JsonIteratorNullTest, RBeginEnd)
+{
+    json::reverse_iterator it = j.rbegin();
+    EXPECT_EQ(it, j.rend());
+}
+
+TEST_F(JsonIteratorNullTest, CRBeginEnd)
+{
+    json::const_reverse_iterator it = j.crbegin();
+    EXPECT_EQ(it, j.crend());
+}
+
+TEST_F(JsonIteratorNullTest, ConstCRBeginEnd)
+{
+    json::const_reverse_iterator it = j_const.crbegin();
+    EXPECT_EQ(it, j_const.crend());
+}
+#endif
+TEST_F(JsonIteratorNullTest, KeyValue)
+{
+    auto it = j.begin();
+    auto cit = j_const.cbegin();
+    EXPECT_THROW_MSG(it.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_THROW_MSG(it.value(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+    EXPECT_THROW_MSG(cit.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_THROW_MSG(cit.value(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+#if 0
+    auto rit = j.rend();
+    auto crit = j.crend();
+    EXPECT_THROW_MSG(rit.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_THROW_MSG(rit.value(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+    EXPECT_THROW_MSG(crit.key(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
+    EXPECT_THROW_MSG(crit.value(), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+#endif
+}
+
+TEST(JsonIteratorConstConversionTest, Boolean)
+{
+    json j = true;
+    json::const_iterator it = j.begin();
+    EXPECT_EQ(it, j.cbegin());
+    it = j.begin();
+    EXPECT_EQ(it, j.cbegin());
+}
+
+TEST(JsonIteratorConstConversionTest, String)
+{
+    json j = "hello world";
+    json::const_iterator it = j.begin();
+    EXPECT_EQ(it, j.cbegin());
+    it = j.begin();
+    EXPECT_EQ(it, j.cbegin());
+}
+
+TEST(JsonIteratorConstConversionTest, Array)
+{
+    json j = {1, 2, 3};
+    json::const_iterator it = j.begin();
+    EXPECT_EQ(it, j.cbegin());
+    it = j.begin();
+    EXPECT_EQ(it, j.cbegin());
+}
+
+TEST(JsonIteratorConstConversionTest, Object)
+{
+    json j = {{"A", 1}, {"B", 2}, {"C", 3}};
+    json::const_iterator it = j.begin();
+    EXPECT_EQ(it, j.cbegin());
+    it = j.begin();
+    EXPECT_EQ(it, j.cbegin());
+}
+
+TEST(JsonIteratorConstConversionTest, Integer)
+{
+    json j = 23;
+    json::const_iterator it = j.begin();
+    EXPECT_EQ(it, j.cbegin());
+    it = j.begin();
+    EXPECT_EQ(it, j.cbegin());
+}
+
+TEST(JsonIteratorConstConversionTest, Unsigned)
+{
+    json j = 23u;
+    json::const_iterator it = j.begin();
+    EXPECT_EQ(it, j.cbegin());
+    it = j.begin();
+    EXPECT_EQ(it, j.cbegin());
+}
+
+TEST(JsonIteratorConstConversionTest, Float)
+{
+    json j = 23.42;
+    json::const_iterator it = j.begin();
+    EXPECT_EQ(it, j.cbegin());
+    it = j.begin();
+    EXPECT_EQ(it, j.cbegin());
+}
+
+TEST(JsonIteratorConstConversionTest, Null)
+{
+    json j = nullptr;
+    json::const_iterator it = j.begin();
+    EXPECT_EQ(it, j.cbegin());
+    it = j.begin();
+    EXPECT_EQ(it, j.cbegin());
+}
diff --git a/wpiutil/src/test/native/cpp/json/unit-iterators2.cpp b/wpiutil/src/test/native/cpp/json/unit-iterators2.cpp
new file mode 100644
index 0000000..69a4dac
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-iterators2.cpp
@@ -0,0 +1,899 @@
+/*----------------------------------------------------------------------------*/
+/* Modifications Copyright (c) FIRST 2017. All Rights Reserved.               */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include "unit-json.h"
+using wpi::json;
+
+TEST(JsonIteratorTest, Comparisons)
+{
+    json j_values = {nullptr, true, 42, 42u, 23.23, {{"one", 1}, {"two", 2}}, {1, 2, 3, 4, 5}, "Hello, world"};
+
+    for (json& j : j_values)
+    {
+        SCOPED_TRACE(j.dump());
+        auto it1 = j.begin();
+        auto it2 = j.begin();
+        auto it3 = j.begin();
+        ++it2;
+        ++it3;
+        ++it3;
+        auto it1_c = j.cbegin();
+        auto it2_c = j.cbegin();
+        auto it3_c = j.cbegin();
+        ++it2_c;
+        ++it3_c;
+        ++it3_c;
+
+        // comparison: equal
+        {
+            EXPECT_TRUE(it1 == it1);
+            EXPECT_FALSE(it1 == it2);
+            EXPECT_FALSE(it1 == it3);
+            EXPECT_FALSE(it2 == it3);
+            EXPECT_TRUE(it1_c == it1_c);
+            EXPECT_FALSE(it1_c == it2_c);
+            EXPECT_FALSE(it1_c == it3_c);
+            EXPECT_FALSE(it2_c == it3_c);
+        }
+
+        // comparison: not equal
+        {
+            // check definition
+            EXPECT_EQ( (it1 != it1), !(it1 == it1) );
+            EXPECT_EQ( (it1 != it2), !(it1 == it2) );
+            EXPECT_EQ( (it1 != it3), !(it1 == it3) );
+            EXPECT_EQ( (it2 != it3), !(it2 == it3) );
+            EXPECT_EQ( (it1_c != it1_c), !(it1_c == it1_c) );
+            EXPECT_EQ( (it1_c != it2_c), !(it1_c == it2_c) );
+            EXPECT_EQ( (it1_c != it3_c), !(it1_c == it3_c) );
+            EXPECT_EQ( (it2_c != it3_c), !(it2_c == it3_c) );
+        }
+
+        // comparison: smaller
+        {
+            if (j.type() == json::value_t::object)
+            {
+                EXPECT_THROW_MSG(it1 < it1, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1 < it2, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it2 < it3, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1 < it3, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c < it1_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c < it2_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it2_c < it3_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c < it3_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+            }
+            else
+            {
+                EXPECT_FALSE(it1 < it1);
+                EXPECT_TRUE(it1 < it2);
+                EXPECT_TRUE(it1 < it3);
+                EXPECT_TRUE(it2 < it3);
+                EXPECT_FALSE(it1_c < it1_c);
+                EXPECT_TRUE(it1_c < it2_c);
+                EXPECT_TRUE(it1_c < it3_c);
+                EXPECT_TRUE(it2_c < it3_c);
+            }
+        }
+
+        // comparison: less than or equal
+        {
+            if (j.type() == json::value_t::object)
+            {
+                EXPECT_THROW_MSG(it1 <= it1, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1 <= it2, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it2 <= it3, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1 <= it3, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c <= it1_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c <= it2_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it2_c <= it3_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c <= it3_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+            }
+            else
+            {
+                // check definition
+                EXPECT_EQ( (it1 <= it1), !(it1 < it1) );
+                EXPECT_EQ( (it1 <= it2), !(it2 < it1) );
+                EXPECT_EQ( (it1 <= it3), !(it3 < it1) );
+                EXPECT_EQ( (it2 <= it3), !(it3 < it2) );
+                EXPECT_EQ( (it1_c <= it1_c), !(it1_c < it1_c) );
+                EXPECT_EQ( (it1_c <= it2_c), !(it2_c < it1_c) );
+                EXPECT_EQ( (it1_c <= it3_c), !(it3_c < it1_c) );
+                EXPECT_EQ( (it2_c <= it3_c), !(it3_c < it2_c) );
+            }
+        }
+
+        // comparison: greater than
+        {
+            if (j.type() == json::value_t::object)
+            {
+                EXPECT_THROW_MSG(it1 > it1, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1 > it2, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it2 > it3, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1 > it3, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c > it1_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c > it2_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it2_c > it3_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c > it3_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+            }
+            else
+            {
+                // check definition
+                EXPECT_EQ( (it1 > it1), (it1 < it1) );
+                EXPECT_EQ( (it1 > it2), (it2 < it1) );
+                EXPECT_EQ( (it1 > it3), (it3 < it1) );
+                EXPECT_EQ( (it2 > it3), (it3 < it2) );
+                EXPECT_EQ( (it1_c > it1_c), (it1_c < it1_c) );
+                EXPECT_EQ( (it1_c > it2_c), (it2_c < it1_c) );
+                EXPECT_EQ( (it1_c > it3_c), (it3_c < it1_c) );
+                EXPECT_EQ( (it2_c > it3_c), (it3_c < it2_c) );
+            }
+        }
+
+        // comparison: greater than or equal
+        {
+            if (j.type() == json::value_t::object)
+            {
+                EXPECT_THROW_MSG(it1 >= it1, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1 >= it2, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it2 >= it3, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1 >= it3, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c >= it1_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c >= it2_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it2_c >= it3_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c >= it3_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+            }
+            else
+            {
+                // check definition
+                EXPECT_EQ( (it1 >= it1), !(it1 < it1) );
+                EXPECT_EQ( (it1 >= it2), !(it1 < it2) );
+                EXPECT_EQ( (it1 >= it3), !(it1 < it3) );
+                EXPECT_EQ( (it2 >= it3), !(it2 < it3) );
+                EXPECT_EQ( (it1_c >= it1_c), !(it1_c < it1_c) );
+                EXPECT_EQ( (it1_c >= it2_c), !(it1_c < it2_c) );
+                EXPECT_EQ( (it1_c >= it3_c), !(it1_c < it3_c) );
+                EXPECT_EQ( (it2_c >= it3_c), !(it2_c < it3_c) );
+            }
+        }
+    }
+
+    // check exceptions if different objects are compared
+    for (auto j : j_values)
+    {
+        for (auto k : j_values)
+        {
+            if (j != k)
+            {
+                EXPECT_THROW_MSG(j.begin() == k.begin(), json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.212] cannot compare iterators of different containers");
+                EXPECT_THROW_MSG(j.cbegin() == k.cbegin(), json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.212] cannot compare iterators of different containers");
+
+                EXPECT_THROW_MSG(j.begin() < k.begin(), json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.212] cannot compare iterators of different containers");
+                EXPECT_THROW_MSG(j.cbegin() < k.cbegin(), json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.212] cannot compare iterators of different containers");
+            }
+        }
+    }
+}
+
+class JsonIteratorArithmeticTest : public ::testing::Test {
+ protected:
+    json j_object = {{"one", 1}, {"two", 2}, {"three", 3}};
+    json j_array = {1, 2, 3, 4, 5, 6};
+    json j_null = nullptr;
+    json j_value = 42;
+};
+
+TEST_F(JsonIteratorArithmeticTest, AddSubObject)
+{
+    {
+        auto it = j_object.begin();
+        EXPECT_THROW_MSG(it += 1, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.cbegin();
+        EXPECT_THROW_MSG(it += 1, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.begin();
+        EXPECT_THROW_MSG(it + 1, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.cbegin();
+        EXPECT_THROW_MSG(it + 1, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.begin();
+        EXPECT_THROW_MSG(1 + it, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.cbegin();
+        EXPECT_THROW_MSG(1 + it, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.begin();
+        EXPECT_THROW_MSG(it -= 1, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.cbegin();
+        EXPECT_THROW_MSG(it -= 1, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.begin();
+        EXPECT_THROW_MSG(it - 1, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.cbegin();
+        EXPECT_THROW_MSG(it - 1, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.begin();
+        EXPECT_THROW_MSG(it - it, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.cbegin();
+        EXPECT_THROW_MSG(it - it, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+}
+
+TEST_F(JsonIteratorArithmeticTest, AddSubArray)
+{
+    auto it = j_array.begin();
+    it += 3;
+    EXPECT_EQ((j_array.begin() + 3), it);
+    EXPECT_EQ(json::iterator(3 + j_array.begin()), it);
+    EXPECT_EQ((it - 3), j_array.begin());
+    EXPECT_EQ((it - j_array.begin()), 3);
+    EXPECT_EQ(*it, json(4));
+    it -= 2;
+    EXPECT_EQ(*it, json(2));
+}
+
+TEST_F(JsonIteratorArithmeticTest, AddSubArrayConst)
+{
+    auto it = j_array.cbegin();
+    it += 3;
+    EXPECT_EQ((j_array.cbegin() + 3), it);
+    EXPECT_EQ(json::const_iterator(3 + j_array.cbegin()), it);
+    EXPECT_EQ((it - 3), j_array.cbegin());
+    EXPECT_EQ((it - j_array.cbegin()), 3);
+    EXPECT_EQ(*it, json(4));
+    it -= 2;
+    EXPECT_EQ(*it, json(2));
+}
+
+TEST_F(JsonIteratorArithmeticTest, AddSubNull)
+{
+    auto it = j_null.begin();
+    it += 3;
+    EXPECT_EQ((j_null.begin() + 3), it);
+    EXPECT_EQ(json::iterator(3 + j_null.begin()), it);
+    EXPECT_EQ((it - 3), j_null.begin());
+    EXPECT_EQ((it - j_null.begin()), 3);
+    EXPECT_NE(it, j_null.end());
+    it -= 3;
+    EXPECT_EQ(it, j_null.end());
+}
+
+TEST_F(JsonIteratorArithmeticTest, AddSubNullConst)
+{
+    auto it = j_null.cbegin();
+    it += 3;
+    EXPECT_EQ((j_null.cbegin() + 3), it);
+    EXPECT_EQ(json::const_iterator(3 + j_null.cbegin()), it);
+    EXPECT_EQ((it - 3), j_null.cbegin());
+    EXPECT_EQ((it - j_null.cbegin()), 3);
+    EXPECT_NE(it, j_null.cend());
+    it -= 3;
+    EXPECT_EQ(it, j_null.cend());
+}
+
+TEST_F(JsonIteratorArithmeticTest, AddSubValue)
+{
+    auto it = j_value.begin();
+    it += 3;
+    EXPECT_EQ((j_value.begin() + 3), it);
+    EXPECT_EQ(json::iterator(3 + j_value.begin()), it);
+    EXPECT_EQ((it - 3), j_value.begin());
+    EXPECT_EQ((it - j_value.begin()), 3);
+    EXPECT_NE(it, j_value.end());
+    it -= 3;
+    EXPECT_EQ(*it, json(42));
+}
+
+TEST_F(JsonIteratorArithmeticTest, AddSubValueConst)
+{
+    auto it = j_value.cbegin();
+    it += 3;
+    EXPECT_EQ((j_value.cbegin() + 3), it);
+    EXPECT_EQ(json::const_iterator(3 + j_value.cbegin()), it);
+    EXPECT_EQ((it - 3), j_value.cbegin());
+    EXPECT_EQ((it - j_value.cbegin()), 3);
+    EXPECT_NE(it, j_value.cend());
+    it -= 3;
+    EXPECT_EQ(*it, json(42));
+}
+
+TEST_F(JsonIteratorArithmeticTest, SubscriptObject)
+{
+    auto it = j_object.begin();
+    EXPECT_THROW_MSG(it[0], json::invalid_iterator,
+                     "[json.exception.invalid_iterator.208] cannot use operator[] for object iterators");
+    EXPECT_THROW_MSG(it[1], json::invalid_iterator,
+                     "[json.exception.invalid_iterator.208] cannot use operator[] for object iterators");
+}
+
+TEST_F(JsonIteratorArithmeticTest, SubscriptObjectConst)
+{
+    auto it = j_object.cbegin();
+    EXPECT_THROW_MSG(it[0], json::invalid_iterator,
+                     "[json.exception.invalid_iterator.208] cannot use operator[] for object iterators");
+    EXPECT_THROW_MSG(it[1], json::invalid_iterator,
+                     "[json.exception.invalid_iterator.208] cannot use operator[] for object iterators");
+}
+
+TEST_F(JsonIteratorArithmeticTest, SubscriptArray)
+{
+    auto it = j_array.begin();
+    EXPECT_EQ(it[0], json(1));
+    EXPECT_EQ(it[1], json(2));
+    EXPECT_EQ(it[2], json(3));
+    EXPECT_EQ(it[3], json(4));
+    EXPECT_EQ(it[4], json(5));
+    EXPECT_EQ(it[5], json(6));
+}
+
+TEST_F(JsonIteratorArithmeticTest, SubscriptArrayConst)
+{
+    auto it = j_array.cbegin();
+    EXPECT_EQ(it[0], json(1));
+    EXPECT_EQ(it[1], json(2));
+    EXPECT_EQ(it[2], json(3));
+    EXPECT_EQ(it[3], json(4));
+    EXPECT_EQ(it[4], json(5));
+    EXPECT_EQ(it[5], json(6));
+}
+
+TEST_F(JsonIteratorArithmeticTest, SubscriptNull)
+{
+    auto it = j_null.begin();
+    EXPECT_THROW_MSG(it[0], json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+    EXPECT_THROW_MSG(it[1], json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+}
+
+TEST_F(JsonIteratorArithmeticTest, SubscriptNullConst)
+{
+    auto it = j_null.cbegin();
+    EXPECT_THROW_MSG(it[0], json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+    EXPECT_THROW_MSG(it[1], json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+}
+
+TEST_F(JsonIteratorArithmeticTest, SubscriptValue)
+{
+    auto it = j_value.begin();
+    EXPECT_EQ(it[0], json(42));
+    EXPECT_THROW_MSG(it[1], json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+}
+
+TEST_F(JsonIteratorArithmeticTest, SubscriptValueConst)
+{
+    auto it = j_value.cbegin();
+    EXPECT_EQ(it[0], json(42));
+    EXPECT_THROW_MSG(it[1], json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+}
+#if 0
+TEST(JsonReverseIteratorTest, Comparisons)
+{
+    json j_values = {nullptr, true, 42, 42u, 23.23, {{"one", 1}, {"two", 2}}, {1, 2, 3, 4, 5}, "Hello, world"};
+
+    for (json& j : j_values)
+    {
+        SCOPED_TRACE(j.dump());
+        auto it1 = j.rbegin();
+        auto it2 = j.rbegin();
+        auto it3 = j.rbegin();
+        ++it2;
+        ++it3;
+        ++it3;
+        auto it1_c = j.crbegin();
+        auto it2_c = j.crbegin();
+        auto it3_c = j.crbegin();
+        ++it2_c;
+        ++it3_c;
+        ++it3_c;
+
+        // comparison: equal
+        {
+            EXPECT_TRUE(it1 == it1);
+            EXPECT_FALSE(it1 == it2);
+            EXPECT_FALSE(it1 == it3);
+            EXPECT_FALSE(it2 == it3);
+            EXPECT_TRUE(it1_c == it1_c);
+            EXPECT_FALSE(it1_c == it2_c);
+            EXPECT_FALSE(it1_c == it3_c);
+            EXPECT_FALSE(it2_c == it3_c);
+        }
+
+        // comparison: not equal
+        {
+            // check definition
+            EXPECT_EQ( (it1 != it1), !(it1 == it1) );
+            EXPECT_EQ( (it1 != it2), !(it1 == it2) );
+            EXPECT_EQ( (it1 != it3), !(it1 == it3) );
+            EXPECT_EQ( (it2 != it3), !(it2 == it3) );
+            EXPECT_EQ( (it1_c != it1_c), !(it1_c == it1_c) );
+            EXPECT_EQ( (it1_c != it2_c), !(it1_c == it2_c) );
+            EXPECT_EQ( (it1_c != it3_c), !(it1_c == it3_c) );
+            EXPECT_EQ( (it2_c != it3_c), !(it2_c == it3_c) );
+        }
+
+        // comparison: smaller
+        {
+            if (j.type() == json::value_t::object)
+            {
+                EXPECT_THROW_MSG(it1 < it1, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1 < it2, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it2 < it3, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1 < it3, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c < it1_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c < it2_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it2_c < it3_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c < it3_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+            }
+            else
+            {
+                EXPECT_FALSE(it1 < it1);
+                EXPECT_TRUE(it1 < it2);
+                EXPECT_TRUE(it1 < it3);
+                EXPECT_TRUE(it2 < it3);
+                EXPECT_FALSE(it1_c < it1_c);
+                EXPECT_TRUE(it1_c < it2_c);
+                EXPECT_TRUE(it1_c < it3_c);
+                EXPECT_TRUE(it2_c < it3_c);
+            }
+        }
+
+        // comparison: less than or equal
+        {
+            if (j.type() == json::value_t::object)
+            {
+                EXPECT_THROW_MSG(it1 <= it1, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1 <= it2, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it2 <= it3, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1 <= it3, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c <= it1_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c <= it2_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it2_c <= it3_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c <= it3_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+            }
+            else
+            {
+                // check definition
+                EXPECT_EQ( (it1 <= it1), !(it1 < it1) );
+                EXPECT_EQ( (it1 <= it2), !(it2 < it1) );
+                EXPECT_EQ( (it1 <= it3), !(it3 < it1) );
+                EXPECT_EQ( (it2 <= it3), !(it3 < it2) );
+                EXPECT_EQ( (it1_c <= it1_c), !(it1_c < it1_c) );
+                EXPECT_EQ( (it1_c <= it2_c), !(it2_c < it1_c) );
+                EXPECT_EQ( (it1_c <= it3_c), !(it3_c < it1_c) );
+                EXPECT_EQ( (it2_c <= it3_c), !(it3_c < it2_c) );
+            }
+        }
+
+        // comparison: greater than
+        {
+            if (j.type() == json::value_t::object)
+            {
+                EXPECT_THROW_MSG(it1 > it1, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1 > it2, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it2 > it3, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1 > it3, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c > it1_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c > it2_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it2_c > it3_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c > it3_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+            }
+            else
+            {
+                // check definition
+                EXPECT_EQ( (it1 > it1), (it1 < it1) );
+                EXPECT_EQ( (it1 > it2), (it2 < it1) );
+                EXPECT_EQ( (it1 > it3), (it3 < it1) );
+                EXPECT_EQ( (it2 > it3), (it3 < it2) );
+                EXPECT_EQ( (it1_c > it1_c), (it1_c < it1_c) );
+                EXPECT_EQ( (it1_c > it2_c), (it2_c < it1_c) );
+                EXPECT_EQ( (it1_c > it3_c), (it3_c < it1_c) );
+                EXPECT_EQ( (it2_c > it3_c), (it3_c < it2_c) );
+            }
+        }
+
+        // comparison: greater than or equal
+        {
+            if (j.type() == json::value_t::object)
+            {
+                EXPECT_THROW_MSG(it1 >= it1, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1 >= it2, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it2 >= it3, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1 >= it3, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c >= it1_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c >= it2_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it2_c >= it3_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+                EXPECT_THROW_MSG(it1_c >= it3_c, json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
+            }
+            else
+            {
+                // check definition
+                EXPECT_EQ( (it1 >= it1), !(it1 < it1) );
+                EXPECT_EQ( (it1 >= it2), !(it1 < it2) );
+                EXPECT_EQ( (it1 >= it3), !(it1 < it3) );
+                EXPECT_EQ( (it2 >= it3), !(it2 < it3) );
+                EXPECT_EQ( (it1_c >= it1_c), !(it1_c < it1_c) );
+                EXPECT_EQ( (it1_c >= it2_c), !(it1_c < it2_c) );
+                EXPECT_EQ( (it1_c >= it3_c), !(it1_c < it3_c) );
+                EXPECT_EQ( (it2_c >= it3_c), !(it2_c < it3_c) );
+            }
+        }
+    }
+
+    // check exceptions if different objects are compared
+    for (auto j : j_values)
+    {
+        for (auto k : j_values)
+        {
+            if (j != k)
+            {
+                EXPECT_THROW_MSG(j.rbegin() == k.rbegin(), json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.212] cannot compare iterators of different containers");
+                EXPECT_THROW_MSG(j.crbegin() == k.crbegin(), json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.212] cannot compare iterators of different containers");
+
+                EXPECT_THROW_MSG(j.rbegin() < k.rbegin(), json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.212] cannot compare iterators of different containers");
+                EXPECT_THROW_MSG(j.crbegin() < k.crbegin(), json::invalid_iterator,
+                                 "[json.exception.invalid_iterator.212] cannot compare iterators of different containers");
+            }
+        }
+    }
+}
+
+class JsonReverseIteratorArithmeticTest : public ::testing::Test {
+ protected:
+    json j_object = {{"one", 1}, {"two", 2}, {"three", 3}};
+    json j_array = {1, 2, 3, 4, 5, 6};
+    json j_null = nullptr;
+    json j_value = 42;
+};
+
+TEST_F(JsonReverseIteratorArithmeticTest, AddSubObject)
+{
+    {
+        auto it = j_object.rbegin();
+        EXPECT_THROW_MSG(it += 1, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.crbegin();
+        EXPECT_THROW_MSG(it += 1, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.rbegin();
+        EXPECT_THROW_MSG(it + 1, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.crbegin();
+        EXPECT_THROW_MSG(it + 1, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.rbegin();
+        EXPECT_THROW_MSG(1 + it, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.crbegin();
+        EXPECT_THROW_MSG(1 + it, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.rbegin();
+        EXPECT_THROW_MSG(it -= 1, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.crbegin();
+        EXPECT_THROW_MSG(it -= 1, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.rbegin();
+        EXPECT_THROW_MSG(it - 1, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.crbegin();
+        EXPECT_THROW_MSG(it - 1, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.rbegin();
+        EXPECT_THROW_MSG(it - it, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+    {
+        auto it = j_object.crbegin();
+        EXPECT_THROW_MSG(it - it, json::invalid_iterator,
+                         "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    }
+}
+
+TEST_F(JsonReverseIteratorArithmeticTest, AddSubArray)
+{
+    auto it = j_array.rbegin();
+    it += 3;
+    EXPECT_EQ((j_array.rbegin() + 3), it);
+    EXPECT_EQ(json::reverse_iterator(3 + j_array.rbegin()), it);
+    EXPECT_EQ((it - 3), j_array.rbegin());
+    EXPECT_EQ((it - j_array.rbegin()), 3);
+    EXPECT_EQ(*it, json(3));
+    it -= 2;
+    EXPECT_EQ(*it, json(5));
+}
+
+TEST_F(JsonReverseIteratorArithmeticTest, AddSubArrayConst)
+{
+    auto it = j_array.crbegin();
+    it += 3;
+    EXPECT_EQ((j_array.crbegin() + 3), it);
+    EXPECT_EQ(json::const_reverse_iterator(3 + j_array.crbegin()), it);
+    EXPECT_EQ((it - 3), j_array.crbegin());
+    EXPECT_EQ((it - j_array.crbegin()), 3);
+    EXPECT_EQ(*it, json(3));
+    it -= 2;
+    EXPECT_EQ(*it, json(5));
+}
+
+TEST_F(JsonReverseIteratorArithmeticTest, AddSubNull)
+{
+    auto it = j_null.rbegin();
+    it += 3;
+    EXPECT_EQ((j_null.rbegin() + 3), it);
+    EXPECT_EQ(json::reverse_iterator(3 + j_null.rbegin()), it);
+    EXPECT_EQ((it - 3), j_null.rbegin());
+    EXPECT_EQ((it - j_null.rbegin()), 3);
+    EXPECT_NE(it, j_null.rend());
+    it -= 3;
+    EXPECT_EQ(it, j_null.rend());
+}
+
+TEST_F(JsonReverseIteratorArithmeticTest, AddSubNullConst)
+{
+    auto it = j_null.crbegin();
+    it += 3;
+    EXPECT_EQ((j_null.crbegin() + 3), it);
+    EXPECT_EQ(json::const_reverse_iterator(3 + j_null.crbegin()), it);
+    EXPECT_EQ((it - 3), j_null.crbegin());
+    EXPECT_EQ((it - j_null.crbegin()), 3);
+    EXPECT_NE(it, j_null.crend());
+    it -= 3;
+    EXPECT_EQ(it, j_null.crend());
+}
+
+TEST_F(JsonReverseIteratorArithmeticTest, AddSubValue)
+{
+    auto it = j_value.rbegin();
+    it += 3;
+    EXPECT_EQ((j_value.rbegin() + 3), it);
+    EXPECT_EQ(json::reverse_iterator(3 + j_value.rbegin()), it);
+    EXPECT_EQ((it - 3), j_value.rbegin());
+    EXPECT_EQ((it - j_value.rbegin()), 3);
+    EXPECT_NE(it, j_value.rend());
+    it -= 3;
+    EXPECT_EQ(*it, json(42));
+}
+
+TEST_F(JsonReverseIteratorArithmeticTest, AddSubValueConst)
+{
+    auto it = j_value.crbegin();
+    it += 3;
+    EXPECT_EQ((j_value.crbegin() + 3), it);
+    EXPECT_EQ(json::const_reverse_iterator(3 + j_value.crbegin()), it);
+    EXPECT_EQ((it - 3), j_value.crbegin());
+    EXPECT_EQ((it - j_value.crbegin()), 3);
+    EXPECT_NE(it, j_value.crend());
+    it -= 3;
+    EXPECT_EQ(*it, json(42));
+}
+
+TEST_F(JsonReverseIteratorArithmeticTest, SubscriptObject)
+{
+    auto it = j_object.rbegin();
+    EXPECT_THROW_MSG(it[0], json::invalid_iterator,
+                     "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    EXPECT_THROW_MSG(it[1], json::invalid_iterator,
+                     "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+}
+
+TEST_F(JsonReverseIteratorArithmeticTest, SubscriptObjectConst)
+{
+    auto it = j_object.crbegin();
+    EXPECT_THROW_MSG(it[0], json::invalid_iterator,
+                     "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+    EXPECT_THROW_MSG(it[1], json::invalid_iterator,
+                     "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
+}
+
+TEST_F(JsonReverseIteratorArithmeticTest, SubscriptArray)
+{
+    auto it = j_array.rbegin();
+    EXPECT_EQ(it[0], json(6));
+    EXPECT_EQ(it[1], json(5));
+    EXPECT_EQ(it[2], json(4));
+    EXPECT_EQ(it[3], json(3));
+    EXPECT_EQ(it[4], json(2));
+    EXPECT_EQ(it[5], json(1));
+}
+
+TEST_F(JsonReverseIteratorArithmeticTest, SubscriptArrayConst)
+{
+    auto it = j_array.crbegin();
+    EXPECT_EQ(it[0], json(6));
+    EXPECT_EQ(it[1], json(5));
+    EXPECT_EQ(it[2], json(4));
+    EXPECT_EQ(it[3], json(3));
+    EXPECT_EQ(it[4], json(2));
+    EXPECT_EQ(it[5], json(1));
+}
+
+TEST_F(JsonReverseIteratorArithmeticTest, SubscriptNull)
+{
+    auto it = j_null.rbegin();
+    EXPECT_THROW_MSG(it[0], json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+    EXPECT_THROW_MSG(it[1], json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+}
+
+TEST_F(JsonReverseIteratorArithmeticTest, SubscriptNullConst)
+{
+    auto it = j_null.crbegin();
+    EXPECT_THROW_MSG(it[0], json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+    EXPECT_THROW_MSG(it[1], json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+}
+
+TEST_F(JsonReverseIteratorArithmeticTest, SubscriptValue)
+{
+    auto it = j_value.rbegin();
+    EXPECT_EQ(it[0], json(42));
+    EXPECT_THROW_MSG(it[1], json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+}
+
+TEST_F(JsonReverseIteratorArithmeticTest, SubscriptValueConst)
+{
+    auto it = j_value.crbegin();
+    EXPECT_EQ(it[0], json(42));
+    EXPECT_THROW_MSG(it[1], json::invalid_iterator,
+                     "[json.exception.invalid_iterator.214] cannot get value");
+}
+#endif
diff --git a/wpiutil/src/test/native/cpp/json/unit-json.h b/wpiutil/src/test/native/cpp/json/unit-json.h
new file mode 100644
index 0000000..5a764b7
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-json.h
@@ -0,0 +1,88 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) FIRST 2017. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/#ifndef UNIT_JSON_H_
+#define UNIT_JSON_H_
+
+#include <ostream>
+
+#include "wpi/json.h"
+
+namespace wpi {
+
+inline
+void PrintTo(const json& j, std::ostream* os) {
+  *os << j.dump();
+}
+
+class JsonTest {
+ public:
+  static const json::json_value& GetValue(const json& j) {
+    return j.m_value;
+  }
+  static json::pointer GetObject(json::iterator it) {
+    return it.m_object;
+  }
+  static json::const_pointer GetObject(json::const_iterator it) {
+    return it.m_object;
+  }
+  static std::string pop_back(json::json_pointer& p) {
+    return p.pop_back();
+  }
+  static json::json_pointer top(const json::json_pointer& p) {
+    return p.top();
+  }
+};
+
+}  // namespace wpi
+
+// clang warns on TEST_THROW_MSG(x == y, ...) saying the result is unused.
+// suppress this warning.
+#if defined(__clang__)
+#pragma GCC diagnostic ignored "-Wunused-comparison"
+#endif
+
+// variant of GTEST_TEST_THROW_ that also checks the exception's message.
+#define TEST_THROW_MSG(statement, expected_exception, expected_msg, fail) \
+  GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+  if (::testing::internal::ConstCharPtr gtest_msg = "") { \
+    bool gtest_caught_expected = false; \
+    try { \
+      GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
+    } \
+    catch (expected_exception const& gtest_ex) { \
+      gtest_caught_expected = true; \
+      if (::std::string(gtest_ex.what()) != expected_msg) { \
+        ::testing::AssertionResult gtest_ar = ::testing::AssertionFailure(); \
+        gtest_ar \
+            << "Expected: " #statement " throws an exception with message \"" \
+            << expected_msg "\".\n  Actual: it throws message \"" \
+            << gtest_ex.what() << "\"."; \
+        fail(gtest_ar.failure_message()); \
+      } \
+    } \
+    catch (...) { \
+      gtest_msg.value = \
+          "Expected: " #statement " throws an exception of type " \
+          #expected_exception ".\n  Actual: it throws a different type."; \
+      goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \
+    } \
+    if (!gtest_caught_expected) { \
+      gtest_msg.value = \
+          "Expected: " #statement " throws an exception of type " \
+          #expected_exception ".\n  Actual: it throws nothing."; \
+      goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \
+    } \
+  } else \
+    GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__): \
+      fail(gtest_msg.value)
+
+#define EXPECT_THROW_MSG(statement, expected_exception, expected_msg) \
+  TEST_THROW_MSG(statement, expected_exception, expected_msg, GTEST_NONFATAL_FAILURE_)
+
+#define ASSERT_THROW_MSG(statement, expected_exception, expected_msg) \
+  TEST_THROW_MSG(statement, expected_exception, expected_msg, GTEST_FATAL_FAILURE_)
+
+#endif
diff --git a/wpiutil/src/test/native/cpp/json/unit-json_pointer.cpp b/wpiutil/src/test/native/cpp/json/unit-json_pointer.cpp
new file mode 100644
index 0000000..d54fd6a
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-json_pointer.cpp
@@ -0,0 +1,402 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) FIRST 2017. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include "unit-json.h"
+using wpi::json;
+using wpi::JsonTest;
+
+TEST(JsonPointerTest, Errors)
+{
+    EXPECT_THROW_MSG(json::json_pointer("foo"), json::parse_error,
+                     "[json.exception.parse_error.107] parse error at 1: JSON pointer must be empty or begin with '/' - was: 'foo'");
+
+    EXPECT_THROW_MSG(json::json_pointer("/~~"), json::parse_error,
+                     "[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'");
+
+    EXPECT_THROW_MSG(json::json_pointer("/~"), json::parse_error,
+                     "[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'");
+
+    json::json_pointer p;
+    EXPECT_THROW_MSG(JsonTest::top(p), json::out_of_range,
+                     "[json.exception.out_of_range.405] JSON pointer has no parent");
+    EXPECT_THROW_MSG(JsonTest::pop_back(p), json::out_of_range,
+                     "[json.exception.out_of_range.405] JSON pointer has no parent");
+}
+
+// examples from RFC 6901
+TEST(JsonPointerTest, AccessNonConst)
+{
+    json j = R"(
+    {
+        "foo": ["bar", "baz"],
+        "": 0,
+        "a/b": 1,
+        "c%d": 2,
+        "e^f": 3,
+        "g|h": 4,
+        "i\\j": 5,
+        "k\"l": 6,
+        " ": 7,
+        "m~n": 8
+    }
+    )"_json;
+
+    // the whole document
+    EXPECT_EQ(j[json::json_pointer()], j);
+    EXPECT_EQ(j[json::json_pointer("")], j);
+
+    // array access
+    EXPECT_EQ(j[json::json_pointer("/foo")], j["foo"]);
+    EXPECT_EQ(j[json::json_pointer("/foo/0")], j["foo"][0]);
+    EXPECT_EQ(j[json::json_pointer("/foo/1")], j["foo"][1]);
+    EXPECT_EQ(j["/foo/1"_json_pointer], j["foo"][1]);
+
+    // checked array access
+    EXPECT_EQ(j.at(json::json_pointer("/foo/0")), j["foo"][0]);
+    EXPECT_EQ(j.at(json::json_pointer("/foo/1")), j["foo"][1]);
+
+    // empty string access
+    EXPECT_EQ(j[json::json_pointer("/")], j[""]);
+
+    // other cases
+    EXPECT_EQ(j[json::json_pointer("/ ")], j[" "]);
+    EXPECT_EQ(j[json::json_pointer("/c%d")], j["c%d"]);
+    EXPECT_EQ(j[json::json_pointer("/e^f")], j["e^f"]);
+    EXPECT_EQ(j[json::json_pointer("/g|h")], j["g|h"]);
+    EXPECT_EQ(j[json::json_pointer("/i\\j")], j["i\\j"]);
+    EXPECT_EQ(j[json::json_pointer("/k\"l")], j["k\"l"]);
+
+    // checked access
+    EXPECT_EQ(j.at(json::json_pointer("/ ")), j[" "]);
+    EXPECT_EQ(j.at(json::json_pointer("/c%d")), j["c%d"]);
+    EXPECT_EQ(j.at(json::json_pointer("/e^f")), j["e^f"]);
+    EXPECT_EQ(j.at(json::json_pointer("/g|h")), j["g|h"]);
+    EXPECT_EQ(j.at(json::json_pointer("/i\\j")), j["i\\j"]);
+    EXPECT_EQ(j.at(json::json_pointer("/k\"l")), j["k\"l"]);
+
+    // escaped access
+    EXPECT_EQ(j[json::json_pointer("/a~1b")], j["a/b"]);
+    EXPECT_EQ(j[json::json_pointer("/m~0n")], j["m~n"]);
+
+    // unescaped access
+    // access to nonexisting values yield object creation
+    EXPECT_NO_THROW(j[json::json_pointer("/a/b")] = 42);
+    EXPECT_EQ(j["a"]["b"], json(42));
+    EXPECT_NO_THROW(j[json::json_pointer("/a/c/1")] = 42);
+    EXPECT_EQ(j["a"]["c"], json({nullptr, 42}));
+    EXPECT_NO_THROW(j[json::json_pointer("/a/d/-")] = 42);
+    EXPECT_EQ(j["a"]["d"], json::array({42}));
+    // "/a/b" works for JSON {"a": {"b": 42}}
+    EXPECT_EQ(json({{"a", {{"b", 42}}}})[json::json_pointer("/a/b")], json(42));
+
+    // unresolved access
+    json j_primitive = 1;
+    EXPECT_THROW_MSG(j_primitive["/foo"_json_pointer], json::out_of_range,
+                     "[json.exception.out_of_range.404] unresolved reference token 'foo'");
+    EXPECT_THROW_MSG(j_primitive.at("/foo"_json_pointer), json::out_of_range,
+                     "[json.exception.out_of_range.404] unresolved reference token 'foo'");
+}
+
+TEST(JsonPointerTest, AccessConst)
+{
+    const json j = R"(
+    {
+        "foo": ["bar", "baz"],
+        "": 0,
+        "a/b": 1,
+        "c%d": 2,
+        "e^f": 3,
+        "g|h": 4,
+        "i\\j": 5,
+        "k\"l": 6,
+        " ": 7,
+        "m~n": 8
+    }
+    )"_json;
+
+    // the whole document
+    EXPECT_EQ(j[json::json_pointer()], j);
+    EXPECT_EQ(j[json::json_pointer("")], j);
+
+    // array access
+    EXPECT_EQ(j[json::json_pointer("/foo")], j["foo"]);
+    EXPECT_EQ(j[json::json_pointer("/foo/0")], j["foo"][0]);
+    EXPECT_EQ(j[json::json_pointer("/foo/1")], j["foo"][1]);
+    EXPECT_EQ(j["/foo/1"_json_pointer], j["foo"][1]);
+
+    // checked array access
+    EXPECT_EQ(j.at(json::json_pointer("/foo/0")), j["foo"][0]);
+    EXPECT_EQ(j.at(json::json_pointer("/foo/1")), j["foo"][1]);
+
+    // empty string access
+    EXPECT_EQ(j[json::json_pointer("/")], j[""]);
+
+    // other cases
+    EXPECT_EQ(j[json::json_pointer("/ ")], j[" "]);
+    EXPECT_EQ(j[json::json_pointer("/c%d")], j["c%d"]);
+    EXPECT_EQ(j[json::json_pointer("/e^f")], j["e^f"]);
+    EXPECT_EQ(j[json::json_pointer("/g|h")], j["g|h"]);
+    EXPECT_EQ(j[json::json_pointer("/i\\j")], j["i\\j"]);
+    EXPECT_EQ(j[json::json_pointer("/k\"l")], j["k\"l"]);
+
+    // checked access
+    EXPECT_EQ(j.at(json::json_pointer("/ ")), j[" "]);
+    EXPECT_EQ(j.at(json::json_pointer("/c%d")), j["c%d"]);
+    EXPECT_EQ(j.at(json::json_pointer("/e^f")), j["e^f"]);
+    EXPECT_EQ(j.at(json::json_pointer("/g|h")), j["g|h"]);
+    EXPECT_EQ(j.at(json::json_pointer("/i\\j")), j["i\\j"]);
+    EXPECT_EQ(j.at(json::json_pointer("/k\"l")), j["k\"l"]);
+
+    // escaped access
+    EXPECT_EQ(j[json::json_pointer("/a~1b")], j["a/b"]);
+    EXPECT_EQ(j[json::json_pointer("/m~0n")], j["m~n"]);
+
+    // unescaped access
+    EXPECT_THROW_MSG(j.at(json::json_pointer("/a/b")), json::out_of_range,
+                     "[json.exception.out_of_range.403] key 'a' not found");
+
+    // unresolved access
+    const json j_primitive = 1;
+    EXPECT_THROW_MSG(j_primitive["/foo"_json_pointer], json::out_of_range,
+                     "[json.exception.out_of_range.404] unresolved reference token 'foo'");
+    EXPECT_THROW_MSG(j_primitive.at("/foo"_json_pointer), json::out_of_range,
+                     "[json.exception.out_of_range.404] unresolved reference token 'foo'");
+}
+
+TEST(JsonPointerTest, UserStringLiteral)
+{
+    json j = R"(
+    {
+        "foo": ["bar", "baz"],
+        "": 0,
+        "a/b": 1,
+        "c%d": 2,
+        "e^f": 3,
+        "g|h": 4,
+        "i\\j": 5,
+        "k\"l": 6,
+        " ": 7,
+        "m~n": 8
+    }
+    )"_json;
+
+    // the whole document
+    EXPECT_EQ(j[""_json_pointer], j);
+
+    // array access
+    EXPECT_EQ(j["/foo"_json_pointer], j["foo"]);
+    EXPECT_EQ(j["/foo/0"_json_pointer], j["foo"][0]);
+    EXPECT_EQ(j["/foo/1"_json_pointer], j["foo"][1]);
+}
+
+TEST(JsonPointerTest, ArrayNonConst)
+{
+    json j = {1, 2, 3};
+    const json j_const = j;
+
+    // check reading access
+    EXPECT_EQ(j["/0"_json_pointer], j[0]);
+    EXPECT_EQ(j["/1"_json_pointer], j[1]);
+    EXPECT_EQ(j["/2"_json_pointer], j[2]);
+
+    // assign to existing index
+    j["/1"_json_pointer] = 13;
+    EXPECT_EQ(j[1], json(13));
+
+    // assign to nonexisting index
+    j["/3"_json_pointer] = 33;
+    EXPECT_EQ(j[3], json(33));
+
+    // assign to nonexisting index (with gap)
+    j["/5"_json_pointer] = 55;
+    EXPECT_EQ(j, json({1, 13, 3, 33, nullptr, 55}));
+
+    // error with leading 0
+    EXPECT_THROW_MSG(j["/01"_json_pointer], json::parse_error,
+                     "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'");
+    EXPECT_THROW_MSG(j_const["/01"_json_pointer], json::parse_error,
+                     "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'");
+    EXPECT_THROW_MSG(j.at("/01"_json_pointer), json::parse_error,
+                     "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'");
+    EXPECT_THROW_MSG(j_const.at("/01"_json_pointer), json::parse_error,
+                     "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'");
+
+    // error with incorrect numbers
+    EXPECT_THROW_MSG(j["/one"_json_pointer] = 1, json::parse_error,
+                     "[json.exception.parse_error.109] parse error: array index 'one' is not a number");
+    EXPECT_THROW_MSG(j_const["/one"_json_pointer] == 1, json::parse_error,
+                     "[json.exception.parse_error.109] parse error: array index 'one' is not a number");
+
+    EXPECT_THROW_MSG(j.at("/one"_json_pointer) = 1, json::parse_error,
+                     "[json.exception.parse_error.109] parse error: array index 'one' is not a number");
+    EXPECT_THROW_MSG(j_const.at("/one"_json_pointer) == 1, json::parse_error,
+                     "[json.exception.parse_error.109] parse error: array index 'one' is not a number");
+
+    EXPECT_THROW_MSG(json({{"/list/0", 1}, {"/list/1", 2}, {"/list/three", 3}}).unflatten(), json::parse_error,
+                     "[json.exception.parse_error.109] parse error: array index 'three' is not a number");
+
+    // assign to "-"
+    j["/-"_json_pointer] = 99;
+    EXPECT_EQ(j, json({1, 13, 3, 33, nullptr, 55, 99}));
+
+    // error when using "-" in const object
+    EXPECT_THROW_MSG(j_const["/-"_json_pointer], json::out_of_range,
+                     "[json.exception.out_of_range.402] array index '-' (3) is out of range");
+
+    // error when using "-" with at
+    EXPECT_THROW_MSG(j.at("/-"_json_pointer), json::out_of_range,
+                     "[json.exception.out_of_range.402] array index '-' (7) is out of range");
+    EXPECT_THROW_MSG(j_const.at("/-"_json_pointer), json::out_of_range,
+                     "[json.exception.out_of_range.402] array index '-' (3) is out of range");
+}
+
+TEST(JsonPointerTest, ArrayConst)
+{
+    const json j = {1, 2, 3};
+
+    // check reading access
+    EXPECT_EQ(j["/0"_json_pointer], j[0]);
+    EXPECT_EQ(j["/1"_json_pointer], j[1]);
+    EXPECT_EQ(j["/2"_json_pointer], j[2]);
+
+    // assign to nonexisting index
+    EXPECT_THROW_MSG(j.at("/3"_json_pointer), json::out_of_range,
+                     "[json.exception.out_of_range.401] array index 3 is out of range");
+
+    // assign to nonexisting index (with gap)
+    EXPECT_THROW_MSG(j.at("/5"_json_pointer), json::out_of_range,
+                     "[json.exception.out_of_range.401] array index 5 is out of range");
+
+    // assign to "-"
+    EXPECT_THROW_MSG(j["/-"_json_pointer], json::out_of_range,
+                     "[json.exception.out_of_range.402] array index '-' (3) is out of range");
+    EXPECT_THROW_MSG(j.at("/-"_json_pointer), json::out_of_range,
+                     "[json.exception.out_of_range.402] array index '-' (3) is out of range");
+}
+
+TEST(JsonPointerTest, Flatten)
+{
+    json j =
+    {
+        {"pi", 3.141},
+        {"happy", true},
+        {"name", "Niels"},
+        {"nothing", nullptr},
+        {
+            "answer", {
+                {"everything", 42}
+            }
+        },
+        {"list", {1, 0, 2}},
+        {
+            "object", {
+                {"currency", "USD"},
+                {"value", 42.99},
+                {"", "empty string"},
+                {"/", "slash"},
+                {"~", "tilde"},
+                {"~1", "tilde1"}
+            }
+        }
+    };
+
+    json j_flatten =
+    {
+        {"/pi", 3.141},
+        {"/happy", true},
+        {"/name", "Niels"},
+        {"/nothing", nullptr},
+        {"/answer/everything", 42},
+        {"/list/0", 1},
+        {"/list/1", 0},
+        {"/list/2", 2},
+        {"/object/currency", "USD"},
+        {"/object/value", 42.99},
+        {"/object/", "empty string"},
+        {"/object/~1", "slash"},
+        {"/object/~0", "tilde"},
+        {"/object/~01", "tilde1"}
+    };
+
+    // check if flattened result is as expected
+    EXPECT_EQ(j.flatten(), j_flatten);
+
+    // check if unflattened result is as expected
+    EXPECT_EQ(j_flatten.unflatten(), j);
+
+    // error for nonobjects
+    EXPECT_THROW_MSG(json(1).unflatten(), json::type_error,
+                     "[json.exception.type_error.314] only objects can be unflattened");
+
+    // error for nonprimitve values
+    EXPECT_THROW_MSG(json({{"/1", {1, 2, 3}}}).unflatten(), json::type_error,
+                     "[json.exception.type_error.315] values in object must be primitive");
+
+    // error for conflicting values
+    json j_error = {{"", 42}, {"/foo", 17}};
+    EXPECT_THROW_MSG(j_error.unflatten(), json::type_error,
+                     "[json.exception.type_error.313] invalid value to unflatten");
+
+    // explicit roundtrip check
+    EXPECT_EQ(j.flatten().unflatten(), j);
+
+    // roundtrip for primitive values
+    json j_null;
+    EXPECT_EQ(j_null.flatten().unflatten(), j_null);
+    json j_number = 42;
+    EXPECT_EQ(j_number.flatten().unflatten(), j_number);
+    json j_boolean = false;
+    EXPECT_EQ(j_boolean.flatten().unflatten(), j_boolean);
+    json j_string = "foo";
+    EXPECT_EQ(j_string.flatten().unflatten(), j_string);
+
+    // roundtrip for empty structured values (will be unflattened to null)
+    json j_array(json::value_t::array);
+    EXPECT_EQ(j_array.flatten().unflatten(), json());
+    json j_object(json::value_t::object);
+    EXPECT_EQ(j_object.flatten().unflatten(), json());
+}
+
+TEST(JsonPointerTest, StringRepresentation)
+{
+    for (auto ptr :
+            {"", "/foo", "/foo/0", "/", "/a~1b", "/c%d", "/e^f", "/g|h", "/i\\j", "/k\"l", "/ ", "/m~0n"
+            })
+    {
+        SCOPED_TRACE(ptr);
+        EXPECT_EQ(json::json_pointer(ptr).to_string(), ptr);
+    }
+}
diff --git a/wpiutil/src/test/native/cpp/json/unit-meta.cpp b/wpiutil/src/test/native/cpp/json/unit-meta.cpp
new file mode 100644
index 0000000..45daf8f
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-meta.cpp
@@ -0,0 +1,54 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) FIRST 2017. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2016 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include "unit-json.h"
+using wpi::json;
+
+TEST(JsonVersionTest, Meta)
+{
+    json j = json::meta();
+
+    EXPECT_EQ(j["name"], "WPI version of JSON for Modern C++");
+    EXPECT_EQ(j["copyright"], "(C) 2013-2017 Niels Lohmann, (C) 2017-2018 FIRST");
+    EXPECT_EQ(j["url"], "https://github.com/wpilibsuite/allwpilib");
+    EXPECT_EQ(j["version"], json(
+    {
+        {"string", "3.1.2"},
+        {"major", 3},
+        {"minor", 1},
+        {"patch", 2}
+    }));
+}
diff --git a/wpiutil/src/test/native/cpp/json/unit-modifiers.cpp b/wpiutil/src/test/native/cpp/json/unit-modifiers.cpp
new file mode 100644
index 0000000..4125858
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-modifiers.cpp
@@ -0,0 +1,739 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) FIRST 2017. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include "unit-json.h"
+using wpi::json;
+
+TEST(JsonClearTest, Boolean)
+{
+    json j = true;
+
+    j.clear();
+    EXPECT_EQ(j, json(json::value_t::boolean));
+}
+
+TEST(JsonClearTest, String)
+{
+    json j = "hello world";
+
+    j.clear();
+    EXPECT_EQ(j, json(json::value_t::string));
+}
+
+TEST(JsonClearTest, ArrayEmpty)
+{
+    json j = json::array();
+
+    j.clear();
+    EXPECT_TRUE(j.empty());
+    EXPECT_EQ(j, json(json::value_t::array));
+}
+
+TEST(JsonClearTest, ArrayFilled)
+{
+    json j = {1, 2, 3};
+
+    j.clear();
+    EXPECT_TRUE(j.empty());
+    EXPECT_EQ(j, json(json::value_t::array));
+}
+
+TEST(JsonClearTest, ObjectEmpty)
+{
+    json j = json::object();
+
+    j.clear();
+    EXPECT_TRUE(j.empty());
+    EXPECT_EQ(j, json(json::value_t::object));
+}
+
+TEST(JsonClearTest, ObjectFilled)
+{
+    json j = {{"one", 1}, {"two", 2}, {"three", 3}};
+
+    j.clear();
+    EXPECT_TRUE(j.empty());
+    EXPECT_EQ(j, json(json::value_t::object));
+}
+
+TEST(JsonClearTest, Integer)
+{
+    json j = 23;
+
+    j.clear();
+    EXPECT_EQ(j, json(json::value_t::number_integer));
+}
+
+TEST(JsonClearTest, Unsigned)
+{
+    json j = 23u;
+
+    j.clear();
+    EXPECT_EQ(j, json(json::value_t::number_integer));
+}
+
+TEST(JsonClearTest, Float)
+{
+    json j = 23.42;
+
+    j.clear();
+    EXPECT_EQ(j, json(json::value_t::number_float));
+}
+
+TEST(JsonClearTest, Null)
+{
+    json j = nullptr;
+
+    j.clear();
+    EXPECT_EQ(j, json(json::value_t::null));
+}
+
+TEST(JsonPushBackArrayTest, RRefNull)
+{
+    json j;
+    j.push_back(1);
+    j.push_back(2);
+    EXPECT_EQ(j.type(), json::value_t::array);
+    EXPECT_EQ(j, json({1, 2}));
+}
+
+TEST(JsonPushBackArrayTest, RRefArray)
+{
+    json j = {1, 2, 3};
+    j.push_back("Hello");
+    EXPECT_EQ(j.type(), json::value_t::array);
+    EXPECT_EQ(j, json({1, 2, 3, "Hello"}));
+}
+
+TEST(JsonPushBackArrayTest, RRefOther)
+{
+    json j = 1;
+    EXPECT_THROW_MSG(j.push_back("Hello"), json::type_error,
+                     "[json.exception.type_error.308] cannot use push_back() with number");
+}
+
+TEST(JsonPushBackArrayTest, LRefNull)
+{
+    json j;
+    json k(1);
+    j.push_back(k);
+    j.push_back(k);
+    EXPECT_EQ(j.type(), json::value_t::array);
+    EXPECT_EQ(j, json({1, 1}));
+}
+
+TEST(JsonPushBackArrayTest, LRefArray)
+{
+    json j = {1, 2, 3};
+    json k("Hello");
+    j.push_back(k);
+    EXPECT_EQ(j.type(), json::value_t::array);
+    EXPECT_EQ(j, json({1, 2, 3, "Hello"}));
+}
+
+TEST(JsonPushBackArrayTest, LRefOther)
+{
+    json j = 1;
+    json k("Hello");
+    EXPECT_THROW_MSG(j.push_back(k), json::type_error,
+                     "[json.exception.type_error.308] cannot use push_back() with number");
+}
+#if 0
+TEST(JsonPushBackObjectTest, Null)
+{
+    json j;
+    j.push_back(json::object_t::value_type({"one", 1}));
+    j.push_back(json::object_t::value_type({"two", 2}));
+    EXPECT_EQ(j.type(), json::value_t::object);
+    EXPECT_EQ(j.size(), 2u);
+    EXPECT_EQ(j["one"], json(1));
+    EXPECT_EQ(j["two"], json(2));
+}
+
+TEST(JsonPushBackObjectTest, Object)
+{
+    json j(json::value_t::object);
+    j.push_back(json::object_t::value_type({"one", 1}));
+    j.push_back(json::object_t::value_type({"two", 2}));
+    EXPECT_EQ(j.size(), 2u);
+    EXPECT_EQ(j["one"], json(1));
+    EXPECT_EQ(j["two"], json(2));
+}
+
+TEST(JsonPushBackObjectTest, Other)
+{
+    json j = 1;
+    json k("Hello");
+    EXPECT_THROW_MSG(j.push_back(json::object_t::value_type({"one", 1})), json::type_error,
+                     "[json.exception.type_error.308] cannot use push_back() with number");
+}
+#endif
+TEST(JsonPushBackInitListTest, Null)
+{
+    json j;
+    j.push_back({"foo", "bar"});
+    EXPECT_EQ(j, json::array({{"foo", "bar"}}));
+
+    json k;
+    k.push_back({1, 2, 3});
+    EXPECT_EQ(k, json::array({{1, 2, 3}}));
+}
+
+TEST(JsonPushBackInitListTest, Array)
+{
+    json j = {1, 2, 3};
+    j.push_back({"foo", "bar"});
+    EXPECT_EQ(j, json({1, 2, 3, {"foo", "bar"}}));
+
+    json k = {1, 2, 3};
+    k.push_back({1, 2, 3});
+    EXPECT_EQ(k, json({1, 2, 3, {1, 2, 3}}));
+}
+
+TEST(JsonPushBackInitListTest, Object)
+{
+    json j = {{"key1", 1}};
+    j.push_back({"key2", "bar"});
+    EXPECT_EQ(j, json({{"key1", 1}, {"key2", "bar"}}));
+
+    json k = {{"key1", 1}};
+    EXPECT_THROW_MSG(k.push_back({1, 2, 3, 4}), json::type_error,
+                     "[json.exception.type_error.308] cannot use push_back() with object");
+}
+
+TEST(JsonEmplaceBackArrayTest, Null)
+{
+    json j;
+    j.emplace_back(1);
+    j.emplace_back(2);
+    EXPECT_EQ(j.type(), json::value_t::array);
+    EXPECT_EQ(j, json({1, 2}));
+}
+
+TEST(JsonEmplaceBackArrayTest, Array)
+{
+    json j = {1, 2, 3};
+    j.emplace_back("Hello");
+    EXPECT_EQ(j.type(), json::value_t::array);
+    EXPECT_EQ(j, json({1, 2, 3, "Hello"}));
+}
+
+TEST(JsonEmplaceBackArrayTest, MultipleValues)
+{
+    json j;
+    j.emplace_back(3, "foo");
+    EXPECT_EQ(j.type(), json::value_t::array);
+    EXPECT_EQ(j, json({{"foo", "foo", "foo"}}));
+}
+
+TEST(JsonEmplaceBackArrayTest, Other)
+{
+    json j = 1;
+    EXPECT_THROW_MSG(j.emplace_back("Hello"), json::type_error,
+                     "[json.exception.type_error.311] cannot use emplace_back() with number");
+}
+
+TEST(JsonEmplaceObjectTest, Null)
+{
+    // start with a null value
+    json j;
+
+    // add a new key
+    auto res1 = j.emplace("foo", "bar");
+    EXPECT_EQ(res1.second, true);
+    EXPECT_EQ(*res1.first, "bar");
+
+    // the null value is changed to an object
+    EXPECT_EQ(j.type(), json::value_t::object);
+
+    // add a new key
+    auto res2 = j.emplace("baz", "bam");
+    EXPECT_EQ(res2.second, true);
+    EXPECT_EQ(*res2.first, "bam");
+
+    // we try to insert at given key - no change
+    auto res3 = j.emplace("baz", "bad");
+    EXPECT_EQ(res3.second, false);
+    EXPECT_EQ(*res3.first, "bam");
+
+    // the final object
+    EXPECT_EQ(j, json({{"baz", "bam"}, {"foo", "bar"}}));
+}
+
+TEST(JsonEmplaceObjectTest, Object)
+{
+    // start with an object
+    json j = {{"foo", "bar"}};
+
+    // add a new key
+    auto res1 = j.emplace("baz", "bam");
+    EXPECT_EQ(res1.second, true);
+    EXPECT_EQ(*res1.first, "bam");
+
+    // add an existing key
+    auto res2 = j.emplace("foo", "bad");
+    EXPECT_EQ(res2.second, false);
+    EXPECT_EQ(*res2.first, "bar");
+
+    // check final object
+    EXPECT_EQ(j, json({{"baz", "bam"}, {"foo", "bar"}}));
+}
+
+TEST(JsonEmplaceObjectTest, Other)
+{
+    json j = 1;
+    EXPECT_THROW_MSG(j.emplace("foo", "bar"), json::type_error,
+                     "[json.exception.type_error.311] cannot use emplace() with number");
+}
+
+TEST(JsonPlusEqualArrayTest, RRefNull)
+{
+    json j;
+    j += 1;
+    j += 2;
+    EXPECT_EQ(j.type(), json::value_t::array);
+    EXPECT_EQ(j, json({1, 2}));
+}
+
+TEST(JsonPlusEqualArrayTest, RRefArray)
+{
+    json j = {1, 2, 3};
+    j += "Hello";
+    EXPECT_EQ(j.type(), json::value_t::array);
+    EXPECT_EQ(j, json({1, 2, 3, "Hello"}));
+}
+
+TEST(JsonPlusEqualArrayTest, RRefOther)
+{
+    json j = 1;
+    EXPECT_THROW_MSG(j += "Hello", json::type_error,
+                     "[json.exception.type_error.308] cannot use push_back() with number");
+}
+
+TEST(JsonPlusEqualArrayTest, LRefNull)
+{
+    json j;
+    json k(1);
+    j += k;
+    j += k;
+    EXPECT_EQ(j.type(), json::value_t::array);
+    EXPECT_EQ(j, json({1, 1}));
+}
+
+TEST(JsonPlusEqualArrayTest, LRefArray)
+{
+    json j = {1, 2, 3};
+    json k("Hello");
+    j += k;
+    EXPECT_EQ(j.type(), json::value_t::array);
+    EXPECT_EQ(j, json({1, 2, 3, "Hello"}));
+}
+
+TEST(JsonPlusEqualArrayTest, LRefOther)
+{
+    json j = 1;
+    json k("Hello");
+    EXPECT_THROW_MSG(j += k, json::type_error,
+                     "[json.exception.type_error.308] cannot use push_back() with number");
+}
+#if 0
+TEST(JsonPlusEqualObjectTest, Null)
+{
+    json j;
+    j += json::object_t::value_type({"one", 1});
+    j += json::object_t::value_type({"two", 2});
+    EXPECT_EQ(j.type(), json::value_t::object);
+    EXPECT_EQ(j.size(), 2u);
+    EXPECT_EQ(j["one"], json(1));
+    EXPECT_EQ(j["two"], json(2));
+}
+
+TEST(JsonPlusEqualObjectTest, Object)
+{
+    json j(json::value_t::object);
+    j += json::object_t::value_type({"one", 1});
+    j += json::object_t::value_type({"two", 2});
+    EXPECT_EQ(j.size(), 2u);
+    EXPECT_EQ(j["one"], json(1));
+    EXPECT_EQ(j["two"], json(2));
+}
+
+TEST(JsonPlusEqualObjectTest, Other)
+{
+    json j = 1;
+    json k("Hello");
+    EXPECT_THROW_MSG(j += json::object_t::value_type({"one", 1}), json::type_error,
+                     "[json.exception.type_error.308] cannot use push_back() with number");
+}
+#endif
+TEST(JsonPlusEqualInitListTest, Null)
+{
+    json j;
+    j += {"foo", "bar"};
+    EXPECT_EQ(j, json::array({{"foo", "bar"}}));
+
+    json k;
+    k += {1, 2, 3};
+    EXPECT_EQ(k, json::array({{1, 2, 3}}));
+}
+
+TEST(JsonPlusEqualInitListTest, Array)
+{
+    json j = {1, 2, 3};
+    j += {"foo", "bar"};
+    EXPECT_EQ(j, json({1, 2, 3, {"foo", "bar"}}));
+
+    json k = {1, 2, 3};
+    k += {1, 2, 3};
+    EXPECT_EQ(k, json({1, 2, 3, {1, 2, 3}}));
+}
+
+TEST(JsonPlusEqualInitListTest, Object)
+{
+    json j = {{"key1", 1}};
+    j += {"key2", "bar"};
+    EXPECT_EQ(j, json({{"key1", 1}, {"key2", "bar"}}));
+
+    json k = {{"key1", 1}};
+    EXPECT_THROW_MSG((k += {1, 2, 3, 4}), json::type_error,
+                     "[json.exception.type_error.308] cannot use push_back() with object");
+}
+
+class JsonInsertTest : public ::testing::Test {
+ protected:
+    json j_array = {1, 2, 3, 4};
+    json j_value = 5;
+    json j_other_array = {"first", "second"};
+    json j_object1 = {{"one", "eins"}, {"two", "zwei"}};
+    json j_object2 = {{"eleven", "elf"}, {"seventeen", "siebzehn"}};
+};
+
+TEST_F(JsonInsertTest, ValueBegin)
+{
+    auto it = j_array.insert(j_array.begin(), j_value);
+    EXPECT_EQ(j_array.size(), 5u);
+    EXPECT_EQ(*it, j_value);
+    EXPECT_EQ(j_array.begin(), it);
+    EXPECT_EQ(j_array, json({5, 1, 2, 3, 4}));
+}
+
+TEST_F(JsonInsertTest, ValueMiddle)
+{
+    auto it = j_array.insert(j_array.begin() + 2, j_value);
+    EXPECT_EQ(j_array.size(), 5u);
+    EXPECT_EQ(*it, j_value);
+    EXPECT_EQ((it - j_array.begin()), 2);
+    EXPECT_EQ(j_array, json({1, 2, 5, 3, 4}));
+}
+
+TEST_F(JsonInsertTest, ValueEnd)
+{
+    auto it = j_array.insert(j_array.end(), j_value);
+    EXPECT_EQ(j_array.size(), 5u);
+    EXPECT_EQ(*it, j_value);
+    EXPECT_EQ((j_array.end() - it), 1);
+    EXPECT_EQ(j_array, json({1, 2, 3, 4, 5}));
+}
+
+TEST_F(JsonInsertTest, RvalueBegin)
+{
+    auto it = j_array.insert(j_array.begin(), 5);
+    EXPECT_EQ(j_array.size(), 5u);
+    EXPECT_EQ(*it, j_value);
+    EXPECT_EQ(j_array.begin(), it);
+    EXPECT_EQ(j_array, json({5, 1, 2, 3, 4}));
+}
+
+TEST_F(JsonInsertTest, RvalueMiddle)
+{
+    auto it = j_array.insert(j_array.begin() + 2, 5);
+    EXPECT_EQ(j_array.size(), 5u);
+    EXPECT_EQ(*it, j_value);
+    EXPECT_EQ((it - j_array.begin()), 2);
+    EXPECT_EQ(j_array, json({1, 2, 5, 3, 4}));
+}
+
+TEST_F(JsonInsertTest, RvalueEnd)
+{
+    auto it = j_array.insert(j_array.end(), 5);
+    EXPECT_EQ(j_array.size(), 5u);
+    EXPECT_EQ(*it, j_value);
+    EXPECT_EQ((j_array.end() - it), 1);
+    EXPECT_EQ(j_array, json({1, 2, 3, 4, 5}));
+}
+
+TEST_F(JsonInsertTest, CopyBegin)
+{
+    auto it = j_array.insert(j_array.begin(), 3, 5);
+    EXPECT_EQ(j_array.size(), 7u);
+    EXPECT_EQ(*it, j_value);
+    EXPECT_EQ(j_array.begin(), it);
+    EXPECT_EQ(j_array, json({5, 5, 5, 1, 2, 3, 4}));
+}
+
+TEST_F(JsonInsertTest, CopyMiddle)
+{
+    auto it = j_array.insert(j_array.begin() + 2, 3, 5);
+    EXPECT_EQ(j_array.size(), 7u);
+    EXPECT_EQ(*it, j_value);
+    EXPECT_EQ((it - j_array.begin()), 2);
+    EXPECT_EQ(j_array, json({1, 2, 5, 5, 5, 3, 4}));
+}
+
+TEST_F(JsonInsertTest, CopyEnd)
+{
+    auto it = j_array.insert(j_array.end(), 3, 5);
+    EXPECT_EQ(j_array.size(), 7u);
+    EXPECT_EQ(*it, j_value);
+    EXPECT_EQ((j_array.end() - it), 3);
+    EXPECT_EQ(j_array, json({1, 2, 3, 4, 5, 5, 5}));
+}
+
+TEST_F(JsonInsertTest, CopyNothing)
+{
+    auto it = j_array.insert(j_array.end(), 0, 5);
+    EXPECT_EQ(j_array.size(), 4u);
+    // the returned iterator points to the first inserted element;
+    // there were 4 elements, so it should point to the 5th
+    EXPECT_EQ(it, j_array.begin() + 4);
+    EXPECT_EQ(j_array, json({1, 2, 3, 4}));
+}
+
+TEST_F(JsonInsertTest, RangeForArrayProper)
+{
+    auto it = j_array.insert(j_array.end(), j_other_array.begin(), j_other_array.end());
+    EXPECT_EQ(j_array.size(), 6u);
+    EXPECT_EQ(*it, *j_other_array.begin());
+    EXPECT_EQ((j_array.end() - it), 2);
+    EXPECT_EQ(j_array, json({1, 2, 3, 4, "first", "second"}));
+}
+
+TEST_F(JsonInsertTest, RangeForArrayEmpty)
+{
+    auto it = j_array.insert(j_array.end(), j_other_array.begin(), j_other_array.begin());
+    EXPECT_EQ(j_array.size(), 4u);
+    EXPECT_EQ(it, j_array.end());
+    EXPECT_EQ(j_array, json({1, 2, 3, 4}));
+}
+
+TEST_F(JsonInsertTest, RangeForArrayInvalid)
+{
+    json j_other_array2 = {"first", "second"};
+
+    EXPECT_THROW_MSG(j_array.insert(j_array.end(), j_array.begin(), j_array.end()),
+                    json::invalid_iterator,
+                    "[json.exception.invalid_iterator.211] passed iterators may not belong to container");
+    EXPECT_THROW_MSG(j_array.insert(j_array.end(), j_other_array.begin(), j_other_array2.end()),
+                    json::invalid_iterator,
+                    "[json.exception.invalid_iterator.210] iterators do not fit");
+}
+
+TEST_F(JsonInsertTest, RangeForObjectProper)
+{
+    j_object1.insert(j_object2.begin(), j_object2.end());
+    EXPECT_EQ(j_object1.size(), 4u);
+}
+
+TEST_F(JsonInsertTest, RangeForObjectEmpty)
+{
+    j_object1.insert(j_object2.begin(), j_object2.begin());
+    EXPECT_EQ(j_object1.size(), 2u);
+}
+
+TEST_F(JsonInsertTest, RangeForObjectInvalid)
+{
+    json j_other_array2 = {"first", "second"};
+
+    EXPECT_THROW_MSG(j_array.insert(j_object2.begin(), j_object2.end()), json::type_error,
+                     "[json.exception.type_error.309] cannot use insert() with array");
+    EXPECT_THROW_MSG(j_object1.insert(j_object1.begin(), j_object2.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.210] iterators do not fit");
+    EXPECT_THROW_MSG(j_object1.insert(j_array.begin(), j_array.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.202] iterators first and last must point to objects");
+}
+
+TEST_F(JsonInsertTest, InitListBegin)
+{
+    auto it = j_array.insert(j_array.begin(), {7, 8, 9});
+    EXPECT_EQ(j_array.size(), 7u);
+    EXPECT_EQ(*it, json(7));
+    EXPECT_EQ(j_array.begin(), it);
+    EXPECT_EQ(j_array, json({7, 8, 9, 1, 2, 3, 4}));
+}
+
+TEST_F(JsonInsertTest, InitListMiddle)
+{
+    auto it = j_array.insert(j_array.begin() + 2, {7, 8, 9});
+    EXPECT_EQ(j_array.size(), 7u);
+    EXPECT_EQ(*it, json(7));
+    EXPECT_EQ((it - j_array.begin()), 2);
+    EXPECT_EQ(j_array, json({1, 2, 7, 8, 9, 3, 4}));
+}
+
+TEST_F(JsonInsertTest, InitListEnd)
+{
+    auto it = j_array.insert(j_array.end(), {7, 8, 9});
+    EXPECT_EQ(j_array.size(), 7u);
+    EXPECT_EQ(*it, json(7));
+    EXPECT_EQ((j_array.end() - it), 3);
+    EXPECT_EQ(j_array, json({1, 2, 3, 4, 7, 8, 9}));
+}
+
+TEST_F(JsonInsertTest, InvalidIterator)
+{
+    // pass iterator to a different array
+    json j_another_array = {1, 2};
+    json j_yet_another_array = {"first", "second"};
+    EXPECT_THROW_MSG(j_array.insert(j_another_array.end(), 10), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.202] iterator does not fit current value");
+    EXPECT_THROW_MSG(j_array.insert(j_another_array.end(), j_value), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.202] iterator does not fit current value");
+    EXPECT_THROW_MSG(j_array.insert(j_another_array.end(), 10, 11), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.202] iterator does not fit current value");
+    EXPECT_THROW_MSG(j_array.insert(j_another_array.end(), j_yet_another_array.begin(), j_yet_another_array.end()), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.202] iterator does not fit current value");
+    EXPECT_THROW_MSG(j_array.insert(j_another_array.end(), {1, 2, 3, 4}), json::invalid_iterator,
+                     "[json.exception.invalid_iterator.202] iterator does not fit current value");
+}
+
+TEST_F(JsonInsertTest, NonArray)
+{
+    // call insert on a non-array type
+    json j_nonarray = 3;
+    json j_yet_another_array = {"first", "second"};
+    EXPECT_THROW_MSG(j_nonarray.insert(j_nonarray.end(), 10), json::type_error,
+                     "[json.exception.type_error.309] cannot use insert() with number");
+    EXPECT_THROW_MSG(j_nonarray.insert(j_nonarray.end(), j_value), json::type_error,
+                     "[json.exception.type_error.309] cannot use insert() with number");
+    EXPECT_THROW_MSG(j_nonarray.insert(j_nonarray.end(), 10, 11), json::type_error,
+                     "[json.exception.type_error.309] cannot use insert() with number");
+    EXPECT_THROW_MSG(j_nonarray.insert(j_nonarray.end(), j_yet_another_array.begin(),
+                                       j_yet_another_array.end()), json::type_error,
+                     "[json.exception.type_error.309] cannot use insert() with number");
+    EXPECT_THROW_MSG(j_nonarray.insert(j_nonarray.end(), {1, 2, 3, 4}), json::type_error,
+                     "[json.exception.type_error.309] cannot use insert() with number");
+}
+
+TEST(JsonSwapTest, JsonMember)
+{
+    json j("hello world");
+    json k(42.23);
+
+    j.swap(k);
+
+    EXPECT_EQ(j, json(42.23));
+    EXPECT_EQ(k, json("hello world"));
+}
+
+TEST(JsonSwapTest, JsonNonMember)
+{
+    json j("hello world");
+    json k(42.23);
+
+    std::swap(j, k);
+
+    EXPECT_EQ(j, json(42.23));
+    EXPECT_EQ(k, json("hello world"));
+}
+
+TEST(JsonSwapTest, ArrayT)
+{
+    json j = {1, 2, 3, 4};
+    json::array_t a = {"foo", "bar", "baz"};
+
+    j.swap(a);
+
+    EXPECT_EQ(j, json({"foo", "bar", "baz"}));
+
+    j.swap(a);
+
+    EXPECT_EQ(j, json({1, 2, 3, 4}));
+}
+
+TEST(JsonSwapTest, NonArrayT)
+{
+    json j = 17;
+    json::array_t a = {"foo", "bar", "baz"};
+
+    EXPECT_THROW_MSG(j.swap(a), json::type_error,
+                     "[json.exception.type_error.310] cannot use swap() with number");
+}
+
+TEST(JsonSwapTest, ObjectT)
+{
+    json j = {{"one", 1}, {"two", 2}};
+    json::object_t o = {{"cow", "Kuh"}, {"chicken", "Huhn"}};
+
+    j.swap(o);
+
+    EXPECT_EQ(j, json({{"cow", "Kuh"}, {"chicken", "Huhn"}}));
+
+    j.swap(o);
+
+    EXPECT_EQ(j, json({{"one", 1}, {"two", 2}}));
+}
+
+TEST(JsonSwapTest, NonObjectT)
+{
+    json j = 17;
+    json::object_t o = {{"cow", "Kuh"}, {"chicken", "Huhn"}};
+
+    EXPECT_THROW_MSG(j.swap(o), json::type_error,
+                     "[json.exception.type_error.310] cannot use swap() with number");
+}
+
+TEST(JsonSwapTest, StringT)
+{
+    json j = "Hello world";
+    std::string s = "Hallo Welt";
+
+    j.swap(s);
+
+    EXPECT_EQ(j, json("Hallo Welt"));
+
+    j.swap(s);
+
+    EXPECT_EQ(j, json("Hello world"));
+}
+
+TEST(JsonSwapTest, NonStringT)
+{
+    json j = 17;
+    std::string s = "Hallo Welt";
+
+    EXPECT_THROW_MSG(j.swap(s), json::type_error,
+                     "[json.exception.type_error.310] cannot use swap() with number");
+}
diff --git a/wpiutil/src/test/native/cpp/json/unit-msgpack.cpp b/wpiutil/src/test/native/cpp/json/unit-msgpack.cpp
new file mode 100644
index 0000000..c0a237d
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-msgpack.cpp
@@ -0,0 +1,1269 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) FIRST 2017. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include "unit-json.h"
+using wpi::json;
+
+#include <fstream>
+
+TEST(MessagePackDiscardedTest, Case)
+{
+    // discarded values are not serialized
+    json j = json::value_t::discarded;
+    const auto result = json::to_msgpack(j);
+    EXPECT_TRUE(result.empty());
+}
+
+TEST(MessagePackNullTest, Case)
+{
+    json j = nullptr;
+    std::vector<uint8_t> expected = {0xc0};
+    const auto result = json::to_msgpack(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+TEST(MessagePackBooleanTest, True)
+{
+    json j = true;
+    std::vector<uint8_t> expected = {0xc3};
+    const auto result = json::to_msgpack(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+TEST(MessagePackBooleanTest, False)
+{
+    json j = false;
+    std::vector<uint8_t> expected = {0xc2};
+    const auto result = json::to_msgpack(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+// -32..-1 (negative fixnum)
+TEST(MessagePackSignedTest, Neg0)
+{
+    for (auto i = -32; i <= -1; ++i)
+    {
+        SCOPED_TRACE(i);
+
+        // create JSON value with integer number
+        json j = i;
+
+        // check type
+        EXPECT_TRUE(j.is_number_integer());
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(static_cast<uint8_t>(i));
+
+        // compare result + size
+        const auto result = json::to_msgpack(j);
+        EXPECT_EQ(result, expected);
+        EXPECT_EQ(result.size(), 1u);
+
+        // check individual bytes
+        EXPECT_EQ(static_cast<int8_t>(result[0]), i);
+
+        // roundtrip
+        EXPECT_EQ(json::from_msgpack(result), j);
+    }
+}
+
+// 0..127 (positive fixnum)
+TEST(MessagePackSignedTest, Pos0)
+{
+    for (size_t i = 0; i <= 127; ++i)
+    {
+        SCOPED_TRACE(i);
+
+        // create JSON value with integer number
+        json j = -1;
+        j.get_ref<int64_t&>() = static_cast<int64_t>(i);
+
+        // check type
+        EXPECT_TRUE(j.is_number_integer());
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(static_cast<uint8_t>(i));
+
+        // compare result + size
+        const auto result = json::to_msgpack(j);
+        EXPECT_EQ(result, expected);
+        EXPECT_EQ(result.size(), 1u);
+
+        // check individual bytes
+        EXPECT_EQ(result[0], static_cast<uint8_t>(i));
+
+        // roundtrip
+        EXPECT_EQ(json::from_msgpack(result), j);
+    }
+}
+
+// 128..255 (int 8)
+TEST(MessagePackSignedTest, Pos1)
+{
+    for (size_t i = 128; i <= 255; ++i)
+    {
+        SCOPED_TRACE(i);
+
+        // create JSON value with integer number
+        json j = -1;
+        j.get_ref<int64_t&>() = static_cast<int64_t>(i);
+
+        // check type
+        EXPECT_TRUE(j.is_number_integer());
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(static_cast<uint8_t>(0xcc));
+        expected.push_back(static_cast<uint8_t>(i));
+
+        // compare result + size
+        const auto result = json::to_msgpack(j);
+        EXPECT_EQ(result, expected);
+        EXPECT_EQ(result.size(), 2u);
+
+        // check individual bytes
+        EXPECT_EQ(result[0], static_cast<uint8_t>(0xcc));
+        uint8_t restored = static_cast<uint8_t>(result[1]);
+        EXPECT_EQ(restored, i);
+
+        // roundtrip
+        EXPECT_EQ(json::from_msgpack(result), j);
+    }
+}
+
+// 256..65535 (int 16)
+TEST(MessagePackSignedTest, Pos2)
+{
+    for (size_t i = 256; i <= 65535; ++i)
+    {
+        SCOPED_TRACE(i);
+
+        // create JSON value with integer number
+        json j = -1;
+        j.get_ref<int64_t&>() = static_cast<int64_t>(i);
+
+        // check type
+        EXPECT_TRUE(j.is_number_integer());
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(static_cast<uint8_t>(0xcd));
+        expected.push_back(static_cast<uint8_t>((i >> 8) & 0xff));
+        expected.push_back(static_cast<uint8_t>(i & 0xff));
+
+        // compare result + size
+        const auto result = json::to_msgpack(j);
+        EXPECT_EQ(result, expected);
+        EXPECT_EQ(result.size(), 3u);
+
+        // check individual bytes
+        EXPECT_EQ(result[0], static_cast<uint8_t>(0xcd));
+        uint16_t restored = static_cast<uint16_t>(static_cast<uint8_t>(result[1]) * 256 + static_cast<uint8_t>(result[2]));
+        EXPECT_EQ(restored, i);
+
+        // roundtrip
+        EXPECT_EQ(json::from_msgpack(result), j);
+    }
+}
+
+// 65536..4294967295 (int 32)
+class MessagePackSignedPos4Test : public ::testing::TestWithParam<uint32_t> {};
+TEST_P(MessagePackSignedPos4Test, Case)
+{
+    // create JSON value with integer number
+    json j = -1;
+    j.get_ref<int64_t&>() = static_cast<int64_t>(GetParam());
+
+    // check type
+    EXPECT_TRUE(j.is_number_integer());
+
+    // create expected byte vector
+    std::vector<uint8_t> expected;
+    expected.push_back(static_cast<uint8_t>(0xce));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 24) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 16) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 8) & 0xff));
+    expected.push_back(static_cast<uint8_t>(GetParam() & 0xff));
+
+    // compare result + size
+    const auto result = json::to_msgpack(j);
+    EXPECT_EQ(result, expected);
+    EXPECT_EQ(result.size(), 5u);
+
+    // check individual bytes
+    EXPECT_EQ(result[0], static_cast<uint8_t>(0xce));
+    uint32_t restored = (static_cast<uint32_t>(static_cast<uint8_t>(result[1])) << 030) +
+                        (static_cast<uint32_t>(static_cast<uint8_t>(result[2])) << 020) +
+                        (static_cast<uint32_t>(static_cast<uint8_t>(result[3])) << 010) +
+                        static_cast<uint32_t>(static_cast<uint8_t>(result[4]));
+    EXPECT_EQ(restored, GetParam());
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+static const uint32_t pos4_numbers[] = {
+    65536u,
+    77777u,
+    1048576u,
+    4294967295u,
+};
+
+INSTANTIATE_TEST_SUITE_P(MessagePackSignedPos4Tests, MessagePackSignedPos4Test,
+                        ::testing::ValuesIn(pos4_numbers));
+
+// 4294967296..9223372036854775807 (int 64)
+class MessagePackSignedPos8Test : public ::testing::TestWithParam<uint64_t> {};
+TEST_P(MessagePackSignedPos8Test, Case)
+{
+    // create JSON value with integer number
+    json j = -1;
+    j.get_ref<int64_t&>() =
+        static_cast<int64_t>(GetParam());
+
+    // check type
+    EXPECT_TRUE(j.is_number_integer());
+
+    // create expected byte vector
+    std::vector<uint8_t> expected;
+    expected.push_back(static_cast<uint8_t>(0xcf));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 070) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 060) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 050) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 040) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 030) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 020) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 010) & 0xff));
+    expected.push_back(static_cast<char>(GetParam() & 0xff));
+
+    // compare result + size
+    const auto result = json::to_msgpack(j);
+    EXPECT_EQ(result, expected);
+    EXPECT_EQ(result.size(), 9u);
+
+    // check individual bytes
+    EXPECT_EQ(result[0], static_cast<uint8_t>(0xcf));
+    uint64_t restored = (static_cast<uint64_t>(static_cast<uint8_t>(result[1])) << 070) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[2])) << 060) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[3])) << 050) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[4])) << 040) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[5])) << 030) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[6])) << 020) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[7])) << 010) +
+                        static_cast<uint64_t>(static_cast<uint8_t>(result[8]));
+    EXPECT_EQ(restored, GetParam());
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+static const uint64_t pos8_numbers[] = {
+    4294967296lu,
+    9223372036854775807lu,
+};
+
+INSTANTIATE_TEST_SUITE_P(MessagePackSignedPos8Tests, MessagePackSignedPos8Test,
+                        ::testing::ValuesIn(pos8_numbers));
+
+// -128..-33 (int 8)
+TEST(MessagePackSignedTest, Neg1)
+{
+    for (auto i = -128; i <= -33; ++i)
+    {
+        SCOPED_TRACE(i);
+
+        // create JSON value with integer number
+        json j = i;
+
+        // check type
+        EXPECT_TRUE(j.is_number_integer());
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(static_cast<uint8_t>(0xd0));
+        expected.push_back(static_cast<uint8_t>(i));
+
+        // compare result + size
+        const auto result = json::to_msgpack(j);
+        EXPECT_EQ(result, expected);
+        EXPECT_EQ(result.size(), 2u);
+
+        // check individual bytes
+        EXPECT_EQ(result[0], static_cast<uint8_t>(0xd0));
+        EXPECT_EQ(static_cast<int8_t>(result[1]), i);
+
+        // roundtrip
+        EXPECT_EQ(json::from_msgpack(result), j);
+    }
+}
+
+// -32768..-129 (int 16)
+TEST(MessagePackSignedTest, Neg2)
+{
+    for (int16_t i = -32768; i <= -129; ++i)
+    {
+        SCOPED_TRACE(i);
+
+        // create JSON value with integer number
+        json j = i;
+
+        // check type
+        EXPECT_TRUE(j.is_number_integer());
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(static_cast<uint8_t>(0xd1));
+        expected.push_back(static_cast<uint8_t>((i >> 8) & 0xff));
+        expected.push_back(static_cast<uint8_t>(i & 0xff));
+
+        // compare result + size
+        const auto result = json::to_msgpack(j);
+        EXPECT_EQ(result, expected);
+        EXPECT_EQ(result.size(), 3u);
+
+        // check individual bytes
+        EXPECT_EQ(result[0], static_cast<uint8_t>(0xd1));
+        int16_t restored = static_cast<int16_t>((static_cast<uint8_t>(result[1]) << 8) +
+                                                static_cast<uint8_t>(result[2]));
+        EXPECT_EQ(restored, i);
+
+        // roundtrip
+        EXPECT_EQ(json::from_msgpack(result), j);
+    }
+}
+
+// -32769..-2147483648
+class MessagePackSignedNeg4Test : public ::testing::TestWithParam<int32_t> {};
+TEST_P(MessagePackSignedNeg4Test, Case)
+{
+    // create JSON value with integer number
+    json j = GetParam();
+
+    // check type
+    EXPECT_TRUE(j.is_number_integer());
+
+    // create expected byte vector
+    std::vector<uint8_t> expected;
+    expected.push_back(static_cast<uint8_t>(0xd2));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 24) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 16) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 8) & 0xff));
+    expected.push_back(static_cast<uint8_t>(GetParam() & 0xff));
+
+    // compare result + size
+    const auto result = json::to_msgpack(j);
+    EXPECT_EQ(result, expected);
+    EXPECT_EQ(result.size(), 5u);
+
+    // check individual bytes
+    EXPECT_EQ(result[0], static_cast<uint8_t>(0xd2));
+    uint32_t restored = (static_cast<uint32_t>(static_cast<uint8_t>(result[1])) << 030) +
+                        (static_cast<uint32_t>(static_cast<uint8_t>(result[2])) << 020) +
+                        (static_cast<uint32_t>(static_cast<uint8_t>(result[3])) << 010) +
+                        static_cast<uint32_t>(static_cast<uint8_t>(result[4]));
+    EXPECT_EQ(static_cast<int32_t>(restored), GetParam());
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+static const int32_t neg4_numbers[] = {
+    -32769,
+    -65536,
+    -77777,
+    -1048576,
+    -2147483648ll,
+};
+
+INSTANTIATE_TEST_SUITE_P(MessagePackSignedNeg4Tests, MessagePackSignedNeg4Test,
+                        ::testing::ValuesIn(neg4_numbers));
+
+// -9223372036854775808..-2147483649 (int 64)
+class MessagePackSignedNeg8Test : public ::testing::TestWithParam<int64_t> {};
+TEST_P(MessagePackSignedNeg8Test, Case)
+{
+    // create JSON value with unsigned integer number
+    json j = GetParam();
+
+    // check type
+    EXPECT_TRUE(j.is_number_integer());
+
+    // create expected byte vector
+    std::vector<uint8_t> expected;
+    expected.push_back(static_cast<uint8_t>(0xd3));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 070) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 060) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 050) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 040) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 030) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 020) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 010) & 0xff));
+    expected.push_back(static_cast<uint8_t>(GetParam() & 0xff));
+
+    // compare result + size
+    const auto result = json::to_msgpack(j);
+    EXPECT_EQ(result, expected);
+    EXPECT_EQ(result.size(), 9u);
+
+    // check individual bytes
+    EXPECT_EQ(result[0], static_cast<uint8_t>(0xd3));
+    int64_t restored = (static_cast<int64_t>(static_cast<uint8_t>(result[1])) << 070) +
+                       (static_cast<int64_t>(static_cast<uint8_t>(result[2])) << 060) +
+                       (static_cast<int64_t>(static_cast<uint8_t>(result[3])) << 050) +
+                       (static_cast<int64_t>(static_cast<uint8_t>(result[4])) << 040) +
+                       (static_cast<int64_t>(static_cast<uint8_t>(result[5])) << 030) +
+                       (static_cast<int64_t>(static_cast<uint8_t>(result[6])) << 020) +
+                       (static_cast<int64_t>(static_cast<uint8_t>(result[7])) << 010) +
+                       static_cast<int64_t>(static_cast<uint8_t>(result[8]));
+    EXPECT_EQ(restored, GetParam());
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+static const int64_t neg8_numbers[] = {
+    INT64_MIN,
+    -2147483649ll,
+};
+
+INSTANTIATE_TEST_SUITE_P(MessagePackSignedNeg8Tests, MessagePackSignedNeg8Test,
+                        ::testing::ValuesIn(neg8_numbers));
+
+// 0..127 (positive fixnum)
+TEST(MessagePackUnsignedTest, Pos0)
+{
+    for (size_t i = 0; i <= 127; ++i)
+    {
+        SCOPED_TRACE(i);
+
+        // create JSON value with unsigned integer number
+        json j = i;
+
+        // check type
+        EXPECT_TRUE(j.is_number_unsigned());
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(static_cast<uint8_t>(i));
+
+        // compare result + size
+        const auto result = json::to_msgpack(j);
+        EXPECT_EQ(result, expected);
+        EXPECT_EQ(result.size(), 1u);
+
+        // check individual bytes
+        EXPECT_EQ(result[0], static_cast<uint8_t>(i));
+
+        // roundtrip
+        EXPECT_EQ(json::from_msgpack(result), j);
+    }
+}
+
+// 128..255 (uint 8)
+TEST(MessagePackUnsignedTest, Pos1)
+{
+    for (size_t i = 128; i <= 255; ++i)
+    {
+        SCOPED_TRACE(i);
+
+        // create JSON value with unsigned integer number
+        json j = i;
+
+        // check type
+        EXPECT_TRUE(j.is_number_unsigned());
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(static_cast<uint8_t>(0xcc));
+        expected.push_back(static_cast<uint8_t>(i));
+
+        // compare result + size
+        const auto result = json::to_msgpack(j);
+        EXPECT_EQ(result, expected);
+        EXPECT_EQ(result.size(), 2u);
+
+        // check individual bytes
+        EXPECT_EQ(result[0], static_cast<uint8_t>(0xcc));
+        uint8_t restored = static_cast<uint8_t>(result[1]);
+        EXPECT_EQ(restored, i);
+
+        // roundtrip
+        EXPECT_EQ(json::from_msgpack(result), j);
+    }
+}
+
+// 256..65535 (uint 16)
+TEST(MessagePackUnsignedTest, Pos2)
+{
+    for (size_t i = 256; i <= 65535; ++i)
+    {
+        SCOPED_TRACE(i);
+
+        // create JSON value with unsigned integer number
+        json j = i;
+
+        // check type
+        EXPECT_TRUE(j.is_number_unsigned());
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(static_cast<uint8_t>(0xcd));
+        expected.push_back(static_cast<uint8_t>((i >> 8) & 0xff));
+        expected.push_back(static_cast<uint8_t>(i & 0xff));
+
+        // compare result + size
+        const auto result = json::to_msgpack(j);
+        EXPECT_EQ(result, expected);
+        EXPECT_EQ(result.size(), 3u);
+
+        // check individual bytes
+        EXPECT_EQ(result[0], static_cast<uint8_t>(0xcd));
+        uint16_t restored = static_cast<uint16_t>(static_cast<uint8_t>(result[1]) * 256 + static_cast<uint8_t>(result[2]));
+        EXPECT_EQ(restored, i);
+
+        // roundtrip
+        EXPECT_EQ(json::from_msgpack(result), j);
+    }
+}
+
+// 65536..4294967295 (uint 32)
+class MessagePackUnsignedPos4Test : public ::testing::TestWithParam<uint32_t> {};
+TEST_P(MessagePackUnsignedPos4Test, Case)
+{
+    // create JSON value with unsigned integer number
+    json j = GetParam();
+
+    // check type
+    EXPECT_TRUE(j.is_number_unsigned());
+
+    // create expected byte vector
+    std::vector<uint8_t> expected;
+    expected.push_back(static_cast<uint8_t>(0xce));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 24) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 16) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 8) & 0xff));
+    expected.push_back(static_cast<uint8_t>(GetParam() & 0xff));
+
+    // compare result + size
+    const auto result = json::to_msgpack(j);
+    EXPECT_EQ(result, expected);
+    EXPECT_EQ(result.size(), 5u);
+
+    // check individual bytes
+    EXPECT_EQ(result[0], static_cast<uint8_t>(0xce));
+    uint32_t restored = (static_cast<uint32_t>(static_cast<uint8_t>(result[1])) << 030) +
+                        (static_cast<uint32_t>(static_cast<uint8_t>(result[2])) << 020) +
+                        (static_cast<uint32_t>(static_cast<uint8_t>(result[3])) << 010) +
+                        static_cast<uint32_t>(static_cast<uint8_t>(result[4]));
+    EXPECT_EQ(restored, GetParam());
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+INSTANTIATE_TEST_SUITE_P(MessagePackUnsignedPos4Tests,
+                        MessagePackUnsignedPos4Test,
+                        ::testing::ValuesIn(pos4_numbers));
+
+// 4294967296..18446744073709551615 (uint 64)
+class MessagePackUnsignedPos8Test : public ::testing::TestWithParam<uint64_t> {};
+TEST_P(MessagePackUnsignedPos8Test, Case)
+{
+    // create JSON value with unsigned integer number
+    json j = GetParam();
+
+    // check type
+    EXPECT_TRUE(j.is_number_unsigned());
+
+    // create expected byte vector
+    std::vector<uint8_t> expected;
+    expected.push_back(static_cast<uint8_t>(0xcf));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 070) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 060) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 050) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 040) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 030) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 020) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 010) & 0xff));
+    expected.push_back(static_cast<uint8_t>(GetParam() & 0xff));
+
+    // compare result + size
+    const auto result = json::to_msgpack(j);
+    EXPECT_EQ(result, expected);
+    EXPECT_EQ(result.size(), 9u);
+
+    // check individual bytes
+    EXPECT_EQ(result[0], static_cast<uint8_t>(0xcf));
+    uint64_t restored = (static_cast<uint64_t>(static_cast<uint8_t>(result[1])) << 070) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[2])) << 060) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[3])) << 050) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[4])) << 040) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[5])) << 030) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[6])) << 020) +
+                        (static_cast<uint64_t>(static_cast<uint8_t>(result[7])) << 010) +
+                        static_cast<uint64_t>(static_cast<uint8_t>(result[8]));
+    EXPECT_EQ(restored, GetParam());
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+INSTANTIATE_TEST_SUITE_P(MessagePackUnsignedPos8Tests,
+                        MessagePackUnsignedPos8Test,
+                        ::testing::ValuesIn(pos8_numbers));
+
+// 3.1415925
+TEST(MessagePackFloatTest, Number)
+{
+    double v = 3.1415925;
+    json j = v;
+    std::vector<uint8_t> expected = {0xcb,0x40,0x09,0x21,0xfb,0x3f,0xa6,0xde,0xfc};
+    const auto result = json::to_msgpack(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+    EXPECT_EQ(json::from_msgpack(result), v);
+}
+
+// N = 0..31
+TEST(MessagePackStringTest, String1)
+{
+    // explicitly enumerate the first byte for all 32 strings
+    const std::vector<uint8_t> first_bytes =
+    {
+        0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+        0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1,
+        0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba,
+        0xbb, 0xbc, 0xbd, 0xbe, 0xbf
+    };
+
+    for (size_t N = 0; N < first_bytes.size(); ++N)
+    {
+        SCOPED_TRACE(N);
+
+        // create JSON value with string containing of N * 'x'
+        const auto s = std::string(N, 'x');
+        json j = s;
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(static_cast<uint8_t>(first_bytes[N]));
+        for (size_t i = 0; i < N; ++i)
+        {
+            expected.push_back('x');
+        }
+
+        // check first byte
+        EXPECT_EQ((first_bytes[N] & 0x1f), static_cast<uint8_t>(N));
+
+        // compare result + size
+        const auto result = json::to_msgpack(j);
+        EXPECT_EQ(result, expected);
+        EXPECT_EQ(result.size(), N + 1);
+        // check that no null byte is appended
+        if (N > 0)
+        {
+            EXPECT_NE(result.back(), '\x00');
+        }
+
+        // roundtrip
+        EXPECT_EQ(json::from_msgpack(result), j);
+    }
+}
+
+// N = 32..255
+TEST(MessagePackStringTest, String2)
+{
+    for (size_t N = 32; N <= 255; ++N)
+    {
+        SCOPED_TRACE(N);
+
+        // create JSON value with string containing of N * 'x'
+        const auto s = std::string(N, 'x');
+        json j = s;
+
+        // create expected byte vector
+        std::vector<uint8_t> expected;
+        expected.push_back(static_cast<uint8_t>(0xd9));
+        expected.push_back(static_cast<uint8_t>(N));
+        for (size_t i = 0; i < N; ++i)
+        {
+            expected.push_back('x');
+        }
+
+        // compare result + size
+        const auto result = json::to_msgpack(j);
+        EXPECT_EQ(result, expected);
+        EXPECT_EQ(result.size(), N + 2);
+        // check that no null byte is appended
+        EXPECT_NE(result.back(), '\x00');
+
+        // roundtrip
+        EXPECT_EQ(json::from_msgpack(result), j);
+    }
+}
+
+// N = 256..65535
+class MessagePackString3Test : public ::testing::TestWithParam<size_t> {};
+TEST_P(MessagePackString3Test, Case)
+{
+    // create JSON value with string containing of N * 'x'
+    const auto s = std::string(GetParam(), 'x');
+    json j = s;
+
+    // create expected byte vector (hack: create string first)
+    std::vector<uint8_t> expected;
+    expected.push_back(static_cast<uint8_t>(0xda));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 8) & 0xff));
+    expected.push_back(static_cast<uint8_t>(GetParam() & 0xff));
+    for (size_t i = 0; i < GetParam(); ++i)
+    {
+        expected.push_back('x');
+    }
+
+    // compare result + size
+    const auto result = json::to_msgpack(j);
+    EXPECT_EQ(result, expected);
+    EXPECT_EQ(result.size(), GetParam() + 3);
+    // check that no null byte is appended
+    EXPECT_NE(result.back(), '\x00');
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+static size_t string3_lens[] = {
+    256u,
+    999u,
+    1025u,
+    3333u,
+    2048u,
+    65535u
+};
+
+INSTANTIATE_TEST_SUITE_P(MessagePackString3Tests, MessagePackString3Test,
+                        ::testing::ValuesIn(string3_lens));
+
+
+// N = 65536..4294967295
+class MessagePackString5Test : public ::testing::TestWithParam<size_t> {};
+TEST_P(MessagePackString5Test, Case)
+{
+    // create JSON value with string containing of N * 'x'
+    const auto s = std::string(GetParam(), 'x');
+    json j = s;
+
+    // create expected byte vector (hack: create string first)
+    std::vector<uint8_t> expected;
+    expected.push_back(static_cast<uint8_t>(0xdb));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 24) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 16) & 0xff));
+    expected.push_back(static_cast<uint8_t>((GetParam() >> 8) & 0xff));
+    expected.push_back(static_cast<uint8_t>(GetParam() & 0xff));
+    for (size_t i = 0; i < GetParam(); ++i)
+    {
+        expected.push_back('x');
+    }
+
+    // compare result + size
+    const auto result = json::to_msgpack(j);
+    EXPECT_EQ(result, expected);
+    EXPECT_EQ(result.size(), GetParam() + 5);
+    // check that no null byte is appended
+    EXPECT_NE(result.back(), '\x00');
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+static size_t string5_lens[] = {
+    65536u,
+    77777u,
+    1048576u
+};
+
+INSTANTIATE_TEST_SUITE_P(MessagePackString5Tests, MessagePackString5Test,
+                        ::testing::ValuesIn(string5_lens));
+
+TEST(MessagePackArrayTest, Empty)
+{
+    json j = json::array();
+    std::vector<uint8_t> expected = {0x90};
+    const auto result = json::to_msgpack(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+// [null]
+TEST(MessagePackArrayTest, Null)
+{
+    json j = {nullptr};
+    std::vector<uint8_t> expected = {0x91,0xc0};
+    const auto result = json::to_msgpack(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+// [1,2,3,4,5]
+TEST(MessagePackArrayTest, Simple)
+{
+    json j = json::parse("[1,2,3,4,5]");
+    std::vector<uint8_t> expected = {0x95,0x01,0x02,0x03,0x04,0x05};
+    const auto result = json::to_msgpack(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+// [[[[]]]]
+TEST(MessagePackArrayTest, NestEmpty)
+{
+    json j = json::parse("[[[[]]]]");
+    std::vector<uint8_t> expected = {0x91,0x91,0x91,0x90};
+    const auto result = json::to_msgpack(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+// array 16
+TEST(MessagePackArrayTest, UInt16)
+{
+    json j(16, nullptr);
+    std::vector<uint8_t> expected(j.size() + 3, static_cast<uint8_t>(0xc0)); // all null
+    expected[0] = static_cast<uint8_t>(0xdc); // array 16
+    expected[1] = 0x00; // size (0x0010), byte 0
+    expected[2] = 0x10; // size (0x0010), byte 1
+    const auto result = json::to_msgpack(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+// array 32
+TEST(MessagePackArrayTest, UInt32)
+{
+    json j(65536, nullptr);
+    std::vector<uint8_t> expected(j.size() + 5, static_cast<uint8_t>(0xc0)); // all null
+    expected[0] = static_cast<uint8_t>(0xdd); // array 32
+    expected[1] = 0x00; // size (0x00100000), byte 0
+    expected[2] = 0x01; // size (0x00100000), byte 1
+    expected[3] = 0x00; // size (0x00100000), byte 2
+    expected[4] = 0x00; // size (0x00100000), byte 3
+    const auto result = json::to_msgpack(j);
+    //EXPECT_EQ(result, expected);
+
+    EXPECT_EQ(result.size(), expected.size());
+    for (size_t i = 0; i < expected.size(); ++i)
+    {
+        SCOPED_TRACE(i);
+        EXPECT_EQ(result[i], expected[i]);
+    }
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+TEST(MessagePackObjectTest, Empty)
+{
+    json j = json::object();
+    std::vector<uint8_t> expected = {0x80};
+    const auto result = json::to_msgpack(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+// {"":null}
+TEST(MessagePackObjectTest, EmptyKey)
+{
+    json j = {{"", nullptr}};
+    std::vector<uint8_t> expected = {0x81,0xa0,0xc0};
+    const auto result = json::to_msgpack(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+// {"a": {"b": {"c": {}}}}
+TEST(MessagePackObjectTest, NestedEmpty)
+{
+    json j = json::parse("{\"a\": {\"b\": {\"c\": {}}}}");
+    std::vector<uint8_t> expected = {0x81,0xa1,0x61,0x81,0xa1,0x62,0x81,0xa1,0x63,0x80};
+    const auto result = json::to_msgpack(j);
+    EXPECT_EQ(result, expected);
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+// map 16
+TEST(MessagePackObjectTest, UInt16)
+{
+    json j = R"({"00": null, "01": null, "02": null, "03": null,
+                 "04": null, "05": null, "06": null, "07": null,
+                 "08": null, "09": null, "10": null, "11": null,
+                 "12": null, "13": null, "14": null, "15": null})"_json;
+
+    const auto result = json::to_msgpack(j);
+
+    // Checking against an expected vector byte by byte is
+    // difficult, because no assumption on the order of key/value
+    // pairs are made. We therefore only check the prefix (type and
+    // size and the overall size. The rest is then handled in the
+    // roundtrip check.
+    EXPECT_EQ(result.size(), 67u); // 1 type, 2 size, 16*4 content
+    EXPECT_EQ(result[0], static_cast<uint8_t>(0xde)); // map 16
+    EXPECT_EQ(result[1], 0x00); // byte 0 of size (0x0010)
+    EXPECT_EQ(result[2], 0x10); // byte 1 of size (0x0010)
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+// map 32
+TEST(MessagePackObjectTest, UInt32)
+{
+    json j;
+    for (auto i = 0; i < 65536; ++i)
+    {
+        // format i to a fixed width of 5
+        // each entry will need 7 bytes: 6 for fixstr, 1 for null
+        std::stringstream ss;
+        ss << std::setw(5) << std::setfill('0') << i;
+        j.emplace(ss.str(), nullptr);
+    }
+
+    const auto result = json::to_msgpack(j);
+
+    // Checking against an expected vector byte by byte is
+    // difficult, because no assumption on the order of key/value
+    // pairs are made. We therefore only check the prefix (type and
+    // size and the overall size. The rest is then handled in the
+    // roundtrip check.
+    EXPECT_EQ(result.size(), 458757u); // 1 type, 4 size, 65536*7 content
+    EXPECT_EQ(result[0], static_cast<uint8_t>(0xdf)); // map 32
+    EXPECT_EQ(result[1], 0x00); // byte 0 of size (0x00010000)
+    EXPECT_EQ(result[2], 0x01); // byte 1 of size (0x00010000)
+    EXPECT_EQ(result[3], 0x00); // byte 2 of size (0x00010000)
+    EXPECT_EQ(result[4], 0x00); // byte 3 of size (0x00010000)
+
+    // roundtrip
+    EXPECT_EQ(json::from_msgpack(result), j);
+}
+
+// from float32
+TEST(MessagePackFloat32Test, Case)
+{
+    auto given = std::vector<uint8_t>({0xca,0x41,0xc8,0x00,0x01});
+    json j = json::from_msgpack(given);
+    EXPECT_LT(std::fabs(j.get<double>() - 25), 0.001);
+}
+
+TEST(MessagePackErrorTest, TooShortByteVector)
+{
+    EXPECT_THROW_MSG(json::from_msgpack(std::vector<uint8_t>({0xcc})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 2: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_msgpack(std::vector<uint8_t>({0xcd})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 2: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_msgpack(std::vector<uint8_t>({0xcd,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 3: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_msgpack(std::vector<uint8_t>({0xce})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 2: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_msgpack(std::vector<uint8_t>({0xce,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 3: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_msgpack(std::vector<uint8_t>({0xce,0x00,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 4: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_msgpack(std::vector<uint8_t>({0xce,0x00,0x00,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 5: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_msgpack(std::vector<uint8_t>({0xcf})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 2: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_msgpack(std::vector<uint8_t>({0xcf,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 3: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_msgpack(std::vector<uint8_t>({0xcf,0x00,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 4: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_msgpack(std::vector<uint8_t>({0xcf,0x00,0x00,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 5: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_msgpack(std::vector<uint8_t>({0xcf,0x00,0x00,0x00,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 6: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_msgpack(std::vector<uint8_t>({0xcf,0x00,0x00,0x00,0x00,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 7: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_msgpack(std::vector<uint8_t>({0xcf,0x00,0x00,0x00,0x00,0x00,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 8: unexpected end of input");
+    EXPECT_THROW_MSG(json::from_msgpack(std::vector<uint8_t>({0xcf,0x00,0x00,0x00,0x00,0x00,0x00,0x00})), json::parse_error,
+                     "[json.exception.parse_error.110] parse error at 9: unexpected end of input");
+}
+
+TEST(MessagePackErrorTest, UnsupportedBytesConcrete)
+{
+    EXPECT_THROW_MSG(json::from_msgpack(std::vector<uint8_t>({0xc1})), json::parse_error,
+                     "[json.exception.parse_error.112] parse error at 1: error reading MessagePack; last byte: 0xc1");
+    EXPECT_THROW_MSG(json::from_msgpack(std::vector<uint8_t>({0xc6})), json::parse_error,
+                     "[json.exception.parse_error.112] parse error at 1: error reading MessagePack; last byte: 0xc6");
+}
+
+TEST(MessagePackErrorTest, UnsupportedBytesAll)
+{
+    for (auto byte :
+            {
+                // never used
+                0xc1,
+                // bin
+                0xc4, 0xc5, 0xc6,
+                // ext
+                0xc7, 0xc8, 0xc9,
+                // fixext
+                0xd4, 0xd5, 0xd6, 0xd7, 0xd8
+            })
+    {
+        EXPECT_THROW(json::from_msgpack(std::vector<uint8_t>({static_cast<uint8_t>(byte)})), json::parse_error);
+    }
+}
+#if 0
+// use this testcase outside [hide] to run it with Valgrind
+TEST(MessagePackRoundtripTest, Sample)
+{
+    std::string filename = "test/data/json_testsuite/sample.json";
+
+    // parse JSON file
+    std::ifstream f_json(filename);
+    json j1 = json::parse(f_json);
+
+    // parse MessagePack file
+    std::ifstream f_msgpack(filename + ".msgpack", std::ios::binary);
+    std::vector<uint8_t> packed((std::istreambuf_iterator<char>(f_msgpack)),
+                                std::istreambuf_iterator<char>());
+    json j2;
+    EXPECT_NO_THROW(j2 = json::from_msgpack(packed));
+
+    // compare parsed JSON values
+    EXPECT_EQ(j1, j2);
+
+    // check with different start index
+    packed.insert(packed.begin(), 5, 0xff);
+    EXPECT_EQ(j1, json::from_msgpack(packed, 5));
+}
+
+class MessagePackRegressionTest : public ::testing::TestWithParam<const char*> {};
+TEST_P(MessagePackRegressionTest, Case)
+{
+    // parse JSON file
+    std::ifstream f_json(GetParam());
+    json j1 = json::parse(f_json);
+
+    // parse MessagePack file
+    std::ifstream f_msgpack(filename + ".msgpack", std::ios::binary);
+    std::vector<uint8_t> packed((std::istreambuf_iterator<char>(f_msgpack)),
+                                std::istreambuf_iterator<char>());
+    json j2;
+    EXPECT_NO_THROW(j2 = json::from_msgpack(packed));
+
+    // compare parsed JSON values
+    EXPECT_EQ(j1, j2);
+}
+
+static const char* regression_test_cases[] = {
+    "test/data/json_nlohmann_tests/all_unicode.json",
+    "test/data/json.org/1.json",
+    "test/data/json.org/2.json",
+    "test/data/json.org/3.json",
+    "test/data/json.org/4.json",
+    "test/data/json.org/5.json",
+    "test/data/json_roundtrip/roundtrip01.json",
+    "test/data/json_roundtrip/roundtrip02.json",
+    "test/data/json_roundtrip/roundtrip03.json",
+    "test/data/json_roundtrip/roundtrip04.json",
+    "test/data/json_roundtrip/roundtrip05.json",
+    "test/data/json_roundtrip/roundtrip06.json",
+    "test/data/json_roundtrip/roundtrip07.json",
+    "test/data/json_roundtrip/roundtrip08.json",
+    "test/data/json_roundtrip/roundtrip09.json",
+    "test/data/json_roundtrip/roundtrip10.json",
+    "test/data/json_roundtrip/roundtrip11.json",
+    "test/data/json_roundtrip/roundtrip12.json",
+    "test/data/json_roundtrip/roundtrip13.json",
+    "test/data/json_roundtrip/roundtrip14.json",
+    "test/data/json_roundtrip/roundtrip15.json",
+    "test/data/json_roundtrip/roundtrip16.json",
+    "test/data/json_roundtrip/roundtrip17.json",
+    "test/data/json_roundtrip/roundtrip18.json",
+    "test/data/json_roundtrip/roundtrip19.json",
+    "test/data/json_roundtrip/roundtrip20.json",
+    "test/data/json_roundtrip/roundtrip21.json",
+    "test/data/json_roundtrip/roundtrip22.json",
+    "test/data/json_roundtrip/roundtrip23.json",
+    "test/data/json_roundtrip/roundtrip24.json",
+    "test/data/json_roundtrip/roundtrip25.json",
+    "test/data/json_roundtrip/roundtrip26.json",
+    "test/data/json_roundtrip/roundtrip27.json",
+    "test/data/json_roundtrip/roundtrip28.json",
+    "test/data/json_roundtrip/roundtrip29.json",
+    "test/data/json_roundtrip/roundtrip30.json",
+    "test/data/json_roundtrip/roundtrip31.json",
+    "test/data/json_roundtrip/roundtrip32.json",
+    "test/data/json_testsuite/sample.json", // kills AppVeyor
+    "test/data/json_tests/pass1.json",
+    "test/data/json_tests/pass2.json",
+    "test/data/json_tests/pass3.json",
+    "test/data/regression/floats.json",
+    "test/data/regression/signed_ints.json",
+    "test/data/regression/unsigned_ints.json",
+    "test/data/regression/working_file.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_arraysWithSpaces.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_empty-string.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_empty.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_ending_with_newline.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_false.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_heterogeneous.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_null.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_with_1_and_newline.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_with_leading_space.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_with_several_null.json",
+    "test/data/nst_json_testsuite/test_parsing/y_array_with_trailing_space.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_0e+1.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_0e1.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_after_space.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_double_close_to_zero.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_double_huge_neg_exp.json",
+    //"test/data/nst_json_testsuite/test_parsing/y_number_huge_exp.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_int_with_exp.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_minus_zero.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_negative_int.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_negative_one.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_negative_zero.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_real_capital_e.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_real_capital_e_neg_exp.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_real_capital_e_pos_exp.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_real_exponent.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_real_fraction_exponent.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_real_neg_exp.json",
+    //"test/data/nst_json_testsuite/test_parsing/y_number_real_neg_overflow.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_real_pos_exponent.json",
+    //"test/data/nst_json_testsuite/test_parsing/y_number_real_pos_overflow.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_real_underflow.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_simple_int.json",
+    "test/data/nst_json_testsuite/test_parsing/y_number_simple_real.json",
+    //"test/data/nst_json_testsuite/test_parsing/y_number_too_big_neg_int.json",
+    //"test/data/nst_json_testsuite/test_parsing/y_number_too_big_pos_int.json",
+    //"test/data/nst_json_testsuite/test_parsing/y_number_very_big_negative_int.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_basic.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_duplicated_key.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_duplicated_key_and_value.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_empty.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_empty_key.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_escaped_null_in_key.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_extreme_numbers.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_long_strings.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_simple.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_string_unicode.json",
+    "test/data/nst_json_testsuite/test_parsing/y_object_with_newlines.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_UTF-16_Surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pair.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pairs.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_allowed_escapes.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_backslash_and_u_escaped_zero.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_backslash_doublequotes.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_comments.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_double_escape_a.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_double_escape_n.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_escaped_control_character.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_escaped_noncharacter.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_in_array.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_in_array_with_leading_space.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_last_surrogates_1_and_2.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_newline_uescaped.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+1FFFF.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_null_escape.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_one-byte-utf-8.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_pi.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_simple_ascii.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_space.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_three-byte-utf-8.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_two-byte-utf-8.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_u+2028_line_sep.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_u+2029_par_sep.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_uEscape.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_unescaped_char_delete.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_unicode.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_unicodeEscapedBackslash.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_unicode_2.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_unicode_U+2064_invisible_plus.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_unicode_escaped_double_quote.json",
+    // "test/data/nst_json_testsuite/test_parsing/y_string_utf16.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_utf8.json",
+    "test/data/nst_json_testsuite/test_parsing/y_string_with_del_character.json",
+    "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_false.json",
+    "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_int.json",
+    "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_negative_real.json",
+    "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_null.json",
+    "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_string.json",
+    "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_true.json",
+    "test/data/nst_json_testsuite/test_parsing/y_structure_string_empty.json",
+    "test/data/nst_json_testsuite/test_parsing/y_structure_trailing_newline.json",
+    "test/data/nst_json_testsuite/test_parsing/y_structure_true_in_array.json",
+    "test/data/nst_json_testsuite/test_parsing/y_structure_whitespace_array.json",
+};
+
+INSTANTIATE_TEST_SUITE_P(MessagePackRegressionTests, MessagePackRegressionTest,
+                        ::testing::ValuesIn(regression_test_cases));
+#endif
diff --git a/wpiutil/src/test/native/cpp/json/unit-pointer_access.cpp b/wpiutil/src/test/native/cpp/json/unit-pointer_access.cpp
new file mode 100644
index 0000000..a1a7fa4
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-pointer_access.cpp
@@ -0,0 +1,463 @@
+/*----------------------------------------------------------------------------*/
+/* Modifications Copyright (c) FIRST 2017. All Rights Reserved.               */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include "unit-json.h"
+using wpi::json;
+
+TEST(JsonPointerTest, TypesCreate)
+{
+    // create a JSON value with different types
+    json json_types =
+    {
+        {"boolean", true},
+        {
+            "number", {
+                {"integer", 42},
+                {"unsigned", 42u},
+                {"floating-point", 17.23}
+            }
+        },
+        {"string", "Hello, world!"},
+        {"array", {1, 2, 3, 4, 5}},
+        {"null", nullptr}
+    };
+}
+
+// pointer access to object_t
+TEST(JsonPointerTest, ObjectT)
+{
+    using test_type = json::object_t;
+    json value = {{"one", 1}, {"two", 2}};
+
+    // check if pointers are returned correctly
+    test_type* p1 = value.get_ptr<test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<test_type*>());
+    EXPECT_EQ(*p1, value.get<test_type>());
+
+    const test_type* p2 = value.get_ptr<const test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type*>());
+    EXPECT_EQ(*p2, value.get<test_type>());
+
+    const test_type* const p3 = value.get_ptr<const test_type* const>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type* const>());
+    EXPECT_EQ(*p3, value.get<test_type>());
+
+    // check if null pointers are returned correctly
+    EXPECT_NE(value.get_ptr<json::object_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<json::array_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<std::string*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<bool*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<int64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<uint64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<double*>(), nullptr);
+}
+
+// pointer access to const object_t
+TEST(JsonPointerTest, ConstObjectT)
+{
+    using test_type = const json::object_t;
+    const json value = {{"one", 1}, {"two", 2}};
+
+    // check if pointers are returned correctly
+    test_type* p1 = value.get_ptr<test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<test_type*>());
+    EXPECT_EQ(*p1, value.get<test_type>());
+
+    const test_type* p2 = value.get_ptr<const test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type*>());
+    EXPECT_EQ(*p2, value.get<test_type>());
+
+    const test_type* const p3 = value.get_ptr<const test_type* const>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type* const>());
+    EXPECT_EQ(*p3, value.get<test_type>());
+
+    // check if null pointers are returned correctly
+    EXPECT_NE(value.get_ptr<const json::object_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const json::array_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const std::string*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const bool*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const int64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const uint64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const double*>(), nullptr);
+}
+
+// pointer access to array_t
+TEST(JsonPointerTest, ArrayT)
+{
+    using test_type = json::array_t;
+    json value = {1, 2, 3, 4};
+
+    // check if pointers are returned correctly
+    test_type* p1 = value.get_ptr<test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<test_type*>());
+    EXPECT_EQ(*p1, value.get<test_type>());
+
+    const test_type* p2 = value.get_ptr<const test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type*>());
+    EXPECT_EQ(*p2, value.get<test_type>());
+
+    const test_type* const p3 = value.get_ptr<const test_type* const>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type* const>());
+    EXPECT_EQ(*p3, value.get<test_type>());
+
+    // check if null pointers are returned correctly
+    EXPECT_EQ(value.get_ptr<json::object_t*>(), nullptr);
+    EXPECT_NE(value.get_ptr<json::array_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<std::string*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<bool*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<int64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<uint64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<double*>(), nullptr);
+}
+
+// pointer access to const array_t
+TEST(JsonPointerTest, ConstArrayT)
+{
+    using test_type = const json::array_t;
+    const json value = {1, 2, 3, 4};
+
+    // check if pointers are returned correctly
+    test_type* p1 = value.get_ptr<test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<test_type*>());
+    EXPECT_EQ(*p1, value.get<test_type>());
+
+    const test_type* p2 = value.get_ptr<const test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type*>());
+    EXPECT_EQ(*p2, value.get<test_type>());
+
+    const test_type* const p3 = value.get_ptr<const test_type* const>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type* const>());
+    EXPECT_EQ(*p3, value.get<test_type>());
+
+    // check if null pointers are returned correctly
+    EXPECT_EQ(value.get_ptr<const json::object_t*>(), nullptr);
+    EXPECT_NE(value.get_ptr<const json::array_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const std::string*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const bool*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const int64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const uint64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const double*>(), nullptr);
+}
+
+// pointer access to string_t
+TEST(JsonPointerTest, StringT)
+{
+    using test_type = std::string;
+    json value = "hello";
+
+    // check if pointers are returned correctly
+    test_type* p1 = value.get_ptr<test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<test_type*>());
+    EXPECT_EQ(*p1, value.get<test_type>());
+
+    const test_type* p2 = value.get_ptr<const test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type*>());
+    EXPECT_EQ(*p2, value.get<test_type>());
+
+    const test_type* const p3 = value.get_ptr<const test_type* const>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type* const>());
+    EXPECT_EQ(*p3, value.get<test_type>());
+
+    // check if null pointers are returned correctly
+    EXPECT_EQ(value.get_ptr<json::object_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<json::array_t*>(), nullptr);
+    EXPECT_NE(value.get_ptr<std::string*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<bool*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<int64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<uint64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<double*>(), nullptr);
+}
+
+// pointer access to const string_t
+TEST(JsonPointerTest, ConstStringT)
+{
+    using test_type = const std::string;
+    const json value = "hello";
+
+    // check if pointers are returned correctly
+    test_type* p1 = value.get_ptr<test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<test_type*>());
+    EXPECT_EQ(*p1, value.get<test_type>());
+
+    const test_type* p2 = value.get_ptr<const test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type*>());
+    EXPECT_EQ(*p2, value.get<test_type>());
+
+    const test_type* const p3 = value.get_ptr<const test_type* const>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type* const>());
+    EXPECT_EQ(*p3, value.get<test_type>());
+
+    // check if null pointers are returned correctly
+    EXPECT_EQ(value.get_ptr<const json::object_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const json::array_t*>(), nullptr);
+    EXPECT_NE(value.get_ptr<const std::string*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const bool*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const int64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const uint64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const double*>(), nullptr);
+}
+
+// pointer access to boolean_t
+TEST(JsonPointerTest, BooleanT)
+{
+    using test_type = bool;
+    json value = false;
+
+    // check if pointers are returned correctly
+    test_type* p1 = value.get_ptr<test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<test_type*>());
+    EXPECT_EQ(*p1, value.get<test_type>());
+
+    const test_type* p2 = value.get_ptr<const test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type*>());
+    EXPECT_EQ(*p2, value.get<test_type>());
+
+    const test_type* const p3 = value.get_ptr<const test_type* const>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type* const>());
+    EXPECT_EQ(*p3, value.get<test_type>());
+
+    // check if null pointers are returned correctly
+    EXPECT_EQ(value.get_ptr<json::object_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<json::array_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<std::string*>(), nullptr);
+    EXPECT_NE(value.get_ptr<bool*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<int64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<uint64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<double*>(), nullptr);
+}
+
+// pointer access to const boolean_t
+TEST(JsonPointerTest, ConstBooleanT)
+{
+    using test_type = const bool;
+    const json value = false;
+
+    // check if pointers are returned correctly
+    test_type* p1 = value.get_ptr<test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<test_type*>());
+    //EXPECT_EQ(*p1, value.get<test_type>());
+
+    //const test_type* p2 = value.get_ptr<const test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type*>());
+    //EXPECT_EQ(*p2, value.get<test_type>());
+
+    //const test_type* const p3 = value.get_ptr<const test_type* const>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type* const>());
+    //EXPECT_EQ(*p3, value.get<test_type>());
+
+    // check if null pointers are returned correctly
+    EXPECT_EQ(value.get_ptr<const json::object_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const json::array_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const std::string*>(), nullptr);
+    EXPECT_NE(value.get_ptr<const bool*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const int64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const uint64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const double*>(), nullptr);
+}
+
+// pointer access to number_integer_t
+TEST(JsonPointerTest, IntegerT)
+{
+    using test_type = int64_t;
+    json value = 23;
+
+    // check if pointers are returned correctly
+    test_type* p1 = value.get_ptr<test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<test_type*>());
+    EXPECT_EQ(*p1, value.get<test_type>());
+
+    const test_type* p2 = value.get_ptr<const test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type*>());
+    EXPECT_EQ(*p2, value.get<test_type>());
+
+    const test_type* const p3 = value.get_ptr<const test_type* const>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type* const>());
+    EXPECT_EQ(*p3, value.get<test_type>());
+
+    // check if null pointers are returned correctly
+    EXPECT_EQ(value.get_ptr<json::object_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<json::array_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<std::string*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<bool*>(), nullptr);
+    EXPECT_NE(value.get_ptr<int64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<uint64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<double*>(), nullptr);
+}
+
+// pointer access to const number_integer_t
+TEST(JsonPointerTest, ConstIntegerT)
+{
+    using test_type = const int64_t;
+    const json value = 23;
+
+    // check if pointers are returned correctly
+    test_type* p1 = value.get_ptr<test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<test_type*>());
+    EXPECT_EQ(*p1, value.get<test_type>());
+
+    const test_type* p2 = value.get_ptr<const test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type*>());
+    EXPECT_EQ(*p2, value.get<test_type>());
+
+    const test_type* const p3 = value.get_ptr<const test_type* const>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type* const>());
+    EXPECT_EQ(*p3, value.get<test_type>());
+
+    // check if null pointers are returned correctly
+    EXPECT_EQ(value.get_ptr<const json::object_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const json::array_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const std::string*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const bool*>(), nullptr);
+    EXPECT_NE(value.get_ptr<const int64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const uint64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const double*>(), nullptr);
+}
+
+// pointer access to number_unsigned_t
+TEST(JsonPointerTest, UnsignedT)
+{
+    using test_type = uint64_t;
+    json value = 23u;
+
+    // check if pointers are returned correctly
+    test_type* p1 = value.get_ptr<test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<test_type*>());
+    EXPECT_EQ(*p1, value.get<test_type>());
+
+    const test_type* p2 = value.get_ptr<const test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type*>());
+    EXPECT_EQ(*p2, value.get<test_type>());
+
+    const test_type* const p3 = value.get_ptr<const test_type* const>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type* const>());
+    EXPECT_EQ(*p3, value.get<test_type>());
+
+    // check if null pointers are returned correctly
+    EXPECT_EQ(value.get_ptr<json::object_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<json::array_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<std::string*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<bool*>(), nullptr);
+    EXPECT_NE(value.get_ptr<int64_t*>(), nullptr);
+    EXPECT_NE(value.get_ptr<uint64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<double*>(), nullptr);
+}
+
+// pointer access to const number_unsigned_t
+TEST(JsonPointerTest, ConstUnsignedT)
+{
+    using test_type = const uint64_t;
+    const json value = 23u;
+
+    // check if pointers are returned correctly
+    test_type* p1 = value.get_ptr<test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<test_type*>());
+    EXPECT_EQ(*p1, value.get<test_type>());
+
+    const test_type* p2 = value.get_ptr<const test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type*>());
+    EXPECT_EQ(*p2, value.get<test_type>());
+
+    const test_type* const p3 = value.get_ptr<const test_type* const>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type* const>());
+    EXPECT_EQ(*p3, value.get<test_type>());
+
+    // check if null pointers are returned correctly
+    EXPECT_EQ(value.get_ptr<const json::object_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const json::array_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const std::string*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const bool*>(), nullptr);
+    EXPECT_NE(value.get_ptr<const int64_t*>(), nullptr);
+    EXPECT_NE(value.get_ptr<const uint64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const double*>(), nullptr);
+}
+
+// pointer access to number_float_t
+TEST(JsonPointerTest, FloatT)
+{
+    using test_type = double;
+    json value = 42.23;
+
+    // check if pointers are returned correctly
+    test_type* p1 = value.get_ptr<test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<test_type*>());
+    EXPECT_LT(std::fabs(*p1 - value.get<test_type>()), 0.001);
+
+    const test_type* p2 = value.get_ptr<const test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type*>());
+    EXPECT_LT(std::fabs(*p2 - value.get<test_type>()), 0.001);
+
+    const test_type* const p3 = value.get_ptr<const test_type* const>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type* const>());
+    EXPECT_LT(std::fabs(*p3 - value.get<test_type>()), 0.001);
+
+    // check if null pointers are returned correctly
+    EXPECT_EQ(value.get_ptr<json::object_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<json::array_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<std::string*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<bool*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<int64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<uint64_t*>(), nullptr);
+    EXPECT_NE(value.get_ptr<double*>(), nullptr);
+}
+
+// pointer access to const number_float_t
+TEST(JsonPointerTest, ConstFloatT)
+{
+    using test_type = const double;
+    const json value = 42.23;
+
+    // check if pointers are returned correctly
+    test_type* p1 = value.get_ptr<test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<test_type*>());
+    EXPECT_LT(std::fabs(*p1 - value.get<test_type>()), 0.001);
+
+    const test_type* p2 = value.get_ptr<const test_type*>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type*>());
+    EXPECT_LT(std::fabs(*p2 - value.get<test_type>()), 0.001);
+
+    const test_type* const p3 = value.get_ptr<const test_type* const>();
+    EXPECT_EQ(p1, value.get_ptr<const test_type* const>());
+    EXPECT_LT(std::fabs(*p3 - value.get<test_type>()), 0.001);
+
+    // check if null pointers are returned correctly
+    EXPECT_EQ(value.get_ptr<const json::object_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const json::array_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const std::string*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const bool*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const int64_t*>(), nullptr);
+    EXPECT_EQ(value.get_ptr<const uint64_t*>(), nullptr);
+    EXPECT_NE(value.get_ptr<const double*>(), nullptr);
+}
diff --git a/wpiutil/src/test/native/cpp/json/unit-readme.cpp b/wpiutil/src/test/native/cpp/json/unit-readme.cpp
new file mode 100644
index 0000000..ecb7f79
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-readme.cpp
@@ -0,0 +1,327 @@
+/*----------------------------------------------------------------------------*/
+/* Modifications Copyright (c) FIRST 2017. All Rights Reserved.               */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include "unit-json.h"
+using wpi::json;
+
+#include <array>
+#include <deque>
+#include <forward_list>
+#include <list>
+#include <map>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "wpi/raw_ostream.h"
+
+TEST(JsonReadmeTest, Basic)
+{
+    // create an empty structure (null)
+    json j;
+
+    // add a number that is stored as double (note the implicit conversion of j to an object)
+    j["pi"] = 3.141;
+
+    // add a Boolean that is stored as bool
+    j["happy"] = true;
+
+    // add a string that is stored as std::string
+    j["name"] = "Niels";
+
+    // add another null object by passing nullptr
+    j["nothing"] = nullptr;
+
+    // add an object inside the object
+    j["answer"]["everything"] = 42;
+
+    // add an array that is stored as std::vector (using an initializer list)
+    j["list"] = { 1, 0, 2 };
+
+    // add another object (using an initializer list of pairs)
+    j["object"] = { {"currency", "USD"}, {"value", 42.99} };
+
+    // instead, you could also write (which looks very similar to the JSON above)
+    json j2 =
+    {
+        {"pi", 3.141},
+        {"happy", true},
+        {"name", "Niels"},
+        {"nothing", nullptr},
+        {
+            "answer", {
+                {"everything", 42}
+            }
+        },
+        {"list", {1, 0, 2}},
+        {
+            "object", {
+                {"currency", "USD"},
+                {"value", 42.99}
+            }
+        }
+    };
+}
+
+TEST(JsonReadmeTest, Other)
+{
+    // ways to express the empty array []
+    json empty_array_implicit = {{}};
+    json empty_array_explicit = json::array();
+
+    // a way to express the empty object {}
+    json empty_object_explicit = json::object();
+
+    // a way to express an _array_ of key/value pairs [["currency", "USD"], ["value", 42.99]]
+    json array_not_object = { json::array({"currency", "USD"}), json::array({"value", 42.99}) };
+}
+
+TEST(JsonReadmeTest, FromToString)
+{
+    // create object from string literal
+    json j = "{ \"happy\": true, \"pi\": 3.141 }"_json;
+
+    // or even nicer with a raw string literal
+    auto j2 = R"(
+  {
+    "happy": true,
+    "pi": 3.141
+  }
+)"_json;
+
+    // or explicitly
+    auto j3 = json::parse("{ \"happy\": true, \"pi\": 3.141 }");
+
+    // explicit conversion to string
+    std::string s;
+    wpi::raw_string_ostream os(s);
+    j.dump(os);    // {\"happy\":true,\"pi\":3.141}
+    EXPECT_EQ(os.str(), "{\"happy\":true,\"pi\":3.141}");
+
+    // serialization with pretty printing
+    // pass in the amount of spaces to indent
+    std::string s2;
+    wpi::raw_string_ostream os2(s2);
+    j2.dump(os2, 4);
+    EXPECT_EQ(os2.str(), "{\n    \"happy\": true,\n    \"pi\": 3.141\n}");
+    // {
+    //     "happy": true,
+    //     "pi": 3.141
+    // }
+}
+
+TEST(JsonReadmeTest, Basic2)
+{
+    // create an array using push_back
+    json j;
+    j.push_back("foo");
+    j.push_back(1);
+    j.push_back(true);
+
+    std::string s;
+    wpi::raw_string_ostream os(s);
+
+    // iterate the array
+    for (json::iterator it = j.begin(); it != j.end(); ++it)
+    {
+        os << *it << '\n';
+    }
+
+    // range-based for
+    for (auto element : j)
+    {
+        os << element << '\n';
+    }
+
+    // comparison
+    bool x = (j == "[\"foo\", 1, true]"_json);  // true
+    EXPECT_EQ(x, true);
+
+    // getter/setter
+    const std::string tmp = j[0];
+    j[1] = 42;
+    bool foo = j.at(2);
+    EXPECT_EQ(foo, true);
+
+    // other stuff
+    EXPECT_EQ(j.size(), 3u);        // 3 entries
+    EXPECT_EQ(j.empty(), false);
+    EXPECT_EQ(j.type(), json::value_t::array);
+    j.clear();    // the array is empty again
+    EXPECT_EQ(j.size(), 0u);
+    EXPECT_EQ(j.empty(), true);
+
+    // create an object
+    json o;
+    o["foo"] = 23;
+    o["bar"] = false;
+    o["baz"] = 3.141;
+
+    // find an entry
+    if (o.find("foo") != o.end())
+    {
+        // there is an entry with key "foo"
+    }
+}
+
+TEST(JsonReadmeTest, OtherContainer)
+{
+    std::vector<int> c_vector {1, 2, 3, 4};
+    json j_vec(c_vector);
+    json j_vec2(wpi::makeArrayRef(c_vector));
+    // [1, 2, 3, 4]
+
+    std::deque<float> c_deque {1.2f, 2.3f, 3.4f, 5.6f};
+    json j_deque(c_deque);
+    // [1.2, 2.3, 3.4, 5.6]
+
+    std::list<bool> c_list {true, true, false, true};
+    json j_list(c_list);
+    // [true, true, false, true]
+
+    std::forward_list<int64_t> c_flist {12345678909876, 23456789098765, 34567890987654, 45678909876543};
+    json j_flist(c_flist);
+    // [12345678909876, 23456789098765, 34567890987654, 45678909876543]
+
+    std::array<unsigned long, 4> c_array {{1, 2, 3, 4}};
+    json j_array(c_array);
+    // [1, 2, 3, 4]
+
+    std::set<std::string> c_set {"one", "two", "three", "four", "one"};
+    json j_set(c_set); // only one entry for "one" is used
+    // ["four", "one", "three", "two"]
+
+    std::unordered_set<std::string> c_uset {"one", "two", "three", "four", "one"};
+    json j_uset(c_uset); // only one entry for "one" is used
+    // maybe ["two", "three", "four", "one"]
+
+    std::multiset<std::string> c_mset {"one", "two", "one", "four"};
+    json j_mset(c_mset); // both entries for "one" are used
+    // maybe ["one", "two", "one", "four"]
+
+    std::unordered_multiset<std::string> c_umset {"one", "two", "one", "four"};
+    json j_umset(c_umset); // both entries for "one" are used
+    // maybe ["one", "two", "one", "four"]
+}
+
+TEST(JsonReadmeTest, MapContainer)
+{
+    std::map<std::string, int> c_map { {"one", 1}, {"two", 2}, {"three", 3} };
+    json j_map(c_map);
+    // {"one": 1, "two": 2, "three": 3}
+
+#if 0
+    std::unordered_map<const char*, float> c_umap { {"one", 1.2f}, {"two", 2.3f}, {"three", 3.4f} };
+    json j_umap(c_umap);
+    // {"one": 1.2, "two": 2.3, "three": 3.4}
+#endif
+
+    std::multimap<std::string, bool> c_mmap { {"one", true}, {"two", true}, {"three", false}, {"three", true} };
+    json j_mmap(c_mmap); // only one entry for key "three" is used
+    // maybe {"one": true, "two": true, "three": true}
+
+    std::unordered_multimap<std::string, bool> c_ummap { {"one", true}, {"two", true}, {"three", false}, {"three", true} };
+    json j_ummap(c_ummap); // only one entry for key "three" is used
+    // maybe {"one": true, "two": true, "three": true}
+}
+
+TEST(JsonReadmeTest, Values)
+{
+    // strings
+    std::string s1 = "Hello, world!";
+    json js = s1;
+    std::string s2 = js;
+    EXPECT_EQ(s1, s2);
+
+    // Booleans
+    bool b1 = true;
+    json jb = b1;
+    bool b2 = jb;
+    EXPECT_EQ(b1, b2);
+
+    // numbers
+    int i = 42;
+    json jn = i;
+    double f = jn;
+    EXPECT_EQ(i, f);
+
+    // etc.
+
+    std::string vs = js.get<std::string>();
+    bool vb = jb.get<bool>();
+    int vi = jn.get<int>();
+    EXPECT_EQ(s1, vs);
+    EXPECT_EQ(b1, vb);
+    EXPECT_EQ(i, vi);
+
+    // etc.
+}
+
+#if 0
+TEST(JsonReadmeTest, DiffPatch)
+{
+    // a JSON value
+    json j_original = R"({
+  "baz": ["one", "two", "three"],
+  "foo": "bar"
+})"_json;
+
+    // access members with a JSON pointer (RFC 6901)
+    j_original["/baz/1"_json_pointer];
+    // "two"
+
+    // a JSON patch (RFC 6902)
+    json j_patch = R"([
+  { "op": "replace", "path": "/baz", "value": "boo" },
+  { "op": "add", "path": "/hello", "value": ["world"] },
+  { "op": "remove", "path": "/foo"}
+])"_json;
+
+    // apply the patch
+    json j_result = j_original.patch(j_patch);
+    // {
+    //    "baz": "boo",
+    //    "hello": ["world"]
+    // }
+
+    // calculate a JSON patch from two JSON values
+    json::diff(j_result, j_original);
+    // [
+    //   { "op":" replace", "path": "/baz", "value": ["one", "two", "three"] },
+    //   { "op":"remove","path":"/hello" },
+    //   { "op":"add","path":"/foo","value":"bar" }
+    // ]
+}
+#endif
diff --git a/wpiutil/src/test/native/cpp/json/unit-reference_access.cpp b/wpiutil/src/test/native/cpp/json/unit-reference_access.cpp
new file mode 100644
index 0000000..7c3cfb1
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-reference_access.cpp
@@ -0,0 +1,197 @@
+/*----------------------------------------------------------------------------*/
+/* Modifications Copyright (c) FIRST 2017. All Rights Reserved.               */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include "unit-json.h"
+using wpi::json;
+
+// reference access to object_t
+TEST(JsonReferenceTest, ObjectT)
+{
+    using test_type = json::object_t;
+    json value = {{"one", 1}, {"two", 2}};
+
+    // check if references are returned correctly
+    test_type& p1 = value.get_ref<test_type&>();
+    EXPECT_EQ(&p1, value.get_ptr<test_type*>());
+    EXPECT_EQ(p1, value.get<test_type>());
+
+    const test_type& p2 = value.get_ref<const test_type&>();
+    EXPECT_EQ(&p2, value.get_ptr<const test_type*>());
+    EXPECT_EQ(p2, value.get<test_type>());
+
+    // check if mismatching references throw correctly
+    EXPECT_NO_THROW(value.get_ref<json::object_t&>());
+    EXPECT_ANY_THROW(value.get_ref<json::array_t&>());
+    EXPECT_ANY_THROW(value.get_ref<std::string&>());
+    EXPECT_ANY_THROW(value.get_ref<bool&>());
+    EXPECT_ANY_THROW(value.get_ref<int64_t&>());
+    EXPECT_ANY_THROW(value.get_ref<double&>());
+}
+
+// const reference access to const object_t
+TEST(JsonReferenceTest, ConstObjectT)
+{
+    using test_type = json::object_t;
+    const json value = {{"one", 1}, {"two", 2}};
+
+    // this should not compile
+    // test_type& p1 = value.get_ref<test_type&>();
+
+    // check if references are returned correctly
+    const test_type& p2 = value.get_ref<const test_type&>();
+    EXPECT_EQ(&p2, value.get_ptr<const test_type*>());
+    EXPECT_EQ(p2, value.get<test_type>());
+}
+
+// reference access to array_t
+TEST(JsonReferenceTest, ArrayT)
+{
+    using test_type = json::array_t;
+    json value = {1, 2, 3, 4};
+
+    // check if references are returned correctly
+    test_type& p1 = value.get_ref<test_type&>();
+    EXPECT_EQ(&p1, value.get_ptr<test_type*>());
+    EXPECT_EQ(p1, value.get<test_type>());
+
+    const test_type& p2 = value.get_ref<const test_type&>();
+    EXPECT_EQ(&p2, value.get_ptr<const test_type*>());
+    EXPECT_EQ(p2, value.get<test_type>());
+
+    // check if mismatching references throw correctly
+    EXPECT_ANY_THROW(value.get_ref<json::object_t&>());
+    EXPECT_NO_THROW(value.get_ref<json::array_t&>());
+    EXPECT_ANY_THROW(value.get_ref<std::string&>());
+    EXPECT_ANY_THROW(value.get_ref<bool&>());
+    EXPECT_ANY_THROW(value.get_ref<int64_t&>());
+    EXPECT_ANY_THROW(value.get_ref<double&>());
+}
+
+// reference access to string_t
+TEST(JsonReferenceTest, StringT)
+{
+    using test_type = std::string;
+    json value = "hello";
+
+    // check if references are returned correctly
+    test_type& p1 = value.get_ref<test_type&>();
+    EXPECT_EQ(&p1, value.get_ptr<test_type*>());
+    EXPECT_EQ(p1, value.get<test_type>());
+
+    const test_type& p2 = value.get_ref<const test_type&>();
+    EXPECT_EQ(&p2, value.get_ptr<const test_type*>());
+    EXPECT_EQ(p2, value.get<test_type>());
+
+    // check if mismatching references throw correctly
+    EXPECT_ANY_THROW(value.get_ref<json::object_t&>());
+    EXPECT_ANY_THROW(value.get_ref<json::array_t&>());
+    EXPECT_NO_THROW(value.get_ref<std::string&>());
+    EXPECT_ANY_THROW(value.get_ref<bool&>());
+    EXPECT_ANY_THROW(value.get_ref<int64_t&>());
+    EXPECT_ANY_THROW(value.get_ref<double&>());
+}
+
+// reference access to boolean_t
+TEST(JsonReferenceTest, BooleanT)
+{
+    using test_type = bool;
+    json value = false;
+
+    // check if references are returned correctly
+    test_type& p1 = value.get_ref<test_type&>();
+    EXPECT_EQ(&p1, value.get_ptr<test_type*>());
+    EXPECT_EQ(p1, value.get<test_type>());
+
+    const test_type& p2 = value.get_ref<const test_type&>();
+    EXPECT_EQ(&p2, value.get_ptr<const test_type*>());
+    EXPECT_EQ(p2, value.get<test_type>());
+
+    // check if mismatching references throw correctly
+    EXPECT_ANY_THROW(value.get_ref<json::object_t&>());
+    EXPECT_ANY_THROW(value.get_ref<json::array_t&>());
+    EXPECT_ANY_THROW(value.get_ref<std::string&>());
+    EXPECT_NO_THROW(value.get_ref<bool&>());
+    EXPECT_ANY_THROW(value.get_ref<int64_t&>());
+    EXPECT_ANY_THROW(value.get_ref<double&>());
+}
+
+// reference access to number_integer_t
+TEST(JsonReferenceTest, IntegerT)
+{
+    using test_type = int64_t;
+    json value = 23;
+
+    // check if references are returned correctly
+    test_type& p1 = value.get_ref<test_type&>();
+    EXPECT_EQ(&p1, value.get_ptr<test_type*>());
+    EXPECT_EQ(p1, value.get<test_type>());
+
+    const test_type& p2 = value.get_ref<const test_type&>();
+    EXPECT_EQ(&p2, value.get_ptr<const test_type*>());
+    EXPECT_EQ(p2, value.get<test_type>());
+
+    // check if mismatching references throw correctly
+    EXPECT_ANY_THROW(value.get_ref<json::object_t&>());
+    EXPECT_ANY_THROW(value.get_ref<json::array_t&>());
+    EXPECT_ANY_THROW(value.get_ref<std::string&>());
+    EXPECT_ANY_THROW(value.get_ref<bool&>());
+    EXPECT_NO_THROW(value.get_ref<int64_t&>());
+    EXPECT_ANY_THROW(value.get_ref<double&>());
+}
+
+// reference access to number_float_t
+TEST(JsonReferenceTest, FloatT)
+{
+    using test_type = double;
+    json value = 42.23;
+
+    // check if references are returned correctly
+    test_type& p1 = value.get_ref<test_type&>();
+    EXPECT_EQ(&p1, value.get_ptr<test_type*>());
+    EXPECT_EQ(p1, value.get<test_type>());
+
+    const test_type& p2 = value.get_ref<const test_type&>();
+    EXPECT_EQ(&p2, value.get_ptr<const test_type*>());
+    EXPECT_EQ(p2, value.get<test_type>());
+
+    // check if mismatching references throw correctly
+    EXPECT_ANY_THROW(value.get_ref<json::object_t&>());
+    EXPECT_ANY_THROW(value.get_ref<json::array_t&>());
+    EXPECT_ANY_THROW(value.get_ref<std::string&>());
+    EXPECT_ANY_THROW(value.get_ref<bool&>());
+    EXPECT_ANY_THROW(value.get_ref<int64_t&>());
+    EXPECT_NO_THROW(value.get_ref<double&>());
+}
diff --git a/wpiutil/src/test/native/cpp/json/unit-unicode.cpp b/wpiutil/src/test/native/cpp/json/unit-unicode.cpp
new file mode 100644
index 0000000..e0179b6
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/json/unit-unicode.cpp
@@ -0,0 +1,1093 @@
+/*----------------------------------------------------------------------------*/
+/* Modifications Copyright (c) FIRST 2017. All Rights Reserved.               */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.1.1
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "gtest/gtest.h"
+
+#include "unit-json.h"
+using wpi::json;
+
+#include "wpi/Format.h"
+#include "wpi/StringExtras.h"
+#include "wpi/raw_ostream.h"
+
+#include <fstream>
+
+// create and check a JSON string with up to four UTF-8 bytes
+::testing::AssertionResult check_utf8string(bool success_expected, int byte1, int byte2 = -1, int byte3 = -1, int byte4 = -1)
+{
+    std::string json_string = "\"";
+
+    json_string += std::string(1, static_cast<char>(byte1));
+
+    if (byte2 != -1)
+    {
+        json_string += std::string(1, static_cast<char>(byte2));
+    }
+
+    if (byte3 != -1)
+    {
+        json_string += std::string(1, static_cast<char>(byte3));
+    }
+
+    if (byte4 != -1)
+    {
+        json_string += std::string(1, static_cast<char>(byte4));
+    }
+
+    json_string += "\"";
+
+    const char* basemsg = "";
+
+    try {
+        json::parse(json_string);
+    } catch (json::parse_error&) {
+        if (success_expected)
+        {
+            basemsg = "parse_error";
+            goto error;
+        }
+        return ::testing::AssertionSuccess();
+    } catch (...) {
+        basemsg = "other exception";
+        goto error;
+    }
+
+    if (success_expected)
+    {
+        return ::testing::AssertionSuccess();
+    }
+    basemsg = "expected failure";
+
+error:
+    auto result = ::testing::AssertionFailure();
+    result << basemsg << " with {" << wpi::utohexstr(byte1);
+    if (byte2 != -1)
+    {
+        result << ',' << wpi::utohexstr(byte2);
+    }
+    if (byte3 != -1)
+    {
+        result << ',' << wpi::utohexstr(byte3);
+    }
+    if (byte4 != -1)
+    {
+        result << ',' << wpi::utohexstr(byte4);
+    }
+    result << '}';
+    return result;
+}
+
+/*
+RFC 3629 describes in Sect. 4 the syntax of UTF-8 byte sequences as
+follows:
+
+    A UTF-8 string is a sequence of octets representing a sequence of UCS
+    characters.  An octet sequence is valid UTF-8 only if it matches the
+    following syntax, which is derived from the rules for encoding UTF-8
+    and is expressed in the ABNF of [RFC2234].
+
+    UTF8-octets = *( UTF8-char )
+    UTF8-char   = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
+    UTF8-1      = %x00-7F
+    UTF8-2      = %xC2-DF UTF8-tail
+    UTF8-3      = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
+                  %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
+    UTF8-4      = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
+                  %xF4 %x80-8F 2( UTF8-tail )
+    UTF8-tail   = %x80-BF
+*/
+
+// ill-formed first byte
+TEST(JsonUnicodeRfc3629Test, IllFormedFirstByte)
+{
+    for (int byte1 = 0x80; byte1 <= 0xC1; ++byte1)
+    {
+        EXPECT_TRUE(check_utf8string(false, byte1));
+    }
+
+    for (int byte1 = 0xF5; byte1 <= 0xFF; ++byte1)
+    {
+        EXPECT_TRUE(check_utf8string(false, byte1));
+    }
+}
+
+// UTF8-1 (x00-x7F), well-formed
+TEST(JsonUnicodeRfc3629Test, Utf8_1WellFormed)
+{
+    for (int byte1 = 0x00; byte1 <= 0x7F; ++byte1)
+    {
+        // unescaped control characters are parse errors in JSON
+        if (0x00 <= byte1 && byte1 <= 0x1F)
+        {
+            EXPECT_TRUE(check_utf8string(false, byte1));
+            continue;
+        }
+
+        // a single quote is a parse error in JSON
+        if (byte1 == 0x22)
+        {
+            EXPECT_TRUE(check_utf8string(false, byte1));
+            continue;
+        }
+
+        // a single backslash is a parse error in JSON
+        if (byte1 == 0x5C)
+        {
+            EXPECT_TRUE(check_utf8string(false, byte1));
+            continue;
+        }
+
+        // all other characters are OK
+        EXPECT_TRUE(check_utf8string(true, byte1));
+    }
+}
+
+// UTF8-2 (xC2-xDF UTF8-tail)
+// well-formed
+TEST(JsonUnicodeRfc3629Test, Utf8_2WellFormed)
+{
+    for (int byte1 = 0xC2; byte1 <= 0xDF; ++byte1)
+    {
+        for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+        {
+            EXPECT_TRUE(check_utf8string(true, byte1, byte2));
+        }
+    }
+}
+
+// ill-formed: missing second byte
+TEST(JsonUnicodeRfc3629Test, Utf8_2Missing2)
+{
+    for (int byte1 = 0xC2; byte1 <= 0xDF; ++byte1)
+    {
+        EXPECT_TRUE(check_utf8string(false, byte1));
+    }
+}
+
+// ill-formed: wrong second byte
+TEST(JsonUnicodeRfc3629Test, Utf8_2Wrong2)
+{
+    for (int byte1 = 0xC2; byte1 <= 0xDF; ++byte1)
+    {
+        for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
+        {
+            // skip correct second byte
+            if (0x80 <= byte2 && byte2 <= 0xBF)
+            {
+                continue;
+            }
+
+            EXPECT_TRUE(check_utf8string(false, byte1, byte2));
+        }
+    }
+}
+
+// UTF8-3 (xE0 xA0-BF UTF8-tail)
+// well-formed
+TEST(JsonUnicodeRfc3629Test, Utf8_3AWellFormed)
+{
+    for (int byte1 = 0xE0; byte1 <= 0xE0; ++byte1)
+    {
+        for (int byte2 = 0xA0; byte2 <= 0xBF; ++byte2)
+        {
+            for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+            {
+                EXPECT_TRUE(check_utf8string(true, byte1, byte2, byte3));
+            }
+        }
+    }
+}
+
+// ill-formed: missing second byte
+TEST(JsonUnicodeRfc3629Test, Utf8_3AMissing2)
+{
+    for (int byte1 = 0xE0; byte1 <= 0xE0; ++byte1)
+    {
+        EXPECT_TRUE(check_utf8string(false, byte1));
+    }
+}
+
+// ill-formed: missing third byte
+TEST(JsonUnicodeRfc3629Test, Utf8_3AMissing3)
+{
+    for (int byte1 = 0xE0; byte1 <= 0xE0; ++byte1)
+    {
+        for (int byte2 = 0xA0; byte2 <= 0xBF; ++byte2)
+        {
+            EXPECT_TRUE(check_utf8string(false, byte1, byte2));
+        }
+    }
+}
+
+// ill-formed: wrong second byte
+TEST(JsonUnicodeRfc3629Test, Utf8_3AWrong2)
+{
+    for (int byte1 = 0xE0; byte1 <= 0xE0; ++byte1)
+    {
+        for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
+        {
+            // skip correct second byte
+            if (0xA0 <= byte2 && byte2 <= 0xBF)
+            {
+                continue;
+            }
+
+            for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+            {
+                EXPECT_TRUE(check_utf8string(false, byte1, byte2, byte3));
+            }
+        }
+    }
+}
+
+// ill-formed: wrong third byte
+TEST(JsonUnicodeRfc3629Test, Utf8_3AWrong3)
+{
+    for (int byte1 = 0xE0; byte1 <= 0xE0; ++byte1)
+    {
+        for (int byte2 = 0xA0; byte2 <= 0xBF; ++byte2)
+        {
+            for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
+            {
+                // skip correct third byte
+                if (0x80 <= byte3 && byte3 <= 0xBF)
+                {
+                    continue;
+                }
+
+                EXPECT_TRUE(check_utf8string(false, byte1, byte2, byte3));
+            }
+        }
+    }
+}
+
+// UTF8-3 (xE1-xEC UTF8-tail UTF8-tail)
+// well-formed
+TEST(JsonUnicodeRfc3629Test, Utf8_3BWellFormed)
+{
+    for (int byte1 = 0xE1; byte1 <= 0xEC; ++byte1)
+    {
+        for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+        {
+            for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+            {
+                EXPECT_TRUE(check_utf8string(true, byte1, byte2, byte3));
+            }
+        }
+    }
+}
+
+// ill-formed: missing second byte
+TEST(JsonUnicodeRfc3629Test, Utf8_3BMissing2)
+{
+    for (int byte1 = 0xE1; byte1 <= 0xEC; ++byte1)
+    {
+        EXPECT_TRUE(check_utf8string(false, byte1));
+    }
+}
+
+// ill-formed: missing third byte
+TEST(JsonUnicodeRfc3629Test, Utf8_3BMissing3)
+{
+    for (int byte1 = 0xE1; byte1 <= 0xEC; ++byte1)
+    {
+        for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+        {
+            EXPECT_TRUE(check_utf8string(false, byte1, byte2));
+        }
+    }
+}
+
+// ill-formed: wrong second byte
+TEST(JsonUnicodeRfc3629Test, DISABLED_Utf8_3BWrong2)
+{
+    for (int byte1 = 0xE1; byte1 <= 0xEC; ++byte1)
+    {
+        for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
+        {
+            // skip correct second byte
+            if (0x80 <= byte2 && byte2 <= 0xBF)
+            {
+                continue;
+            }
+
+            for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+            {
+                EXPECT_TRUE(check_utf8string(false, byte1, byte2, byte3));
+            }
+        }
+    }
+}
+
+// ill-formed: wrong third byte
+TEST(JsonUnicodeRfc3629Test, DISABLED_Utf8_3BWrong3)
+{
+    for (int byte1 = 0xE1; byte1 <= 0xEC; ++byte1)
+    {
+        for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+        {
+            for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
+            {
+                // skip correct third byte
+                if (0x80 <= byte3 && byte3 <= 0xBF)
+                {
+                    continue;
+                }
+
+                EXPECT_TRUE(check_utf8string(false, byte1, byte2, byte3));
+            }
+        }
+    }
+}
+
+// UTF8-3 (xED x80-9F UTF8-tail)
+// well-formed
+TEST(JsonUnicodeRfc3629Test, Utf8_3CWellFormed)
+{
+    for (int byte1 = 0xED; byte1 <= 0xED; ++byte1)
+    {
+        for (int byte2 = 0x80; byte2 <= 0x9F; ++byte2)
+        {
+            for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+            {
+                EXPECT_TRUE(check_utf8string(true, byte1, byte2, byte3));
+            }
+        }
+    }
+}
+
+// ill-formed: missing second byte
+TEST(JsonUnicodeRfc3629Test, Utf8_3CMissing2)
+{
+    for (int byte1 = 0xED; byte1 <= 0xED; ++byte1)
+    {
+        EXPECT_TRUE(check_utf8string(false, byte1));
+    }
+}
+
+// ill-formed: missing third byte
+TEST(JsonUnicodeRfc3629Test, Utf8_3CMissing3)
+{
+    for (int byte1 = 0xED; byte1 <= 0xED; ++byte1)
+    {
+        for (int byte2 = 0x80; byte2 <= 0x9F; ++byte2)
+        {
+            EXPECT_TRUE(check_utf8string(false, byte1, byte2));
+        }
+    }
+}
+
+// ill-formed: wrong second byte
+TEST(JsonUnicodeRfc3629Test, Utf8_3CWrong2)
+{
+    for (int byte1 = 0xED; byte1 <= 0xED; ++byte1)
+    {
+        for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
+        {
+            // skip correct second byte
+            if (0x80 <= byte2 && byte2 <= 0x9F)
+            {
+                continue;
+            }
+
+            for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+            {
+                EXPECT_TRUE(check_utf8string(false, byte1, byte2, byte3));
+            }
+        }
+    }
+}
+
+// ill-formed: wrong third byte
+TEST(JsonUnicodeRfc3629Test, Utf8_3CWrong3)
+{
+    for (int byte1 = 0xED; byte1 <= 0xED; ++byte1)
+    {
+        for (int byte2 = 0x80; byte2 <= 0x9F; ++byte2)
+        {
+            for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
+            {
+                // skip correct third byte
+                if (0x80 <= byte3 && byte3 <= 0xBF)
+                {
+                    continue;
+                }
+
+                EXPECT_TRUE(check_utf8string(false, byte1, byte2, byte3));
+            }
+        }
+    }
+}
+
+// UTF8-3 (xEE-xEF UTF8-tail UTF8-tail)
+// well-formed
+TEST(JsonUnicodeRfc3629Test, Utf8_3DWellFormed)
+{
+    for (int byte1 = 0xEE; byte1 <= 0xEF; ++byte1)
+    {
+        for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+        {
+            for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+            {
+                EXPECT_TRUE(check_utf8string(true, byte1, byte2, byte3));
+            }
+        }
+    }
+}
+
+// ill-formed: missing second byte
+TEST(JsonUnicodeRfc3629Test, Utf8_3DMissing2)
+{
+    for (int byte1 = 0xEE; byte1 <= 0xEF; ++byte1)
+    {
+        EXPECT_TRUE(check_utf8string(false, byte1));
+    }
+}
+
+// ill-formed: missing third byte
+TEST(JsonUnicodeRfc3629Test, Utf8_3DMissing3)
+{
+    for (int byte1 = 0xEE; byte1 <= 0xEF; ++byte1)
+    {
+        for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+        {
+            EXPECT_TRUE(check_utf8string(false, byte1, byte2));
+        }
+    }
+}
+
+// ill-formed: wrong second byte
+TEST(JsonUnicodeRfc3629Test, Utf8_3DWrong2)
+{
+    for (int byte1 = 0xEE; byte1 <= 0xEF; ++byte1)
+    {
+        for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
+        {
+            // skip correct second byte
+            if (0x80 <= byte2 && byte2 <= 0xBF)
+            {
+                continue;
+            }
+
+            for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+            {
+                EXPECT_TRUE(check_utf8string(false, byte1, byte2, byte3));
+            }
+        }
+    }
+}
+
+// ill-formed: wrong third byte
+TEST(JsonUnicodeRfc3629Test, Utf8_3DWrong3)
+{
+    for (int byte1 = 0xEE; byte1 <= 0xEF; ++byte1)
+    {
+        for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+        {
+            for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
+            {
+                // skip correct third byte
+                if (0x80 <= byte3 && byte3 <= 0xBF)
+                {
+                    continue;
+                }
+
+                EXPECT_TRUE(check_utf8string(false, byte1, byte2, byte3));
+            }
+        }
+    }
+}
+
+// UTF8-4 (xF0 x90-BF UTF8-tail UTF8-tail)
+// well-formed
+TEST(JsonUnicodeRfc3629Test, Utf8_4AWellFormed)
+{
+    for (int byte1 = 0xF0; byte1 <= 0xF0; ++byte1)
+    {
+        for (int byte2 = 0x90; byte2 <= 0xBF; ++byte2)
+        {
+            for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+            {
+                for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
+                {
+                    EXPECT_TRUE(check_utf8string(true, byte1, byte2, byte3, byte4));
+                }
+            }
+        }
+    }
+}
+
+// ill-formed: missing second byte
+TEST(JsonUnicodeRfc3629Test, Utf8_4AMissing2)
+{
+    for (int byte1 = 0xF0; byte1 <= 0xF0; ++byte1)
+    {
+        EXPECT_TRUE(check_utf8string(false, byte1));
+    }
+}
+
+// ill-formed: missing third byte
+TEST(JsonUnicodeRfc3629Test, Utf8_4AMissing3)
+{
+    for (int byte1 = 0xF0; byte1 <= 0xF0; ++byte1)
+    {
+        for (int byte2 = 0x90; byte2 <= 0xBF; ++byte2)
+        {
+            EXPECT_TRUE(check_utf8string(false, byte1, byte2));
+        }
+    }
+}
+
+// ill-formed: missing fourth byte
+TEST(JsonUnicodeRfc3629Test, Utf8_4AMissing4)
+{
+    for (int byte1 = 0xF0; byte1 <= 0xF0; ++byte1)
+    {
+        for (int byte2 = 0x90; byte2 <= 0xBF; ++byte2)
+        {
+            for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+            {
+                EXPECT_TRUE(check_utf8string(false, byte1, byte2, byte3));
+            }
+        }
+    }
+}
+
+// ill-formed: wrong second byte
+TEST(JsonUnicodeRfc3629Test, DISABLED_Utf8_4AWrong2)
+{
+    for (int byte1 = 0xF0; byte1 <= 0xF0; ++byte1)
+    {
+        for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
+        {
+            // skip correct second byte
+            if (0x90 <= byte2 && byte2 <= 0xBF)
+            {
+                continue;
+            }
+
+            for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+            {
+                for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
+                {
+                    EXPECT_TRUE(check_utf8string(false, byte1, byte2, byte3, byte4));
+                }
+            }
+        }
+    }
+}
+
+// ill-formed: wrong third byte
+TEST(JsonUnicodeRfc3629Test, DISABLED_Utf8_4AWrong3)
+{
+    for (int byte1 = 0xF0; byte1 <= 0xF0; ++byte1)
+    {
+        for (int byte2 = 0x90; byte2 <= 0xBF; ++byte2)
+        {
+            for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
+            {
+                // skip correct third byte
+                if (0x80 <= byte3 && byte3 <= 0xBF)
+                {
+                    continue;
+                }
+
+                for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
+                {
+                    EXPECT_TRUE(check_utf8string(false, byte1, byte2, byte3, byte4));
+                }
+            }
+        }
+    }
+}
+
+// ill-formed: wrong fourth byte
+TEST(JsonUnicodeRfc3629Test, Utf8_4AWrong4)
+{
+    for (int byte1 = 0xF0; byte1 <= 0xF0; ++byte1)
+    {
+        for (int byte2 = 0x90; byte2 <= 0xBF; ++byte2)
+        {
+            for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+            {
+                for (int byte4 = 0x00; byte4 <= 0xFF; ++byte4)
+                {
+                    // skip fourth second byte
+                    if (0x80 <= byte3 && byte3 <= 0xBF)
+                    {
+                        continue;
+                    }
+
+                    EXPECT_TRUE(check_utf8string(false, byte1, byte2, byte3, byte4));
+                }
+            }
+        }
+    }
+}
+
+// UTF8-4 (xF1-F3 UTF8-tail UTF8-tail UTF8-tail)
+// well-formed
+TEST(JsonUnicodeRfc3629Test, Utf8_4BWellFormed)
+{
+    for (int byte1 = 0xF1; byte1 <= 0xF3; ++byte1)
+    {
+        for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+        {
+            for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+            {
+                for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
+                {
+                    EXPECT_TRUE(check_utf8string(true, byte1, byte2, byte3, byte4));
+                }
+            }
+        }
+    }
+}
+
+// ill-formed: missing second byte
+TEST(JsonUnicodeRfc3629Test, Utf8_4BMissing2)
+{
+    for (int byte1 = 0xF1; byte1 <= 0xF3; ++byte1)
+    {
+        EXPECT_TRUE(check_utf8string(false, byte1));
+    }
+}
+
+// ill-formed: missing third byte
+TEST(JsonUnicodeRfc3629Test, Utf8_4BMissing3)
+{
+    for (int byte1 = 0xF1; byte1 <= 0xF3; ++byte1)
+    {
+        for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+        {
+            EXPECT_TRUE(check_utf8string(false, byte1, byte2));
+        }
+    }
+}
+
+// ill-formed: missing fourth byte
+TEST(JsonUnicodeRfc3629Test, Utf8_4BMissing4)
+{
+    for (int byte1 = 0xF1; byte1 <= 0xF3; ++byte1)
+    {
+        for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+        {
+            for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+            {
+                EXPECT_TRUE(check_utf8string(false, byte1, byte2, byte3));
+            }
+        }
+    }
+}
+
+// ill-formed: wrong second byte
+TEST(JsonUnicodeRfc3629Test, DISABLED_Utf8_4BWrong2)
+{
+    for (int byte1 = 0xF1; byte1 <= 0xF3; ++byte1)
+    {
+        for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
+        {
+            // skip correct second byte
+            if (0x80 <= byte2 && byte2 <= 0xBF)
+            {
+                continue;
+            }
+
+            for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+            {
+                for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
+                {
+                    EXPECT_TRUE(check_utf8string(false, byte1, byte2, byte3, byte4));
+                }
+            }
+        }
+    }
+}
+
+// ill-formed: wrong third byte
+TEST(JsonUnicodeRfc3629Test, DISABLED_Utf8_4BWrong3)
+{
+    for (int byte1 = 0xF1; byte1 <= 0xF3; ++byte1)
+    {
+        for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+        {
+            for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
+            {
+                // skip correct third byte
+                if (0x80 <= byte3 && byte3 <= 0xBF)
+                {
+                    continue;
+                }
+
+                for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
+                {
+                    EXPECT_TRUE(check_utf8string(false, byte1, byte2, byte3, byte4));
+                }
+            }
+        }
+    }
+}
+
+// ill-formed: wrong fourth byte
+TEST(JsonUnicodeRfc3629Test, Utf8_4BWrong4)
+{
+    for (int byte1 = 0xF1; byte1 <= 0xF3; ++byte1)
+    {
+        for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+        {
+            for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+            {
+                for (int byte4 = 0x00; byte4 <= 0xFF; ++byte4)
+                {
+                    // skip correct fourth byte
+                    if (0x80 <= byte3 && byte3 <= 0xBF)
+                    {
+                        continue;
+                    }
+
+                    EXPECT_TRUE(check_utf8string(false, byte1, byte2, byte3, byte4));
+                }
+            }
+        }
+    }
+}
+
+// UTF8-4 (xF4 x80-8F UTF8-tail UTF8-tail)
+// well-formed
+TEST(JsonUnicodeRfc3629Test, Utf8_4CWellFormed)
+{
+    for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
+    {
+        for (int byte2 = 0x80; byte2 <= 0x8F; ++byte2)
+        {
+            for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+            {
+                for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
+                {
+                    EXPECT_TRUE(check_utf8string(true, byte1, byte2, byte3, byte4));
+                }
+            }
+        }
+    }
+}
+
+// ill-formed: missing second byte
+TEST(JsonUnicodeRfc3629Test, Utf8_4CMissing2)
+{
+    for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
+    {
+        EXPECT_TRUE(check_utf8string(false, byte1));
+    }
+}
+
+// ill-formed: missing third byte
+TEST(JsonUnicodeRfc3629Test, Utf8_4CMissing3)
+{
+    for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
+    {
+        for (int byte2 = 0x80; byte2 <= 0x8F; ++byte2)
+        {
+            EXPECT_TRUE(check_utf8string(false, byte1, byte2));
+        }
+    }
+}
+
+// ill-formed: missing fourth byte
+TEST(JsonUnicodeRfc3629Test, Utf8_4CMissing4)
+{
+    for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
+    {
+        for (int byte2 = 0x80; byte2 <= 0x8F; ++byte2)
+        {
+            for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+            {
+                EXPECT_TRUE(check_utf8string(false, byte1, byte2, byte3));
+            }
+        }
+    }
+}
+
+// ill-formed: wrong second byte
+TEST(JsonUnicodeRfc3629Test, DISABLED_Utf8_4CWrong2)
+{
+    for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
+    {
+        for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
+        {
+            // skip correct second byte
+            if (0x80 <= byte2 && byte2 <= 0x8F)
+            {
+                continue;
+            }
+
+            for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+            {
+                for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
+                {
+                    EXPECT_TRUE(check_utf8string(false, byte1, byte2, byte3, byte4));
+                }
+            }
+        }
+    }
+}
+
+// ill-formed: wrong third byte
+TEST(JsonUnicodeRfc3629Test, DISABLED_Utf8_4CWrong3)
+{
+    for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
+    {
+        for (int byte2 = 0x80; byte2 <= 0x8F; ++byte2)
+        {
+            for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
+            {
+                // skip correct third byte
+                if (0x80 <= byte3 && byte3 <= 0xBF)
+                {
+                    continue;
+                }
+
+                for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
+                {
+                    EXPECT_TRUE(check_utf8string(false, byte1, byte2, byte3, byte4));
+                }
+            }
+        }
+    }
+}
+
+// ill-formed: wrong fourth byte
+TEST(JsonUnicodeRfc3629Test, Utf8_4CWrong4)
+{
+    for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
+    {
+        for (int byte2 = 0x80; byte2 <= 0x8F; ++byte2)
+        {
+            for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+            {
+                for (int byte4 = 0x00; byte4 <= 0xFF; ++byte4)
+                {
+                    // skip correct fourth byte
+                    if (0x80 <= byte3 && byte3 <= 0xBF)
+                    {
+                        continue;
+                    }
+
+                    EXPECT_TRUE(check_utf8string(false, byte1, byte2, byte3, byte4));
+                }
+            }
+        }
+    }
+}
+
+// \\uxxxx sequences
+
+// create an escaped string from a code point
+static std::string codepoint_to_unicode(std::size_t cp)
+{
+    // code points are represented as a six-character sequence: a
+    // reverse solidus, followed by the lowercase letter u, followed
+    // by four hexadecimal digits that encode the character's code
+    // point
+    std::string s;
+    wpi::raw_string_ostream ss(s);
+    ss << "\\u" << wpi::format_hex_no_prefix(cp, 4);
+    ss.flush();
+    return s;
+}
+
+// correct sequences
+TEST(JsonUnicodeCodepointTest, DISABLED_Correct)
+{
+    // generate all UTF-8 code points; in total, 1112064 code points are
+    // generated: 0x1FFFFF code points - 2048 invalid values between
+    // 0xD800 and 0xDFFF.
+    for (std::size_t cp = 0; cp <= 0x10FFFFu; ++cp)
+    {
+        // string to store the code point as in \uxxxx format
+        std::string json_text = "\"";
+
+        // decide whether to use one or two \uxxxx sequences
+        if (cp < 0x10000u)
+        {
+            // The Unicode standard permanently reserves these code point
+            // values for UTF-16 encoding of the high and low surrogates, and
+            // they will never be assigned a character, so there should be no
+            // reason to encode them. The official Unicode standard says that
+            // no UTF forms, including UTF-16, can encode these code points.
+            if (cp >= 0xD800u && cp <= 0xDFFFu)
+            {
+                // if we would not skip these code points, we would get a
+                // "missing low surrogate" exception
+                continue;
+            }
+
+            // code points in the Basic Multilingual Plane can be
+            // represented with one \uxxxx sequence
+            json_text += codepoint_to_unicode(cp);
+        }
+        else
+        {
+            // To escape an extended character that is not in the Basic
+            // Multilingual Plane, the character is represented as a
+            // 12-character sequence, encoding the UTF-16 surrogate pair
+            const auto codepoint1 = 0xd800u + (((cp - 0x10000u) >> 10) & 0x3ffu);
+            const auto codepoint2 = 0xdc00u + ((cp - 0x10000u) & 0x3ffu);
+            json_text += codepoint_to_unicode(codepoint1) + codepoint_to_unicode(codepoint2);
+        }
+
+        json_text += "\"";
+        SCOPED_TRACE(json_text);
+        EXPECT_NO_THROW(json::parse(json_text));
+    }
+}
+
+#if 0
+// incorrect sequences
+// high surrogate without low surrogate
+TEST(JsonUnicodeCodepointTest, IncorrectHighMissingLow)
+{
+    // D800..DBFF are high surrogates and must be followed by low
+    // surrogates DC00..DFFF; here, nothing follows
+    for (std::size_t cp = 0xD800u; cp <= 0xDBFFu; ++cp)
+    {
+        std::string json_text = "\"" + codepoint_to_unicode(cp) + "\"";
+        SCOPED_TRACE(json_text);
+        EXPECT_THROW(json::parse(json_text), json::parse_error);
+    }
+}
+
+// high surrogate with wrong low surrogate
+TEST(JsonUnicodeCodepointTest, IncorrectHighWrongLow)
+{
+    // D800..DBFF are high surrogates and must be followed by low
+    // surrogates DC00..DFFF; here a different sequence follows
+    for (std::size_t cp1 = 0xD800u; cp1 <= 0xDBFFu; ++cp1)
+    {
+        for (std::size_t cp2 = 0x0000u; cp2 <= 0xFFFFu; ++cp2)
+        {
+            if (0xDC00u <= cp2 && cp2 <= 0xDFFFu)
+            {
+                continue;
+            }
+
+            std::string json_text = "\"" + codepoint_to_unicode(cp1) + codepoint_to_unicode(cp2) + "\"";
+            SCOPED_TRACE(json_text);
+            EXPECT_THROW(json::parse(json_text), json::parse_error);
+        }
+    }
+}
+
+// low surrogate without high surrogate
+TEST(JsonUnicodeCodepointTest, IncorrectLowMissingHigh)
+{
+    // low surrogates DC00..DFFF must follow high surrogates; here,
+    // they occur alone
+    for (std::size_t cp = 0xDC00u; cp <= 0xDFFFu; ++cp)
+    {
+        std::string json_text = "\"" + codepoint_to_unicode(cp) + "\"";
+        SCOPED_TRACE(json_text);
+        EXPECT_THROW(json::parse(json_text), json::parse_error);
+    }
+}
+#endif
+
+#if 0
+// read all unicode characters
+TEST(JsonUnicodeTest, ReadAllUnicode)
+{
+    // read a file with all unicode characters stored as single-character
+    // strings in a JSON array
+    std::ifstream f("test/data/json_nlohmann_tests/all_unicode.json");
+    json j;
+    CHECK_NOTHROW(f >> j);
+
+    // the array has 1112064 + 1 elemnts (a terminating "null" value)
+    // Note: 1112064 = 0x1FFFFF code points - 2048 invalid values between
+    // 0xD800 and 0xDFFF.
+    CHECK(j.size() == 1112065);
+
+    SECTION("check JSON Pointers")
+    {
+        for (auto s : j)
+        {
+            // skip non-string JSON values
+            if (not s.is_string())
+            {
+                continue;
+            }
+
+            std::string ptr = s;
+
+            // tilde must be followed by 0 or 1
+            if (ptr == "~")
+            {
+                ptr += "0";
+            }
+
+            // JSON Pointers must begin with "/"
+            ptr = "/" + ptr;
+
+            CHECK_NOTHROW(json::json_pointer("/" + ptr));
+
+            // check escape/unescape roundtrip
+            auto escaped = json::json_pointer::escape(ptr);
+            json::json_pointer::unescape(escaped);
+            CHECK(escaped == ptr);
+        }
+    }
+}
+
+// ignore byte-order-mark
+// in a stream
+TEST(JsonUnicodeTest, IgnoreBOMStream)
+{
+    // read a file with a UTF-8 BOM
+    std::ifstream f("test/data/json_nlohmann_tests/bom.json");
+    json j;
+    EXPECT_NO_THROW(f >> j);
+}
+
+// with an iterator
+TEST(JsonUnicodeTest, IgnoreBOMIterator)
+{
+    std::string i = "\xef\xbb\xbf{\n   \"foo\": true\n}";
+    EXPECT_NO_THROW(json::parse(i.begin(), i.end()));
+}
+#endif
+// error for incomplete/wrong BOM
+TEST(JsonUnicodeTest, WrongBOM)
+{
+    EXPECT_THROW(json::parse("\xef\xbb"), json::parse_error);
+    EXPECT_THROW(json::parse("\xef\xbb\xbb"), json::parse_error);
+}
diff --git a/wpiutil/src/test/native/cpp/leb128Test.cpp b/wpiutil/src/test/native/cpp/leb128Test.cpp
new file mode 100644
index 0000000..a3a7ed0
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/leb128Test.cpp
@@ -0,0 +1,112 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved.                        */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+//===- llvm/unittest/Support/LEB128Test.cpp - LEB128 function tests -------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include <stdint.h>
+
+#include <string>
+
+#include "gtest/gtest.h"
+#include "wpi/SmallString.h"
+#include "wpi/StringRef.h"
+#include "wpi/leb128.h"
+#include "wpi/raw_istream.h"
+
+namespace wpi {
+
+TEST(LEB128Test, WriteUleb128) {
+#define EXPECT_ULEB128_EQ(EXPECTED, VALUE, PAD)         \
+  do {                                                  \
+    StringRef expected(EXPECTED, sizeof(EXPECTED) - 1); \
+    SmallString<32> buf;                                \
+    size_t size = WriteUleb128(buf, VALUE);             \
+    EXPECT_EQ(size, buf.size());                        \
+    EXPECT_EQ(expected, buf.str());                     \
+  } while (0)
+
+  // Write ULEB128
+  EXPECT_ULEB128_EQ("\x00", 0, 0);
+  EXPECT_ULEB128_EQ("\x01", 1, 0);
+  EXPECT_ULEB128_EQ("\x3f", 63, 0);
+  EXPECT_ULEB128_EQ("\x40", 64, 0);
+  EXPECT_ULEB128_EQ("\x7f", 0x7f, 0);
+  EXPECT_ULEB128_EQ("\x80\x01", 0x80, 0);
+  EXPECT_ULEB128_EQ("\x81\x01", 0x81, 0);
+  EXPECT_ULEB128_EQ("\x90\x01", 0x90, 0);
+  EXPECT_ULEB128_EQ("\xff\x01", 0xff, 0);
+  EXPECT_ULEB128_EQ("\x80\x02", 0x100, 0);
+  EXPECT_ULEB128_EQ("\x81\x02", 0x101, 0);
+
+#undef EXPECT_ULEB128_EQ
+}
+
+TEST(LEB128Test, ReadUleb128) {
+#define EXPECT_READ_ULEB128_EQ(EXPECTED, VALUE) \
+  do {                                          \
+    uint64_t val = 0;                           \
+    size_t size = ReadUleb128(VALUE, &val);     \
+    EXPECT_EQ(sizeof(VALUE) - 1, size);         \
+    EXPECT_EQ(EXPECTED, val);                   \
+  } while (0)
+
+  // Read ULEB128
+  EXPECT_READ_ULEB128_EQ(0u, "\x00");
+  EXPECT_READ_ULEB128_EQ(1u, "\x01");
+  EXPECT_READ_ULEB128_EQ(63u, "\x3f");
+  EXPECT_READ_ULEB128_EQ(64u, "\x40");
+  EXPECT_READ_ULEB128_EQ(0x7fu, "\x7f");
+  EXPECT_READ_ULEB128_EQ(0x80u, "\x80\x01");
+  EXPECT_READ_ULEB128_EQ(0x81u, "\x81\x01");
+  EXPECT_READ_ULEB128_EQ(0x90u, "\x90\x01");
+  EXPECT_READ_ULEB128_EQ(0xffu, "\xff\x01");
+  EXPECT_READ_ULEB128_EQ(0x100u, "\x80\x02");
+  EXPECT_READ_ULEB128_EQ(0x101u, "\x81\x02");
+  EXPECT_READ_ULEB128_EQ(8320u, "\x80\xc1\x80\x80\x10");
+
+#undef EXPECT_READ_ULEB128_EQ
+}
+
+TEST(LEB128Test, SizeUleb128) {
+  // Testing Plan:
+  // (1) 128 ^ n ............ need (n+1) bytes
+  // (2) 128 ^ n * 64 ....... need (n+1) bytes
+  // (3) 128 ^ (n+1) - 1 .... need (n+1) bytes
+
+  EXPECT_EQ(1u, SizeUleb128(0));  // special case
+
+  EXPECT_EQ(1u, SizeUleb128(0x1UL));
+  EXPECT_EQ(1u, SizeUleb128(0x40UL));
+  EXPECT_EQ(1u, SizeUleb128(0x7fUL));
+
+  EXPECT_EQ(2u, SizeUleb128(0x80UL));
+  EXPECT_EQ(2u, SizeUleb128(0x2000UL));
+  EXPECT_EQ(2u, SizeUleb128(0x3fffUL));
+
+  EXPECT_EQ(3u, SizeUleb128(0x4000UL));
+  EXPECT_EQ(3u, SizeUleb128(0x100000UL));
+  EXPECT_EQ(3u, SizeUleb128(0x1fffffUL));
+
+  EXPECT_EQ(4u, SizeUleb128(0x200000UL));
+  EXPECT_EQ(4u, SizeUleb128(0x8000000UL));
+  EXPECT_EQ(4u, SizeUleb128(0xfffffffUL));
+
+  EXPECT_EQ(5u, SizeUleb128(0x10000000UL));
+  EXPECT_EQ(5u, SizeUleb128(0x40000000UL));
+  EXPECT_EQ(5u, SizeUleb128(0x7fffffffUL));
+
+  EXPECT_EQ(5u, SizeUleb128(UINT32_MAX));
+}
+
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/llvm/FunctionExtrasTest.cpp b/wpiutil/src/test/native/cpp/llvm/FunctionExtrasTest.cpp
new file mode 100644
index 0000000..5dcd11f
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/llvm/FunctionExtrasTest.cpp
@@ -0,0 +1,228 @@
+//===- FunctionExtrasTest.cpp - Unit tests for function type erasure ------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "wpi/FunctionExtras.h"
+#include "gtest/gtest.h"
+
+#include <memory>
+
+using namespace wpi;
+
+namespace {
+
+TEST(UniqueFunctionTest, Basic) {
+  unique_function<int(int, int)> Sum = [](int A, int B) { return A + B; };
+  EXPECT_EQ(Sum(1, 2), 3);
+
+  unique_function<int(int, int)> Sum2 = std::move(Sum);
+  EXPECT_EQ(Sum2(1, 2), 3);
+
+  unique_function<int(int, int)> Sum3 = [](int A, int B) { return A + B; };
+  Sum2 = std::move(Sum3);
+  EXPECT_EQ(Sum2(1, 2), 3);
+
+  Sum2 = unique_function<int(int, int)>([](int A, int B) { return A + B; });
+  EXPECT_EQ(Sum2(1, 2), 3);
+
+  // Explicit self-move test.
+  *&Sum2 = std::move(Sum2);
+  EXPECT_EQ(Sum2(1, 2), 3);
+
+  Sum2 = unique_function<int(int, int)>();
+  EXPECT_FALSE(Sum2);
+
+  // Make sure we can forward through l-value reference parameters.
+  unique_function<void(int &)> Inc = [](int &X) { ++X; };
+  int X = 42;
+  Inc(X);
+  EXPECT_EQ(X, 43);
+
+  // Make sure we can forward through r-value reference parameters with
+  // move-only types.
+  unique_function<int(std::unique_ptr<int> &&)> ReadAndDeallocByRef =
+      [](std::unique_ptr<int> &&Ptr) {
+        int V = *Ptr;
+        Ptr.reset();
+        return V;
+      };
+  std::unique_ptr<int> Ptr{new int(13)};
+  EXPECT_EQ(ReadAndDeallocByRef(std::move(Ptr)), 13);
+  EXPECT_FALSE((bool)Ptr);
+
+  // Make sure we can pass a move-only temporary as opposed to a local variable.
+  EXPECT_EQ(ReadAndDeallocByRef(std::unique_ptr<int>(new int(42))), 42);
+
+  // Make sure we can pass a move-only type by-value.
+  unique_function<int(std::unique_ptr<int>)> ReadAndDeallocByVal =
+      [](std::unique_ptr<int> Ptr) {
+        int V = *Ptr;
+        Ptr.reset();
+        return V;
+      };
+  Ptr.reset(new int(13));
+  EXPECT_EQ(ReadAndDeallocByVal(std::move(Ptr)), 13);
+  EXPECT_FALSE((bool)Ptr);
+
+  EXPECT_EQ(ReadAndDeallocByVal(std::unique_ptr<int>(new int(42))), 42);
+}
+
+TEST(UniqueFunctionTest, Captures) {
+  long A = 1, B = 2, C = 3, D = 4, E = 5;
+
+  unique_function<long()> Tmp;
+
+  unique_function<long()> C1 = [A]() { return A; };
+  EXPECT_EQ(C1(), 1);
+  Tmp = std::move(C1);
+  EXPECT_EQ(Tmp(), 1);
+
+  unique_function<long()> C2 = [A, B]() { return A + B; };
+  EXPECT_EQ(C2(), 3);
+  Tmp = std::move(C2);
+  EXPECT_EQ(Tmp(), 3);
+
+  unique_function<long()> C3 = [A, B, C]() { return A + B + C; };
+  EXPECT_EQ(C3(), 6);
+  Tmp = std::move(C3);
+  EXPECT_EQ(Tmp(), 6);
+
+  unique_function<long()> C4 = [A, B, C, D]() { return A + B + C + D; };
+  EXPECT_EQ(C4(), 10);
+  Tmp = std::move(C4);
+  EXPECT_EQ(Tmp(), 10);
+
+  unique_function<long()> C5 = [A, B, C, D, E]() { return A + B + C + D + E; };
+  EXPECT_EQ(C5(), 15);
+  Tmp = std::move(C5);
+  EXPECT_EQ(Tmp(), 15);
+}
+
+TEST(UniqueFunctionTest, MoveOnly) {
+  struct SmallCallable {
+    std::unique_ptr<int> A{new int(1)};
+
+    int operator()(int B) { return *A + B; }
+  };
+  unique_function<int(int)> Small = SmallCallable();
+  EXPECT_EQ(Small(2), 3);
+  unique_function<int(int)> Small2 = std::move(Small);
+  EXPECT_EQ(Small2(2), 3);
+
+  struct LargeCallable {
+    std::unique_ptr<int> A{new int(1)};
+    std::unique_ptr<int> B{new int(2)};
+    std::unique_ptr<int> C{new int(3)};
+    std::unique_ptr<int> D{new int(4)};
+    std::unique_ptr<int> E{new int(5)};
+
+    int operator()() { return *A + *B + *C + *D + *E; }
+  };
+  unique_function<int()> Large = LargeCallable();
+  EXPECT_EQ(Large(), 15);
+  unique_function<int()> Large2 = std::move(Large);
+  EXPECT_EQ(Large2(), 15);
+}
+
+TEST(UniqueFunctionTest, CountForwardingCopies) {
+  struct CopyCounter {
+    int &CopyCount;
+
+    CopyCounter(int &CopyCount) : CopyCount(CopyCount) {}
+    CopyCounter(const CopyCounter &Arg) : CopyCount(Arg.CopyCount) {
+      ++CopyCount;
+    }
+  };
+
+  unique_function<void(CopyCounter)> ByValF = [](CopyCounter) {};
+  int CopyCount = 0;
+  ByValF(CopyCounter(CopyCount));
+  EXPECT_EQ(1, CopyCount);
+
+  CopyCount = 0;
+  {
+    CopyCounter Counter{CopyCount};
+    ByValF(Counter);
+  }
+  EXPECT_EQ(2, CopyCount);
+
+  // Check that we don't generate a copy at all when we can bind a reference all
+  // the way down, even if that reference could *in theory* allow copies.
+  unique_function<void(const CopyCounter &)> ByRefF = [](const CopyCounter &) {
+  };
+  CopyCount = 0;
+  ByRefF(CopyCounter(CopyCount));
+  EXPECT_EQ(0, CopyCount);
+
+  CopyCount = 0;
+  {
+    CopyCounter Counter{CopyCount};
+    ByRefF(Counter);
+  }
+  EXPECT_EQ(0, CopyCount);
+
+  // If we use a reference, we can make a stronger guarantee that *no* copy
+  // occurs.
+  struct Uncopyable {
+    Uncopyable() = default;
+    Uncopyable(const Uncopyable &) = delete;
+  };
+  unique_function<void(const Uncopyable &)> UncopyableF =
+      [](const Uncopyable &) {};
+  UncopyableF(Uncopyable());
+  Uncopyable X;
+  UncopyableF(X);
+}
+
+TEST(UniqueFunctionTest, CountForwardingMoves) {
+  struct MoveCounter {
+    int &MoveCount;
+
+    MoveCounter(int &MoveCount) : MoveCount(MoveCount) {}
+    MoveCounter(MoveCounter &&Arg) : MoveCount(Arg.MoveCount) { ++MoveCount; }
+  };
+
+  unique_function<void(MoveCounter)> ByValF = [](MoveCounter) {};
+  int MoveCount = 0;
+  ByValF(MoveCounter(MoveCount));
+  EXPECT_EQ(1, MoveCount);
+
+  MoveCount = 0;
+  {
+    MoveCounter Counter{MoveCount};
+    ByValF(std::move(Counter));
+  }
+  EXPECT_EQ(2, MoveCount);
+
+  // Check that when we use an r-value reference we get no spurious copies.
+  unique_function<void(MoveCounter &&)> ByRefF = [](MoveCounter &&) {};
+  MoveCount = 0;
+  ByRefF(MoveCounter(MoveCount));
+  EXPECT_EQ(0, MoveCount);
+
+  MoveCount = 0;
+  {
+    MoveCounter Counter{MoveCount};
+    ByRefF(std::move(Counter));
+  }
+  EXPECT_EQ(0, MoveCount);
+
+  // If we use an r-value reference we can in fact make a stronger guarantee
+  // with an unmovable type.
+  struct Unmovable {
+    Unmovable() = default;
+    Unmovable(Unmovable &&) = delete;
+  };
+  unique_function<void(const Unmovable &)> UnmovableF = [](const Unmovable &) {
+  };
+  UnmovableF(Unmovable());
+  Unmovable X;
+  UnmovableF(X);
+}
+
+} // anonymous namespace
diff --git a/wpiutil/src/test/native/cpp/main.cpp b/wpiutil/src/test/native/cpp/main.cpp
new file mode 100644
index 0000000..1e5ecf0
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/main.cpp
@@ -0,0 +1,14 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved.                        */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include "gtest/gtest.h"
+
+int main(int argc, char** argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  int ret = RUN_ALL_TESTS();
+  return ret;
+}
diff --git a/wpiutil/src/test/native/cpp/priority_mutex_test.cpp b/wpiutil/src/test/native/cpp/priority_mutex_test.cpp
new file mode 100644
index 0000000..448a757
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/priority_mutex_test.cpp
@@ -0,0 +1,271 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2016-2019 FIRST. All Rights Reserved.                        */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include <wpi/priority_mutex.h>  // NOLINT(build/include_order)
+
+#include <atomic>
+#include <condition_variable>
+#include <thread>
+
+#include "gtest/gtest.h"
+
+namespace wpi {
+
+#ifdef WPI_HAVE_PRIORITY_MUTEX
+
+using std::chrono::system_clock;
+
+// Threading primitive used to notify many threads that a condition is now true.
+// The condition can not be cleared.
+class Notification {
+ public:
+  // Efficiently waits until the Notification has been notified once.
+  void Wait() {
+    std::unique_lock lock(m_mutex);
+    while (!m_set) {
+      m_condition.wait(lock);
+    }
+  }
+  // Sets the condition to true, and wakes all waiting threads.
+  void Notify() {
+    std::scoped_lock lock(m_mutex);
+    m_set = true;
+    m_condition.notify_all();
+  }
+
+ private:
+  // priority_mutex used for the notification and to protect the bit.
+  priority_mutex m_mutex;
+  // Condition for threads to sleep on.
+  std::condition_variable_any m_condition;
+  // Bool which is true when the notification has been notified.
+  bool m_set = false;
+};
+
+void SetProcessorAffinity(int32_t core_id) {
+  cpu_set_t cpuset;
+  CPU_ZERO(&cpuset);
+  CPU_SET(core_id, &cpuset);
+
+  pthread_t current_thread = pthread_self();
+  ASSERT_EQ(pthread_setaffinity_np(current_thread, sizeof(cpu_set_t), &cpuset),
+            0);
+}
+
+void SetThreadRealtimePriorityOrDie(int32_t priority) {
+  struct sched_param param;
+  // Set realtime priority for this thread
+  param.sched_priority = priority + sched_get_priority_min(SCHED_RR);
+  ASSERT_EQ(pthread_setschedparam(pthread_self(), SCHED_RR, &param), 0)
+      << ": Failed to set scheduler priority.";
+}
+
+// This thread holds the mutex and spins until signaled to release it and stop.
+template <typename MutexType>
+class LowPriorityThread {
+ public:
+  explicit LowPriorityThread(MutexType* mutex)
+      : m_mutex(mutex), m_hold_mutex(1), m_success(0) {}
+
+  void operator()() {
+    SetProcessorAffinity(0);
+    SetThreadRealtimePriorityOrDie(20);
+    m_mutex->lock();
+    m_started.Notify();
+    while (m_hold_mutex.test_and_set()) {
+    }
+    m_mutex->unlock();
+    m_success.store(1);
+  }
+
+  void WaitForStartup() { m_started.Wait(); }
+  void release_mutex() { m_hold_mutex.clear(); }
+  bool success() { return m_success.load(); }
+
+ private:
+  // priority_mutex to grab and release.
+  MutexType* m_mutex;
+  Notification m_started;
+  // Atomic type used to signal when the thread should unlock the mutex.
+  // Using a mutex to protect something could cause other issues, and a delay
+  // between setting and reading isn't a problem as long as the set is atomic.
+  std::atomic_flag m_hold_mutex;
+  std::atomic<int> m_success;
+};
+
+// This thread spins forever until signaled to stop.
+class BusyWaitingThread {
+ public:
+  BusyWaitingThread() : m_run(1), m_success(0) {}
+
+  void operator()() {
+    SetProcessorAffinity(0);
+    SetThreadRealtimePriorityOrDie(21);
+    system_clock::time_point start_time = system_clock::now();
+    m_started.Notify();
+    while (m_run.test_and_set()) {
+      // Have the busy waiting thread time out after a while.  If it times out,
+      // the test failed.
+      if (system_clock::now() - start_time > std::chrono::milliseconds(50)) {
+        return;
+      }
+    }
+    m_success.store(1);
+  }
+
+  void quit() { m_run.clear(); }
+  void WaitForStartup() { m_started.Wait(); }
+  bool success() { return m_success.load(); }
+
+ private:
+  // Use an atomic type to signal if the thread should be running or not.  A
+  // mutex could affect the scheduler, which isn't worth it.  A delay between
+  // setting and reading the new value is fine.
+  std::atomic_flag m_run;
+
+  Notification m_started;
+
+  std::atomic<int> m_success;
+};
+
+// This thread starts up, grabs the mutex, and then exits.
+template <typename MutexType>
+class HighPriorityThread {
+ public:
+  explicit HighPriorityThread(MutexType* mutex) : m_mutex(mutex) {}
+
+  void operator()() {
+    SetProcessorAffinity(0);
+    SetThreadRealtimePriorityOrDie(22);
+    m_started.Notify();
+    m_mutex->lock();
+    m_success.store(1);
+  }
+
+  void WaitForStartup() { m_started.Wait(); }
+  bool success() { return m_success.load(); }
+
+ private:
+  Notification m_started;
+  MutexType* m_mutex;
+  std::atomic<int> m_success{0};
+};
+
+// Class to test a MutexType to see if it solves the priority inheritance
+// problem.
+//
+// To run the test, we need 3 threads, and then 1 thread to kick the test off.
+// The threads must all run on the same core, otherwise they wouldn't starve
+// eachother. The threads and their roles are as follows:
+//
+// Low priority thread:
+//   Holds a lock that the high priority thread needs, and releases it upon
+//   request.
+// Medium priority thread:
+//   Hogs the processor so that the low priority thread will never run if it's
+//   priority doesn't get bumped.
+// High priority thread:
+//   Starts up and then goes to grab the lock that the low priority thread has.
+//
+// Control thread:
+//   Sets the deadlock up so that it will happen 100% of the time by making sure
+//   that each thread in order gets to the point that it needs to be at to cause
+//   the deadlock.
+template <typename MutexType>
+class InversionTestRunner {
+ public:
+  void operator()() {
+    // This thread must run at the highest priority or it can't coordinate the
+    // inversion.  This means that it can't busy wait or everything could
+    // starve.
+    SetThreadRealtimePriorityOrDie(23);
+
+    MutexType m;
+
+    // Start the lowest priority thread and wait until it holds the lock.
+    LowPriorityThread<MutexType> low(&m);
+    std::thread low_thread(std::ref(low));
+    low.WaitForStartup();
+
+    // Start the busy waiting thread and let it get to the loop.
+    BusyWaitingThread busy;
+    std::thread busy_thread(std::ref(busy));
+    busy.WaitForStartup();
+
+    // Start the high priority thread and let it become blocked on the lock.
+    HighPriorityThread<MutexType> high(&m);
+    std::thread high_thread(std::ref(high));
+    high.WaitForStartup();
+    // Startup and locking the mutex in the high priority thread aren't atomic,
+    // but pretty close.  Wait a bit to let it happen.
+    std::this_thread::sleep_for(std::chrono::milliseconds(10));
+
+    // Release the mutex in the low priority thread.  If done right, everything
+    // should finish now.
+    low.release_mutex();
+
+    // Wait for everything to finish and compute success.
+    high_thread.join();
+    busy.quit();
+    busy_thread.join();
+    low_thread.join();
+    m_success = low.success() && busy.success() && high.success();
+  }
+
+  bool success() { return m_success; }
+
+ private:
+  bool m_success = false;
+};
+
+// TODO: Fix roborio permissions to run as root.
+
+// Priority inversion test.
+TEST(MutexTest, DISABLED_PriorityInversionTest) {
+  InversionTestRunner<priority_mutex> runner;
+  std::thread runner_thread(std::ref(runner));
+  runner_thread.join();
+  EXPECT_TRUE(runner.success());
+}
+
+// Verify that the non-priority inversion mutex doesn't pass the test.
+TEST(MutexTest, DISABLED_StdMutexPriorityInversionTest) {
+  InversionTestRunner<std::mutex> runner;
+  std::thread runner_thread(std::ref(runner));
+  runner_thread.join();
+  EXPECT_FALSE(runner.success());
+}
+
+// Smoke test to make sure that mutexes lock and unlock.
+TEST(MutexTest, TryLock) {
+  priority_mutex m;
+  m.lock();
+  EXPECT_FALSE(m.try_lock());
+  m.unlock();
+  EXPECT_TRUE(m.try_lock());
+}
+
+// Priority inversion test.
+TEST(MutexTest, DISABLED_ReentrantPriorityInversionTest) {
+  InversionTestRunner<priority_recursive_mutex> runner;
+  std::thread runner_thread(std::ref(runner));
+  runner_thread.join();
+  EXPECT_TRUE(runner.success());
+}
+
+// Smoke test to make sure that mutexes lock and unlock.
+TEST(MutexTest, ReentrantTryLock) {
+  priority_recursive_mutex m;
+  m.lock();
+  EXPECT_TRUE(m.try_lock());
+  m.unlock();
+  EXPECT_TRUE(m.try_lock());
+}
+
+#endif  // WPI_HAVE_PRIORITY_MUTEX
+
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/raw_uv_stream_test.cpp b/wpiutil/src/test/native/cpp/raw_uv_stream_test.cpp
new file mode 100644
index 0000000..d1a0f6c
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/raw_uv_stream_test.cpp
@@ -0,0 +1,58 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include "wpi/raw_uv_ostream.h"  // NOLINT(build/include_order)
+
+#include "gtest/gtest.h"
+
+namespace wpi {
+
+TEST(RawUvStreamTest, BasicWrite) {
+  SmallVector<uv::Buffer, 4> bufs;
+  raw_uv_ostream os(bufs, 1024);
+  os << "12";
+  os << "34";
+  ASSERT_EQ(bufs.size(), 1u);
+  ASSERT_EQ(bufs[0].len, 4u);
+  ASSERT_EQ(bufs[0].base[0], '1');
+  ASSERT_EQ(bufs[0].base[1], '2');
+  ASSERT_EQ(bufs[0].base[2], '3');
+  ASSERT_EQ(bufs[0].base[3], '4');
+}
+
+TEST(RawUvStreamTest, BoundaryWrite) {
+  SmallVector<uv::Buffer, 4> bufs;
+  raw_uv_ostream os(bufs, 4);
+  ASSERT_EQ(bufs.size(), 0u);
+  os << "12";
+  ASSERT_EQ(bufs.size(), 1u);
+  os << "34";
+  ASSERT_EQ(bufs.size(), 1u);
+  os << "56";
+  ASSERT_EQ(bufs.size(), 2u);
+}
+
+TEST(RawUvStreamTest, LargeWrite) {
+  SmallVector<uv::Buffer, 4> bufs;
+  raw_uv_ostream os(bufs, 4);
+  os << "123456";
+  ASSERT_EQ(bufs.size(), 2u);
+  ASSERT_EQ(bufs[1].len, 2u);
+  ASSERT_EQ(bufs[1].base[0], '5');
+}
+
+TEST(RawUvStreamTest, PrevDataWrite) {
+  SmallVector<uv::Buffer, 4> bufs;
+  bufs.emplace_back(uv::Buffer::Allocate(1024));
+  raw_uv_ostream os(bufs, 1024);
+  os << "1234";
+  ASSERT_EQ(bufs.size(), 2u);
+  ASSERT_EQ(bufs[0].len, 1024u);
+  ASSERT_EQ(bufs[1].len, 4u);
+}
+
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/sha1Test.cpp b/wpiutil/src/test/native/cpp/sha1Test.cpp
new file mode 100644
index 0000000..08d85fe
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/sha1Test.cpp
@@ -0,0 +1,92 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved.                        */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+/*
+    test_sha1.cpp - test program of
+
+    ============
+    SHA-1 in C++
+    ============
+
+    100% Public Domain.
+
+    Original C Code
+        -- Steve Reid <steve@edmweb.com>
+    Small changes to fit into bglibs
+        -- Bruce Guenter <bruce@untroubled.org>
+    Translation to simpler C++ Code
+        -- Volker Grabsch <vog@notjusthosting.com>
+*/
+
+#include <string>
+
+#include "gtest/gtest.h"
+#include "wpi/sha1.h"
+
+namespace wpi {
+
+/*
+ * The 3 test vectors from FIPS PUB 180-1
+ */
+
+TEST(SHA1Test, Standard1) {
+  SHA1 checksum;
+  checksum.Update("abc");
+  ASSERT_EQ(checksum.Final(), "a9993e364706816aba3e25717850c26c9cd0d89d");
+}
+
+TEST(SHA1Test, Standard2) {
+  SHA1 checksum;
+  checksum.Update("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
+  ASSERT_EQ(checksum.Final(), "84983e441c3bd26ebaae4aa1f95129e5e54670f1");
+}
+
+TEST(SHA1Test, Standard3) {
+  SHA1 checksum;
+  // A million repetitions of 'a'
+  for (int i = 0; i < 1000000 / 200; ++i) {
+    checksum.Update(
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
+  }
+  ASSERT_EQ(checksum.Final(), "34aa973cd4c4daa4f61eeb2bdbad27316534016f");
+}
+
+/*
+ * Other tests
+ */
+
+TEST(SHA1Test, OtherNoString) {
+  SHA1 checksum;
+  ASSERT_EQ(checksum.Final(), "da39a3ee5e6b4b0d3255bfef95601890afd80709");
+}
+
+TEST(SHA1Test, OtherEmptyString) {
+  SHA1 checksum;
+  checksum.Update("");
+  ASSERT_EQ(checksum.Final(), "da39a3ee5e6b4b0d3255bfef95601890afd80709");
+}
+
+TEST(SHA1Test, OtherABCDE) {
+  SHA1 checksum;
+  checksum.Update("abcde");
+  ASSERT_EQ(checksum.Final(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334");
+}
+
+TEST(SHA1Test, Concurrent) {
+  // Two concurrent checksum calculations
+  SHA1 checksum1, checksum2;
+  checksum1.Update("abc");
+  ASSERT_EQ(checksum2.Final(),
+            "da39a3ee5e6b4b0d3255bfef95601890afd80709"); /* "" */
+  ASSERT_EQ(checksum1.Final(),
+            "a9993e364706816aba3e25717850c26c9cd0d89d"); /* "abc" */
+}
+
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/sigslot/function-traits.cpp b/wpiutil/src/test/native/cpp/sigslot/function-traits.cpp
new file mode 100644
index 0000000..240e688
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/sigslot/function-traits.cpp
@@ -0,0 +1,135 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+/*
+
+Sigslot, a signal-slot library
+
+https://github.com/palacaze/sigslot
+
+MIT License
+
+Copyright (c) 2017 Pierre-Antoine Lacaze
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+
+#include "wpi/Signal.h"  // NOLINT(build/include_order)
+
+#include "gtest/gtest.h"  // NOLINT(build/include_order)
+
+#include <type_traits>
+
+using namespace wpi::sig::trait;
+
+namespace {
+
+void f1(int, char, float) {}
+void f2(int, char, float) noexcept {}
+
+struct oo {
+  void operator()(int) {}
+  void operator()(int, char, float) {}
+};
+
+struct s {
+  static void s1(int, char, float) {}
+  static void s2(int, char, float) noexcept {}
+
+  void f1(int, char, float) {}
+  void f2(int, char, float) const {}
+  void f3(int, char, float) volatile {}
+  void f4(int, char, float) const volatile {}
+  void f5(int, char, float) noexcept {}
+  void f6(int, char, float) const noexcept {}
+  void f7(int, char, float) volatile noexcept {}
+  void f8(int, char, float) const volatile noexcept {}
+};
+
+struct o1 {
+  void operator()(int, char, float) {}
+};
+struct o2 {
+  void operator()(int, char, float) const {}
+};
+struct o3 {
+  void operator()(int, char, float) volatile {}
+};
+struct o4 {
+  void operator()(int, char, float) const volatile {}
+};
+struct o5 {
+  void operator()(int, char, float) noexcept {}
+};
+struct o6 {
+  void operator()(int, char, float) const noexcept {}
+};
+struct o7 {
+  void operator()(int, char, float) volatile noexcept {}
+};
+struct o8 {
+  void operator()(int, char, float) const volatile noexcept {}
+};
+
+using t = typelist<int, char, float>;
+
+static_assert(is_callable_v<t, decltype(f1)>, "");
+static_assert(is_callable_v<t, decltype(f2)>, "");
+static_assert(is_callable_v<t, decltype(&s::s1)>, "");
+static_assert(is_callable_v<t, decltype(&s::s2)>, "");
+static_assert(is_callable_v<t, oo>, "");
+static_assert(is_callable_v<t, decltype(&s::f1), s*>, "");
+static_assert(is_callable_v<t, decltype(&s::f2), s*>, "");
+static_assert(is_callable_v<t, decltype(&s::f3), s*>, "");
+static_assert(is_callable_v<t, decltype(&s::f4), s*>, "");
+static_assert(is_callable_v<t, decltype(&s::f5), s*>, "");
+static_assert(is_callable_v<t, decltype(&s::f6), s*>, "");
+static_assert(is_callable_v<t, decltype(&s::f7), s*>, "");
+static_assert(is_callable_v<t, decltype(&s::f8), s*>, "");
+static_assert(is_callable_v<t, o1>, "");
+static_assert(is_callable_v<t, o2>, "");
+static_assert(is_callable_v<t, o3>, "");
+static_assert(is_callable_v<t, o4>, "");
+static_assert(is_callable_v<t, o5>, "");
+static_assert(is_callable_v<t, o6>, "");
+static_assert(is_callable_v<t, o7>, "");
+static_assert(is_callable_v<t, o8>, "");
+
+}  // namespace
+
+namespace wpi {
+
+TEST(Signal, FunctionTraits) {
+  auto l1 = [](int, char, float) {};
+  auto l2 = [&](int, char, float) mutable {};
+  auto l3 = [&](auto...) mutable {};
+
+  static_assert(is_callable_v<t, decltype(l1)>, "");
+  static_assert(is_callable_v<t, decltype(l2)>, "");
+  static_assert(is_callable_v<t, decltype(l3)>, "");
+
+  f1(0, '0', 0.0);
+  f2(0, '0', 0.0);
+}
+
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/sigslot/recursive.cpp b/wpiutil/src/test/native/cpp/sigslot/recursive.cpp
new file mode 100644
index 0000000..85ae9a9
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/sigslot/recursive.cpp
@@ -0,0 +1,97 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+/*
+
+Sigslot, a signal-slot library
+
+https://github.com/palacaze/sigslot
+
+MIT License
+
+Copyright (c) 2017 Pierre-Antoine Lacaze
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+
+#include "wpi/Signal.h"  // NOLINT(build/include_order)
+
+#include "gtest/gtest.h"
+
+namespace {
+
+template <typename T>
+struct object {
+  object(T i) : v{i} {}  // NOLINT(runtime/explicit)
+
+  void inc_val(const T& i) {
+    if (i != v) {
+      v++;
+      sig(v);
+    }
+  }
+
+  void dec_val(const T& i) {
+    if (i != v) {
+      v--;
+      sig(v);
+    }
+  }
+
+  T v;
+  wpi::sig::Signal_r<T> sig;
+};
+
+}  // namespace
+
+namespace wpi {
+
+TEST(Signal, Recursive) {
+  object<int> i1(-1);
+  object<int> i2(10);
+
+  i1.sig.connect(&object<int>::dec_val, &i2);
+  i2.sig.connect(&object<int>::inc_val, &i1);
+
+  i1.inc_val(0);
+
+  ASSERT_EQ(i1.v, i2.v);
+}
+
+TEST(Signal, SelfRecursive) {
+  int i = 0;
+
+  wpi::sig::Signal_r<int> s;
+  s.connect([&](int v) {
+    if (i < 10) {
+      i++;
+      s(v + 1);
+    }
+  });
+
+  s(0);
+
+  ASSERT_EQ(i, 10);
+}
+
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/sigslot/signal-extended.cpp b/wpiutil/src/test/native/cpp/sigslot/signal-extended.cpp
new file mode 100644
index 0000000..1ebbc8e
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/sigslot/signal-extended.cpp
@@ -0,0 +1,140 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+/*
+
+Sigslot, a signal-slot library
+
+https://github.com/palacaze/sigslot
+
+MIT License
+
+Copyright (c) 2017 Pierre-Antoine Lacaze
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+
+#include "wpi/Signal.h"  // NOLINT(build/include_order)
+
+#include "gtest/gtest.h"
+
+using namespace wpi::sig;
+
+namespace {
+
+int sum = 0;
+
+void f(Connection& c, int i) {
+  sum += i;
+  c.disconnect();
+}
+
+struct s {
+  static void sf(Connection& c, int i) {
+    sum += i;
+    c.disconnect();
+  }
+  void f(Connection& c, int i) {
+    sum += i;
+    c.disconnect();
+  }
+};
+
+struct o {
+  void operator()(Connection& c, int i) {
+    sum += i;
+    c.disconnect();
+  }
+};
+
+}  // namespace
+
+namespace wpi {
+
+TEST(SignalExtended, FreeConnection) {
+  sum = 0;
+  Signal<int> sig;
+  sig.connect_extended(f);
+
+  sig(1);
+  ASSERT_EQ(sum, 1);
+  sig(1);
+  ASSERT_EQ(sum, 1);
+}
+
+TEST(SignalExtended, StaticConnection) {
+  sum = 0;
+  Signal<int> sig;
+  sig.connect_extended(&s::sf);
+
+  sig(1);
+  ASSERT_EQ(sum, 1);
+  sig(1);
+  ASSERT_EQ(sum, 1);
+}
+
+TEST(SignalExtended, PmfConnection) {
+  sum = 0;
+  Signal<int> sig;
+  s p;
+  sig.connect_extended(&s::f, &p);
+
+  sig(1);
+  ASSERT_EQ(sum, 1);
+  sig(1);
+  ASSERT_EQ(sum, 1);
+}
+
+TEST(SignalExtended, FunctionObjectConnection) {
+  sum = 0;
+  Signal<int> sig;
+  sig.connect_extended(o{});
+
+  sig(1);
+  ASSERT_EQ(sum, 1);
+  sig(1);
+  ASSERT_EQ(sum, 1);
+}
+
+TEST(SignalExtended, LambdaConnection) {
+  sum = 0;
+  Signal<int> sig;
+
+  sig.connect_extended([&](Connection& c, int i) {
+    sum += i;
+    c.disconnect();
+  });
+  sig(1);
+  ASSERT_EQ(sum, 1);
+
+  sig.connect_extended([&](Connection& c, int i) mutable {
+    sum += 2 * i;
+    c.disconnect();
+  });
+  sig(1);
+  ASSERT_EQ(sum, 3);
+  sig(1);
+  ASSERT_EQ(sum, 3);
+}
+
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/sigslot/signal-threaded.cpp b/wpiutil/src/test/native/cpp/sigslot/signal-threaded.cpp
new file mode 100644
index 0000000..c4f7cdb
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/sigslot/signal-threaded.cpp
@@ -0,0 +1,93 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+/*
+
+Sigslot, a signal-slot library
+
+https://github.com/palacaze/sigslot
+
+MIT License
+
+Copyright (c) 2017 Pierre-Antoine Lacaze
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+
+#include "wpi/Signal.h"  // NOLINT(build/include_order)
+
+#include "gtest/gtest.h"  // NOLINT(build/include_order)
+
+#include <array>
+#include <atomic>
+#include <thread>
+
+using namespace wpi::sig;
+
+namespace {
+
+std::atomic<int> sum{0};
+
+void f(int i) { sum += i; }
+
+void emit_many(Signal_mt<int>& sig) {
+  for (int i = 0; i < 10000; ++i) sig(1);
+}
+
+void connect_emit(Signal_mt<int>& sig) {
+  for (int i = 0; i < 100; ++i) {
+    auto s = sig.connect_scoped(f);
+    for (int j = 0; j < 100; ++j) sig(1);
+  }
+}
+
+}  // namespace
+
+namespace wpi {
+
+TEST(Signal, ThreadedMix) {
+  sum = 0;
+
+  Signal_mt<int> sig;
+
+  std::array<std::thread, 10> threads;
+  for (auto& t : threads) t = std::thread(connect_emit, std::ref(sig));
+
+  for (auto& t : threads) t.join();
+}
+
+TEST(Signal, ThreadedEmission) {
+  sum = 0;
+
+  Signal_mt<int> sig;
+  sig.connect(f);
+
+  std::array<std::thread, 10> threads;
+  for (auto& t : threads) t = std::thread(emit_many, std::ref(sig));
+
+  for (auto& t : threads) t.join();
+
+  ASSERT_EQ(sum, 100000);
+}
+
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/sigslot/signal-tracking.cpp b/wpiutil/src/test/native/cpp/sigslot/signal-tracking.cpp
new file mode 100644
index 0000000..89ffd36
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/sigslot/signal-tracking.cpp
@@ -0,0 +1,181 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+/*
+
+Sigslot, a signal-slot library
+
+https://github.com/palacaze/sigslot
+
+MIT License
+
+Copyright (c) 2017 Pierre-Antoine Lacaze
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+
+#include "wpi/Signal.h"  // NOLINT(build/include_order)
+
+#include "gtest/gtest.h"  // NOLINT(build/include_order)
+
+#include <cmath>
+#include <sstream>
+#include <string>
+
+using namespace wpi::sig;
+
+namespace {
+
+int sum = 0;
+
+void f1(int i) { sum += i; }
+struct o1 {
+  void operator()(int i) { sum += 2 * i; }
+};
+
+struct s {
+  void f1(int i) { sum += i; }
+  void f2(int i) const { sum += 2 * i; }
+};
+
+struct oo {
+  void operator()(int i) { sum += i; }
+  void operator()(double i) { sum += std::round(4 * i); }
+};
+
+struct dummy {};
+
+static_assert(trait::is_callable_v<trait::typelist<int>, decltype(&s::f1),
+                                   std::shared_ptr<s>>,
+              "");
+
+}  // namespace
+
+namespace wpi {
+
+TEST(Signal, TrackShared) {
+  sum = 0;
+  Signal<int> sig;
+
+  auto s1 = std::make_shared<s>();
+  sig.connect(&s::f1, s1);
+
+  auto s2 = std::make_shared<s>();
+  std::weak_ptr<s> w2 = s2;
+  sig.connect(&s::f2, w2);
+
+  sig(1);
+  ASSERT_EQ(sum, 3);
+
+  s1.reset();
+  sig(1);
+  ASSERT_EQ(sum, 5);
+
+  s2.reset();
+  sig(1);
+  ASSERT_EQ(sum, 5);
+}
+
+TEST(Signal, TrackOther) {
+  sum = 0;
+  Signal<int> sig;
+
+  auto d1 = std::make_shared<dummy>();
+  sig.connect(f1, d1);
+
+  auto d2 = std::make_shared<dummy>();
+  std::weak_ptr<dummy> w2 = d2;
+  sig.connect(o1(), w2);
+
+  sig(1);
+  ASSERT_EQ(sum, 3);
+
+  d1.reset();
+  sig(1);
+  ASSERT_EQ(sum, 5);
+
+  d2.reset();
+  sig(1);
+  ASSERT_EQ(sum, 5);
+}
+
+TEST(Signal, TrackOverloadedFunctionObject) {
+  sum = 0;
+  Signal<int> sig;
+  Signal<double> sig1;
+
+  auto d1 = std::make_shared<dummy>();
+  sig.connect(oo{}, d1);
+  sig(1);
+  ASSERT_EQ(sum, 1);
+
+  d1.reset();
+  sig(1);
+  ASSERT_EQ(sum, 1);
+
+  auto d2 = std::make_shared<dummy>();
+  std::weak_ptr<dummy> w2 = d2;
+  sig1.connect(oo{}, w2);
+  sig1(1);
+  ASSERT_EQ(sum, 5);
+
+  d2.reset();
+  sig1(1);
+  ASSERT_EQ(sum, 5);
+}
+
+TEST(Signal, TrackGenericLambda) {
+  std::stringstream s;
+
+  auto f = [&](auto a, auto... args) {
+    using result_t = int[];
+    s << a;
+    result_t r{
+        1,
+        ((void)(s << args), 1)...,
+    };
+    (void)r;
+  };
+
+  Signal<int> sig1;
+  Signal<std::string> sig2;
+  Signal<double> sig3;
+
+  auto d1 = std::make_shared<dummy>();
+  sig1.connect(f, d1);
+  sig2.connect(f, d1);
+  sig3.connect(f, d1);
+
+  sig1(1);
+  sig2("foo");
+  sig3(4.1);
+  ASSERT_EQ(s.str(), "1foo4.1");
+
+  d1.reset();
+  sig1(2);
+  sig2("bar");
+  sig3(3.0);
+  ASSERT_EQ(s.str(), "1foo4.1");
+}
+
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/sigslot/signal.cpp b/wpiutil/src/test/native/cpp/sigslot/signal.cpp
new file mode 100644
index 0000000..a4f9208
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/sigslot/signal.cpp
@@ -0,0 +1,540 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+/*
+
+Sigslot, a signal-slot library
+
+https://github.com/palacaze/sigslot
+
+MIT License
+
+Copyright (c) 2017 Pierre-Antoine Lacaze
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+
+#include "wpi/Signal.h"  // NOLINT(build/include_order)
+
+#include "gtest/gtest.h"  // NOLINT(build/include_order)
+
+#include <cmath>
+#include <sstream>
+#include <string>
+
+using namespace wpi::sig;
+
+namespace {
+
+int sum = 0;
+
+void f1(int i) { sum += i; }
+void f2(int i) /*noexcept*/ { sum += 2 * i; }
+
+struct s {
+  static void s1(int i) { sum += i; }
+  static void s2(int i) noexcept { sum += 2 * i; }
+
+  void f1(int i) { sum += i; }
+  void f2(int i) const { sum += i; }
+  void f3(int i) volatile { sum += i; }
+  void f4(int i) const volatile { sum += i; }
+  void f5(int i) noexcept { sum += i; }
+  void f6(int i) const noexcept { sum += i; }
+  void f7(int i) volatile noexcept { sum += i; }
+  void f8(int i) const volatile noexcept { sum += i; }
+};
+
+struct oo {
+  void operator()(int i) { sum += i; }
+  void operator()(double i) { sum += std::round(4 * i); }
+};
+
+struct o1 {
+  void operator()(int i) { sum += i; }
+};
+struct o2 {
+  void operator()(int i) const { sum += i; }
+};
+struct o3 {
+  void operator()(int i) volatile { sum += i; }
+};
+struct o4 {
+  void operator()(int i) const volatile { sum += i; }
+};
+struct o5 {
+  void operator()(int i) noexcept { sum += i; }
+};
+struct o6 {
+  void operator()(int i) const noexcept { sum += i; }
+};
+struct o7 {
+  void operator()(int i) volatile noexcept { sum += i; }
+};
+struct o8 {
+  void operator()(int i) const volatile noexcept { sum += i; }
+};
+
+}  // namespace
+
+namespace wpi {
+
+TEST(Signal, FreeConnection) {
+  sum = 0;
+  Signal<int> sig;
+
+  auto c1 = sig.connect_connection(f1);
+  sig(1);
+  ASSERT_EQ(sum, 1);
+
+  sig.connect(f2);
+  sig(1);
+  ASSERT_EQ(sum, 4);
+}
+
+TEST(Signal, StaticConnection) {
+  sum = 0;
+  Signal<int> sig;
+
+  sig.connect(&s::s1);
+  sig(1);
+  ASSERT_EQ(sum, 1);
+
+  sig.connect(&s::s2);
+  sig(1);
+  ASSERT_EQ(sum, 4);
+}
+
+TEST(Signal, PmfConnection) {
+  sum = 0;
+  Signal<int> sig;
+  s p;
+
+  sig.connect(&s::f1, &p);
+  sig.connect(&s::f2, &p);
+  sig.connect(&s::f3, &p);
+  sig.connect(&s::f4, &p);
+  sig.connect(&s::f5, &p);
+  sig.connect(&s::f6, &p);
+  sig.connect(&s::f7, &p);
+  sig.connect(&s::f8, &p);
+
+  sig(1);
+  ASSERT_EQ(sum, 8);
+}
+
+TEST(Signal, ConstPmfConnection) {
+  sum = 0;
+  Signal<int> sig;
+  const s p;
+
+  sig.connect(&s::f2, &p);
+  sig.connect(&s::f4, &p);
+  sig.connect(&s::f6, &p);
+  sig.connect(&s::f8, &p);
+
+  sig(1);
+  ASSERT_EQ(sum, 4);
+}
+
+TEST(Signal, FunctionObjectConnection) {
+  sum = 0;
+  Signal<int> sig;
+
+  sig.connect(o1{});
+  sig.connect(o2{});
+  sig.connect(o3{});
+  sig.connect(o4{});
+  sig.connect(o5{});
+  sig.connect(o6{});
+  sig.connect(o7{});
+  sig.connect(o8{});
+
+  sig(1);
+  ASSERT_EQ(sum, 8);
+}
+
+TEST(Signal, OverloadedFunctionObjectConnection) {
+  sum = 0;
+  Signal<int> sig;
+  Signal<double> sig1;
+
+  sig.connect(oo{});
+  sig(1);
+  ASSERT_EQ(sum, 1);
+
+  sig1.connect(oo{});
+  sig1(1);
+  ASSERT_EQ(sum, 5);
+}
+
+TEST(Signal, LambdaConnection) {
+  sum = 0;
+  Signal<int> sig;
+
+  sig.connect([&](int i) { sum += i; });
+  sig(1);
+  ASSERT_EQ(sum, 1);
+
+  sig.connect([&](int i) mutable { sum += 2 * i; });
+  sig(1);
+  ASSERT_EQ(sum, 4);
+}
+
+TEST(Signal, GenericLambdaConnection) {
+  std::stringstream s;
+
+  auto f = [&](auto a, auto... args) {
+    using result_t = int[];
+    s << a;
+    result_t r{
+        1,
+        ((void)(s << args), 1)...,
+    };
+    (void)r;
+  };
+
+  Signal<int> sig1;
+  Signal<std::string> sig2;
+  Signal<double> sig3;
+
+  sig1.connect(f);
+  sig2.connect(f);
+  sig3.connect(f);
+  sig1(1);
+  sig2("foo");
+  sig3(4.1);
+
+  ASSERT_EQ(s.str(), "1foo4.1");
+}
+
+TEST(Signal, LvalueEmission) {
+  sum = 0;
+  Signal<int> sig;
+
+  auto c1 = sig.connect_connection(f1);
+  int v = 1;
+  sig(v);
+  ASSERT_EQ(sum, 1);
+
+  sig.connect(f2);
+  sig(v);
+  ASSERT_EQ(sum, 4);
+}
+
+TEST(Signal, Mutation) {
+  int res = 0;
+  Signal<int&> sig;
+
+  sig.connect([](int& r) { r += 1; });
+  sig(res);
+  ASSERT_EQ(res, 1);
+
+  sig.connect([](int& r) mutable { r += 2; });
+  sig(res);
+  ASSERT_EQ(res, 4);
+}
+
+TEST(Signal, CompatibleArgs) {
+  long ll = 0;  // NOLINT(runtime/int)
+  std::string ss;
+  short ii = 0;  // NOLINT(runtime/int)
+
+  auto f = [&](long l, const std::string& s, short i) {  // NOLINT(runtime/int)
+    ll = l;
+    ss = s;
+    ii = i;
+  };
+
+  Signal<int, std::string, bool> sig;
+  sig.connect(f);
+  sig('0', "foo", true);
+
+  ASSERT_EQ(ll, 48);
+  ASSERT_EQ(ss, "foo");
+  ASSERT_EQ(ii, 1);
+}
+
+TEST(Signal, Disconnection) {
+  // test removing only connected
+  {
+    sum = 0;
+    Signal<int> sig;
+
+    auto sc = sig.connect_connection(f1);
+    sig(1);
+    ASSERT_EQ(sum, 1);
+
+    sc.disconnect();
+    sig(1);
+    ASSERT_EQ(sum, 1);
+  }
+
+  // test removing first connected
+  {
+    sum = 0;
+    Signal<int> sig;
+
+    auto sc = sig.connect_connection(f1);
+    sig(1);
+    ASSERT_EQ(sum, 1);
+
+    sig.connect(f2);
+    sig(1);
+    ASSERT_EQ(sum, 4);
+
+    sc.disconnect();
+    sig(1);
+    ASSERT_EQ(sum, 6);
+  }
+
+  // test removing last connected
+  {
+    sum = 0;
+    Signal<int> sig;
+
+    sig.connect(f1);
+    sig(1);
+    ASSERT_EQ(sum, 1);
+
+    auto sc = sig.connect_connection(f2);
+    sig(1);
+    ASSERT_EQ(sum, 4);
+
+    sc.disconnect();
+    sig(1);
+    ASSERT_EQ(sum, 5);
+  }
+}
+
+TEST(Signal, ScopedConnection) {
+  sum = 0;
+  Signal<int> sig;
+
+  {
+    auto sc1 = sig.connect_scoped(f1);
+    sig(1);
+    ASSERT_EQ(sum, 1);
+
+    auto sc2 = sig.connect_scoped(f2);
+    sig(1);
+    ASSERT_EQ(sum, 4);
+  }
+
+  sig(1);
+  ASSERT_EQ(sum, 4);
+
+  sum = 0;
+
+  {
+    ScopedConnection sc1 = sig.connect_connection(f1);
+    sig(1);
+    ASSERT_EQ(sum, 1);
+
+    ScopedConnection sc2 = sig.connect_connection(f2);
+    sig(1);
+    ASSERT_EQ(sum, 4);
+  }
+
+  sig(1);
+  ASSERT_EQ(sum, 4);
+}
+
+TEST(Signal, ConnectionBlocking) {
+  sum = 0;
+  Signal<int> sig;
+
+  auto c1 = sig.connect_connection(f1);
+  sig.connect(f2);
+  sig(1);
+  ASSERT_EQ(sum, 3);
+
+  c1.block();
+  sig(1);
+  ASSERT_EQ(sum, 5);
+
+  c1.unblock();
+  sig(1);
+  ASSERT_EQ(sum, 8);
+}
+
+TEST(Signal, ConnectionBlocker) {
+  sum = 0;
+  Signal<int> sig;
+
+  auto c1 = sig.connect_connection(f1);
+  sig.connect(f2);
+  sig(1);
+  ASSERT_EQ(sum, 3);
+
+  {
+    auto cb = c1.blocker();
+    sig(1);
+    ASSERT_EQ(sum, 5);
+  }
+
+  sig(1);
+  ASSERT_EQ(sum, 8);
+}
+
+TEST(Signal, SignalBlocking) {
+  sum = 0;
+  Signal<int> sig;
+
+  sig.connect(f1);
+  sig.connect(f2);
+  sig(1);
+  ASSERT_EQ(sum, 3);
+
+  sig.block();
+  sig(1);
+  ASSERT_EQ(sum, 3);
+
+  sig.unblock();
+  sig(1);
+  ASSERT_EQ(sum, 6);
+}
+
+TEST(Signal, AllDisconnection) {
+  sum = 0;
+  Signal<int> sig;
+
+  sig.connect(f1);
+  sig.connect(f2);
+  sig(1);
+  ASSERT_EQ(sum, 3);
+
+  sig.disconnect_all();
+  sig(1);
+  ASSERT_EQ(sum, 3);
+}
+
+TEST(Signal, ConnectionCopyingMoving) {
+  sum = 0;
+  Signal<int> sig;
+
+  auto sc1 = sig.connect_connection(f1);
+  auto sc2 = sig.connect_connection(f2);
+
+  auto sc3 = sc1;
+  auto sc4{sc2};
+
+  auto sc5 = std::move(sc3);
+  auto sc6{std::move(sc4)};
+
+  sig(1);
+  ASSERT_EQ(sum, 3);
+
+  sc5.block();
+  sig(1);
+  ASSERT_EQ(sum, 5);
+
+  sc1.unblock();
+  sig(1);
+  ASSERT_EQ(sum, 8);
+
+  sc6.disconnect();
+  sig(1);
+  ASSERT_EQ(sum, 9);
+}
+
+TEST(Signal, ScopedConnectionMoving) {
+  sum = 0;
+  Signal<int> sig;
+
+  {
+    auto sc1 = sig.connect_scoped(f1);
+    sig(1);
+    ASSERT_EQ(sum, 1);
+
+    auto sc2 = sig.connect_scoped(f2);
+    sig(1);
+    ASSERT_EQ(sum, 4);
+
+    auto sc3 = std::move(sc1);
+    sig(1);
+    ASSERT_EQ(sum, 7);
+
+    auto sc4{std::move(sc2)};
+    sig(1);
+    ASSERT_EQ(sum, 10);
+  }
+
+  sig(1);
+  ASSERT_EQ(sum, 10);
+}
+
+TEST(Signal, SignalMoving) {
+  sum = 0;
+  Signal<int> sig;
+
+  sig.connect(f1);
+  sig.connect(f2);
+
+  sig(1);
+  ASSERT_EQ(sum, 3);
+
+  auto sig2 = std::move(sig);
+  sig2(1);
+  ASSERT_EQ(sum, 6);
+
+  auto sig3 = std::move(sig2);
+  sig3(1);
+  ASSERT_EQ(sum, 9);
+}
+
+template <typename T>
+struct object {
+  object();
+  object(T i) : v{i} {}  // NOLINT(runtime/explicit)
+
+  const T& val() const { return v; }
+  T& val() { return v; }
+  void set_val(const T& i) {
+    if (i != v) {
+      v = i;
+      s(i);
+    }
+  }
+
+  Signal<T>& sig() { return s; }
+
+ private:
+  T v;
+  Signal<T> s;
+};
+
+TEST(Signal, Loop) {
+  object<int> i1(0);
+  object<int> i2(3);
+
+  i1.sig().connect(&object<int>::set_val, &i2);
+  i2.sig().connect(&object<int>::set_val, &i1);
+
+  i1.set_val(1);
+
+  ASSERT_EQ(i1.val(), 1);
+  ASSERT_EQ(i2.val(), 1);
+}
+
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/spinlock_bench.cpp b/wpiutil/src/test/native/cpp/spinlock_bench.cpp
new file mode 100644
index 0000000..b36e558
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/spinlock_bench.cpp
@@ -0,0 +1,166 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include "wpi/spinlock.h"  // NOLINT(build/include_order)
+
+#include <chrono>
+#include <iostream>
+#include <mutex>
+#include <thread>
+
+#include "gtest/gtest.h"
+#include "wpi/mutex.h"
+
+static std::mutex std_mutex;
+static std::recursive_mutex std_recursive_mutex;
+static wpi::mutex wpi_mutex;
+static wpi::recursive_mutex wpi_recursive_mutex;
+static wpi::spinlock spinlock;
+static wpi::recursive_spinlock1 recursive_spinlock1;
+static wpi::recursive_spinlock2 recursive_spinlock2;
+static wpi::recursive_spinlock recursive_spinlock;
+
+TEST(SpinlockTest, Benchmark) {
+  using std::chrono::duration_cast;
+  using std::chrono::high_resolution_clock;
+  using std::chrono::microseconds;
+
+  // warmup
+  std::thread thr([]() {
+    int value = 0;
+
+    auto start = high_resolution_clock::now();
+    for (int i = 0; i < 10000000; i++) {
+      std::scoped_lock lock(std_mutex);
+      ++value;
+    }
+    auto stop = high_resolution_clock::now();
+    (void)start;
+    (void)stop;
+  });
+  thr.join();
+
+  std::thread thrb([]() {
+    int value = 0;
+
+    auto start = high_resolution_clock::now();
+    for (int i = 0; i < 1000000; i++) {
+      std::scoped_lock lock(std_mutex);
+      ++value;
+    }
+    auto stop = high_resolution_clock::now();
+    std::cout << "std::mutex sizeof: " << sizeof(std_mutex)
+              << " time: " << duration_cast<microseconds>(stop - start).count()
+              << " value: " << value << "\n";
+  });
+  thrb.join();
+
+  std::thread thrb2([]() {
+    int value = 0;
+
+    auto start = high_resolution_clock::now();
+    for (int i = 0; i < 1000000; i++) {
+      std::scoped_lock lock(std_recursive_mutex);
+      ++value;
+    }
+    auto stop = high_resolution_clock::now();
+    std::cout << "std::recursive_mutex sizeof: " << sizeof(std_recursive_mutex)
+              << " time: " << duration_cast<microseconds>(stop - start).count()
+              << " value: " << value << "\n";
+  });
+  thrb2.join();
+
+  std::thread thr2([]() {
+    int value = 0;
+
+    auto start = high_resolution_clock::now();
+    for (int i = 0; i < 1000000; i++) {
+      std::scoped_lock lock(wpi_mutex);
+      ++value;
+    }
+    auto stop = high_resolution_clock::now();
+    std::cout << "wpi::mutex sizeof: " << sizeof(wpi_mutex)
+              << " time: " << duration_cast<microseconds>(stop - start).count()
+              << " value: " << value << "\n";
+  });
+  thr2.join();
+
+  std::thread thr2b([]() {
+    int value = 0;
+
+    auto start = high_resolution_clock::now();
+    for (int i = 0; i < 1000000; i++) {
+      std::scoped_lock lock(wpi_recursive_mutex);
+      ++value;
+    }
+    auto stop = high_resolution_clock::now();
+    std::cout << "wpi::recursive_mutex sizeof: " << sizeof(wpi_recursive_mutex)
+              << " time: " << duration_cast<microseconds>(stop - start).count()
+              << " value: " << value << "\n";
+  });
+  thr2b.join();
+
+  std::thread thr3([]() {
+    int value = 0;
+
+    auto start = high_resolution_clock::now();
+    for (int i = 0; i < 1000000; i++) {
+      std::scoped_lock lock(spinlock);
+      ++value;
+    }
+    auto stop = high_resolution_clock::now();
+    std::cout << "spinlock sizeof: " << sizeof(spinlock)
+              << " time: " << duration_cast<microseconds>(stop - start).count()
+              << " value: " << value << "\n";
+  });
+  thr3.join();
+
+  std::thread thr4([]() {
+    int value = 0;
+
+    auto start = high_resolution_clock::now();
+    for (int i = 0; i < 1000000; i++) {
+      std::scoped_lock lock(recursive_spinlock1);
+      ++value;
+    }
+    auto stop = high_resolution_clock::now();
+    std::cout << "recursive_spinlock1 sizeof: " << sizeof(recursive_spinlock1)
+              << " time: " << duration_cast<microseconds>(stop - start).count()
+              << " value: " << value << "\n";
+  });
+  thr4.join();
+
+  std::thread thr4b([]() {
+    int value = 0;
+
+    auto start = high_resolution_clock::now();
+    for (int i = 0; i < 1000000; i++) {
+      std::scoped_lock lock(recursive_spinlock2);
+      ++value;
+    }
+    auto stop = high_resolution_clock::now();
+    std::cout << "recursive_spinlock2 sizeof: " << sizeof(recursive_spinlock2)
+              << " time: " << duration_cast<microseconds>(stop - start).count()
+              << " value: " << value << "\n";
+  });
+  thr4b.join();
+
+  std::thread thr4c([]() {
+    int value = 0;
+
+    auto start = high_resolution_clock::now();
+    for (int i = 0; i < 1000000; i++) {
+      std::scoped_lock lock(recursive_spinlock);
+      ++value;
+    }
+    auto stop = high_resolution_clock::now();
+    std::cout << "recursive_spinlock sizeof: " << sizeof(recursive_spinlock)
+              << " time: " << duration_cast<microseconds>(stop - start).count()
+              << " value: " << value << "\n";
+  });
+  thr4c.join();
+}
diff --git a/wpiutil/src/test/native/cpp/uv/UvAsyncFunctionTest.cpp b/wpiutil/src/test/native/cpp/uv/UvAsyncFunctionTest.cpp
new file mode 100644
index 0000000..132adea
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/uv/UvAsyncFunctionTest.cpp
@@ -0,0 +1,241 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include "wpi/uv/AsyncFunction.h"  // NOLINT(build/include_order)
+
+#include "gtest/gtest.h"  // NOLINT(build/include_order)
+
+#include <thread>
+
+#include "wpi/uv/Loop.h"
+#include "wpi/uv/Prepare.h"
+
+namespace wpi {
+namespace uv {
+
+TEST(UvAsyncFunction, Test) {
+  int prepare_cb_called = 0;
+  int async_cb_called[2] = {0, 0};
+  int close_cb_called = 0;
+
+  std::thread theThread;
+
+  auto loop = Loop::Create();
+  auto async = AsyncFunction<int(int)>::Create(loop);
+  auto prepare = Prepare::Create(loop);
+
+  loop->error.connect([](Error) { FAIL(); });
+
+  prepare->error.connect([](Error) { FAIL(); });
+  prepare->prepare.connect([&] {
+    if (prepare_cb_called++) return;
+    theThread = std::thread([&] {
+      auto call0 = async->Call(0);
+      auto call1 = async->Call(1);
+      ASSERT_EQ(call0.get(), 1);
+      ASSERT_EQ(call1.get(), 2);
+    });
+  });
+  prepare->Start();
+
+  async->error.connect([](Error) { FAIL(); });
+  async->closed.connect([&] { close_cb_called++; });
+  async->wakeup = [&](promise<int> out, int v) {
+    ++async_cb_called[v];
+    if (v == 1) {
+      async->Close();
+      prepare->Close();
+    }
+    out.set_value(v + 1);
+  };
+
+  loop->Run();
+
+  ASSERT_EQ(async_cb_called[0], 1);
+  ASSERT_EQ(async_cb_called[1], 1);
+  ASSERT_EQ(close_cb_called, 1);
+
+  if (theThread.joinable()) theThread.join();
+}
+
+TEST(UvAsyncFunction, Ref) {
+  int prepare_cb_called = 0;
+  int val = 0;
+
+  std::thread theThread;
+
+  auto loop = Loop::Create();
+  auto async = AsyncFunction<int(int, int&)>::Create(loop);
+  auto prepare = Prepare::Create(loop);
+
+  prepare->prepare.connect([&] {
+    if (prepare_cb_called++) return;
+    theThread = std::thread([&] { ASSERT_EQ(async->Call(1, val).get(), 2); });
+  });
+  prepare->Start();
+
+  async->wakeup = [&](promise<int> out, int v, int& r) {
+    r = v;
+    async->Close();
+    prepare->Close();
+    out.set_value(v + 1);
+  };
+
+  loop->Run();
+
+  ASSERT_EQ(val, 1);
+
+  if (theThread.joinable()) theThread.join();
+}
+
+TEST(UvAsyncFunction, Movable) {
+  int prepare_cb_called = 0;
+
+  std::thread theThread;
+
+  auto loop = Loop::Create();
+  auto async =
+      AsyncFunction<std::unique_ptr<int>(std::unique_ptr<int>)>::Create(loop);
+  auto prepare = Prepare::Create(loop);
+
+  prepare->prepare.connect([&] {
+    if (prepare_cb_called++) return;
+    theThread = std::thread([&] {
+      auto val = std::make_unique<int>(1);
+      auto val2 = async->Call(std::move(val)).get();
+      ASSERT_NE(val2, nullptr);
+      ASSERT_EQ(*val2, 1);
+    });
+  });
+  prepare->Start();
+
+  async->wakeup = [&](promise<std::unique_ptr<int>> out,
+                      std::unique_ptr<int> v) {
+    async->Close();
+    prepare->Close();
+    out.set_value(std::move(v));
+  };
+
+  loop->Run();
+
+  if (theThread.joinable()) theThread.join();
+}
+
+TEST(UvAsyncFunction, CallIgnoreResult) {
+  int prepare_cb_called = 0;
+
+  std::thread theThread;
+
+  auto loop = Loop::Create();
+  auto async =
+      AsyncFunction<std::unique_ptr<int>(std::unique_ptr<int>)>::Create(loop);
+  auto prepare = Prepare::Create(loop);
+
+  prepare->prepare.connect([&] {
+    if (prepare_cb_called++) return;
+    theThread = std::thread([&] { async->Call(std::make_unique<int>(1)); });
+  });
+  prepare->Start();
+
+  async->wakeup = [&](promise<std::unique_ptr<int>> out,
+                      std::unique_ptr<int> v) {
+    async->Close();
+    prepare->Close();
+    out.set_value(std::move(v));
+  };
+
+  loop->Run();
+
+  if (theThread.joinable()) theThread.join();
+}
+
+TEST(UvAsyncFunction, VoidCall) {
+  int prepare_cb_called = 0;
+
+  std::thread theThread;
+
+  auto loop = Loop::Create();
+  auto async = AsyncFunction<void()>::Create(loop);
+  auto prepare = Prepare::Create(loop);
+
+  prepare->prepare.connect([&] {
+    if (prepare_cb_called++) return;
+    theThread = std::thread([&] { async->Call(); });
+  });
+  prepare->Start();
+
+  async->wakeup = [&](promise<void> out) {
+    async->Close();
+    prepare->Close();
+    out.set_value();
+  };
+
+  loop->Run();
+
+  if (theThread.joinable()) theThread.join();
+}
+
+TEST(UvAsyncFunction, WaitFor) {
+  int prepare_cb_called = 0;
+
+  std::thread theThread;
+
+  auto loop = Loop::Create();
+  auto async = AsyncFunction<int()>::Create(loop);
+  auto prepare = Prepare::Create(loop);
+
+  prepare->prepare.connect([&] {
+    if (prepare_cb_called++) return;
+    theThread = std::thread([&] {
+      ASSERT_FALSE(async->Call().wait_for(std::chrono::milliseconds(10)));
+    });
+  });
+  prepare->Start();
+
+  async->wakeup = [&](promise<int> out) {
+    async->Close();
+    prepare->Close();
+    std::this_thread::sleep_for(std::chrono::milliseconds(100));
+    out.set_value(1);
+  };
+
+  loop->Run();
+
+  if (theThread.joinable()) theThread.join();
+}
+
+TEST(UvAsyncFunction, VoidWaitFor) {
+  int prepare_cb_called = 0;
+
+  std::thread theThread;
+
+  auto loop = Loop::Create();
+  auto async = AsyncFunction<void()>::Create(loop);
+  auto prepare = Prepare::Create(loop);
+
+  prepare->prepare.connect([&] {
+    if (prepare_cb_called++) return;
+    theThread = std::thread([&] {
+      ASSERT_FALSE(async->Call().wait_for(std::chrono::milliseconds(10)));
+    });
+  });
+  prepare->Start();
+
+  async->wakeup = [&](promise<void> out) {
+    async->Close();
+    prepare->Close();
+    std::this_thread::sleep_for(std::chrono::milliseconds(100));
+    out.set_value();
+  };
+
+  loop->Run();
+
+  if (theThread.joinable()) theThread.join();
+}
+
+}  // namespace uv
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/uv/UvAsyncTest.cpp b/wpiutil/src/test/native/cpp/uv/UvAsyncTest.cpp
new file mode 100644
index 0000000..e18f972
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/uv/UvAsyncTest.cpp
@@ -0,0 +1,178 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "wpi/uv/Async.h"  // NOLINT(build/include_order)
+
+#include "gtest/gtest.h"  // NOLINT(build/include_order)
+
+#include <atomic>
+#include <thread>
+
+#include "wpi/mutex.h"
+#include "wpi/uv/Loop.h"
+#include "wpi/uv/Prepare.h"
+
+namespace wpi {
+namespace uv {
+
+TEST(UvAsync, Test) {
+  std::atomic_int async_cb_called{0};
+  int prepare_cb_called = 0;
+  int close_cb_called = 0;
+
+  wpi::mutex mutex;
+  mutex.lock();
+
+  std::thread theThread;
+
+  auto loop = Loop::Create();
+  auto async = Async<>::Create(loop);
+  auto prepare = Prepare::Create(loop);
+
+  loop->error.connect([](Error) { FAIL(); });
+
+  prepare->error.connect([](Error) { FAIL(); });
+  prepare->closed.connect([&] { close_cb_called++; });
+  prepare->prepare.connect([&] {
+    if (prepare_cb_called++) return;
+    theThread = std::thread([&] {
+      for (;;) {
+        mutex.lock();
+        int n = async_cb_called;
+        mutex.unlock();
+
+        if (n == 3) {
+          break;
+        }
+
+        async->Send();
+
+        std::this_thread::yield();
+      }
+    });
+    mutex.unlock();
+  });
+  prepare->Start();
+
+  async->error.connect([](Error) { FAIL(); });
+  async->closed.connect([&] { close_cb_called++; });
+  async->wakeup.connect([&] {
+    mutex.lock();
+    int n = ++async_cb_called;
+    mutex.unlock();
+
+    if (n == 3) {
+      async->Close();
+      prepare->Close();
+    }
+  });
+
+  loop->Run();
+
+  ASSERT_GT(prepare_cb_called, 0);
+  ASSERT_EQ(async_cb_called, 3);
+  ASSERT_EQ(close_cb_called, 2);
+
+  if (theThread.joinable()) theThread.join();
+}
+
+TEST(UvAsync, Data) {
+  int prepare_cb_called = 0;
+  int async_cb_called[2] = {0, 0};
+  int close_cb_called = 0;
+
+  std::thread theThread;
+
+  auto loop = Loop::Create();
+  auto async = Async<int, std::function<void(int)>>::Create(loop);
+  auto prepare = Prepare::Create(loop);
+
+  loop->error.connect([](Error) { FAIL(); });
+
+  prepare->error.connect([](Error) { FAIL(); });
+  prepare->prepare.connect([&] {
+    if (prepare_cb_called++) return;
+    theThread = std::thread([&] {
+      async->Send(0, [&](int v) {
+        ASSERT_EQ(v, 0);
+        ++async_cb_called[0];
+      });
+      async->Send(1, [&](int v) {
+        ASSERT_EQ(v, 1);
+        ++async_cb_called[1];
+        async->Close();
+        prepare->Close();
+      });
+    });
+  });
+  prepare->Start();
+
+  async->error.connect([](Error) { FAIL(); });
+  async->closed.connect([&] { close_cb_called++; });
+  async->wakeup.connect([&](int v, std::function<void(int)> f) { f(v); });
+
+  loop->Run();
+
+  ASSERT_EQ(async_cb_called[0], 1);
+  ASSERT_EQ(async_cb_called[1], 1);
+  ASSERT_EQ(close_cb_called, 1);
+
+  if (theThread.joinable()) theThread.join();
+}
+
+TEST(UvAsync, DataRef) {
+  int prepare_cb_called = 0;
+  int val = 0;
+
+  std::thread theThread;
+
+  auto loop = Loop::Create();
+  auto async = Async<int, int&>::Create(loop);
+  auto prepare = Prepare::Create(loop);
+
+  prepare->prepare.connect([&] {
+    if (prepare_cb_called++) return;
+    theThread = std::thread([&] { async->Send(1, val); });
+  });
+  prepare->Start();
+
+  async->wakeup.connect([&](int v, int& r) {
+    r = v;
+    async->Close();
+    prepare->Close();
+  });
+
+  loop->Run();
+
+  ASSERT_EQ(val, 1);
+
+  if (theThread.joinable()) theThread.join();
+}
+
+}  // namespace uv
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/uv/UvBufferTest.cpp b/wpiutil/src/test/native/cpp/uv/UvBufferTest.cpp
new file mode 100644
index 0000000..e837ca9
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/uv/UvBufferTest.cpp
@@ -0,0 +1,50 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include "wpi/uv/Buffer.h"  // NOLINT(build/include_order)
+
+#include "gtest/gtest.h"  // NOLINT(build/include_order)
+
+namespace wpi {
+namespace uv {
+
+TEST(UvSimpleBufferPool, ConstructDefault) {
+  SimpleBufferPool<> pool;
+  auto buf1 = pool.Allocate();
+  ASSERT_EQ(buf1.len, 4096u);
+}
+
+TEST(UvSimpleBufferPool, ConstructSize) {
+  SimpleBufferPool<4> pool{8192};
+  auto buf1 = pool.Allocate();
+  ASSERT_EQ(buf1.len, 8192u);
+}
+
+TEST(UvSimpleBufferPool, ReleaseReuse) {
+  SimpleBufferPool<4> pool;
+  auto buf1 = pool.Allocate();
+  auto buf1copy = buf1;
+  auto origSize = buf1.len;
+  buf1.len = 8;
+  pool.Release(buf1);
+  ASSERT_EQ(buf1.base, nullptr);
+  auto buf2 = pool.Allocate();
+  ASSERT_EQ(buf1copy.base, buf2.base);
+  ASSERT_EQ(buf2.len, origSize);
+}
+
+TEST(UvSimpleBufferPool, ClearRemaining) {
+  SimpleBufferPool<4> pool;
+  auto buf1 = pool.Allocate();
+  pool.Release(buf1);
+  ASSERT_EQ(pool.Remaining(), 1u);
+  pool.Clear();
+  ASSERT_EQ(pool.Remaining(), 0u);
+}
+
+}  // namespace uv
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/uv/UvGetAddrInfoTest.cpp b/wpiutil/src/test/native/cpp/uv/UvGetAddrInfoTest.cpp
new file mode 100644
index 0000000..11ac426
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/uv/UvGetAddrInfoTest.cpp
@@ -0,0 +1,110 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "wpi/uv/GetAddrInfo.h"  // NOLINT(build/include_order)
+
+#include "gtest/gtest.h"  // NOLINT(build/include_order)
+
+#include "wpi/uv/Loop.h"
+
+#define CONCURRENT_COUNT 10
+
+namespace wpi {
+namespace uv {
+
+TEST(UvGetAddrInfo, BothNull) {
+  int fail_cb_called = 0;
+
+  auto loop = Loop::Create();
+  loop->error.connect([&](Error err) {
+    ASSERT_EQ(err.code(), UV_EINVAL);
+    fail_cb_called++;
+  });
+
+  GetAddrInfo(loop, [](const addrinfo&) { FAIL(); }, Twine::createNull());
+  loop->Run();
+  ASSERT_EQ(fail_cb_called, 1);
+}
+
+TEST(UvGetAddrInfo, FailedLookup) {
+  int fail_cb_called = 0;
+
+  auto loop = Loop::Create();
+  loop->error.connect([&](Error err) {
+    ASSERT_EQ(fail_cb_called, 0);
+    ASSERT_LT(err.code(), 0);
+    fail_cb_called++;
+  });
+
+  // Use a FQDN by ending in a period
+  GetAddrInfo(loop, [](const addrinfo&) { FAIL(); }, "xyzzy.xyzzy.xyzzy.");
+  loop->Run();
+  ASSERT_EQ(fail_cb_called, 1);
+}
+
+TEST(UvGetAddrInfo, Basic) {
+  int getaddrinfo_cbs = 0;
+
+  auto loop = Loop::Create();
+  loop->error.connect([](Error) { FAIL(); });
+
+  GetAddrInfo(loop, [&](const addrinfo&) { getaddrinfo_cbs++; }, "localhost");
+
+  loop->Run();
+
+  ASSERT_EQ(getaddrinfo_cbs, 1);
+}
+
+#ifndef _WIN32
+TEST(UvGetAddrInfo, Concurrent) {
+  int getaddrinfo_cbs = 0;
+  int callback_counts[CONCURRENT_COUNT];
+
+  auto loop = Loop::Create();
+  loop->error.connect([](Error) { FAIL(); });
+
+  for (int i = 0; i < CONCURRENT_COUNT; i++) {
+    callback_counts[i] = 0;
+    GetAddrInfo(loop,
+                [i, &callback_counts, &getaddrinfo_cbs](const addrinfo&) {
+                  callback_counts[i]++;
+                  getaddrinfo_cbs++;
+                },
+                "localhost");
+  }
+
+  loop->Run();
+
+  for (int i = 0; i < CONCURRENT_COUNT; i++) {
+    ASSERT_EQ(callback_counts[i], 1);
+  }
+}
+#endif
+
+}  // namespace uv
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/uv/UvGetNameInfoTest.cpp b/wpiutil/src/test/native/cpp/uv/UvGetNameInfoTest.cpp
new file mode 100644
index 0000000..16de329
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/uv/UvGetNameInfoTest.cpp
@@ -0,0 +1,77 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "wpi/uv/GetNameInfo.h"  // NOLINT(build/include_order)
+
+#include "gtest/gtest.h"  // NOLINT(build/include_order)
+
+#include "wpi/uv/Loop.h"
+
+namespace wpi {
+namespace uv {
+
+TEST(UvGetNameInfo, BasicIp4) {
+  int getnameinfo_cbs = 0;
+
+  auto loop = Loop::Create();
+  loop->error.connect([](Error) { FAIL(); });
+
+  GetNameInfo4(loop,
+               [&](const char* hostname, const char* service) {
+                 ASSERT_NE(hostname, nullptr);
+                 ASSERT_NE(service, nullptr);
+                 getnameinfo_cbs++;
+               },
+               "127.0.0.1", 80);
+
+  loop->Run();
+
+  ASSERT_EQ(getnameinfo_cbs, 1);
+}
+
+TEST(UvGetNameInfo, BasicIp6) {
+  int getnameinfo_cbs = 0;
+
+  auto loop = Loop::Create();
+  loop->error.connect([](Error) { FAIL(); });
+
+  GetNameInfo6(loop,
+               [&](const char* hostname, const char* service) {
+                 ASSERT_NE(hostname, nullptr);
+                 ASSERT_NE(service, nullptr);
+                 getnameinfo_cbs++;
+               },
+               "::1", 80);
+
+  loop->Run();
+
+  ASSERT_EQ(getnameinfo_cbs, 1);
+}
+
+}  // namespace uv
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/uv/UvLoopWalkTest.cpp b/wpiutil/src/test/native/cpp/uv/UvLoopWalkTest.cpp
new file mode 100644
index 0000000..5d2e120
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/uv/UvLoopWalkTest.cpp
@@ -0,0 +1,70 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "wpi/uv/Loop.h"  // NOLINT(build/include_order)
+
+#include "gtest/gtest.h"  // NOLINT(build/include_order)
+
+#include "wpi/uv/Timer.h"
+
+namespace wpi {
+namespace uv {
+
+TEST(UvLoop, Walk) {
+  int seen_timer_handle = 0;
+
+  auto loop = Loop::Create();
+  auto timer = Timer::Create(loop);
+
+  loop->error.connect([](Error) { FAIL(); });
+
+  timer->error.connect([](Error) { FAIL(); });
+
+  timer->timeout.connect([&, theTimer = timer.get()] {
+    theTimer->GetLoopRef().Walk([&](Handle& it) {
+      if (&it == timer.get()) seen_timer_handle++;
+    });
+    theTimer->Close();
+  });
+  timer->Start(Timer::Time{1});
+
+  // Start event loop, expect to see the timer handle
+  ASSERT_EQ(seen_timer_handle, 0);
+  loop->Run();
+  ASSERT_EQ(seen_timer_handle, 1);
+
+  // Loop is finished, should not see our timer handle
+  seen_timer_handle = 0;
+  loop->Walk([&](Handle& it) {
+    if (&it == timer.get()) seen_timer_handle++;
+  });
+  ASSERT_EQ(seen_timer_handle, 0);
+}
+
+}  // namespace uv
+}  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/uv/UvTimerTest.cpp b/wpiutil/src/test/native/cpp/uv/UvTimerTest.cpp
new file mode 100644
index 0000000..6e1bd5c
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/uv/UvTimerTest.cpp
@@ -0,0 +1,74 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
+/* Open Source Software - may be modified and shared by FRC teams. The code   */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project.                                                               */
+/*----------------------------------------------------------------------------*/
+
+#include "wpi/uv/Timer.h"  // NOLINT(build/include_order)
+
+#include "gtest/gtest.h"
+
+namespace wpi {
+namespace uv {
+
+TEST(UvTimer, StartAndStop) {
+  auto loop = Loop::Create();
+  auto handleNoRepeat = Timer::Create(loop);
+  auto handleRepeat = Timer::Create(loop);
+
+  bool checkTimerNoRepeatEvent = false;
+  bool checkTimerRepeatEvent = false;
+
+  handleNoRepeat->error.connect([](Error) { FAIL(); });
+  handleRepeat->error.connect([](Error) { FAIL(); });
+
+  handleNoRepeat->timeout.connect(
+      [&checkTimerNoRepeatEvent, handle = handleNoRepeat.get()] {
+        ASSERT_FALSE(checkTimerNoRepeatEvent);
+        checkTimerNoRepeatEvent = true;
+        handle->Stop();
+        handle->Close();
+        ASSERT_TRUE(handle->IsClosing());
+      });
+
+  handleRepeat->timeout.connect(
+      [&checkTimerRepeatEvent, handle = handleRepeat.get()] {
+        if (checkTimerRepeatEvent) {
+          handle->Stop();
+          handle->Close();
+          ASSERT_TRUE(handle->IsClosing());
+        } else {
+          checkTimerRepeatEvent = true;
+          ASSERT_FALSE(handle->IsClosing());
+        }
+      });
+
+  handleNoRepeat->Start(Timer::Time{0}, Timer::Time{0});
+  handleRepeat->Start(Timer::Time{0}, Timer::Time{1});
+
+  ASSERT_TRUE(handleNoRepeat->IsActive());
+  ASSERT_FALSE(handleNoRepeat->IsClosing());
+
+  ASSERT_TRUE(handleRepeat->IsActive());
+  ASSERT_FALSE(handleRepeat->IsClosing());
+
+  loop->Run();
+
+  ASSERT_TRUE(checkTimerNoRepeatEvent);
+  ASSERT_TRUE(checkTimerRepeatEvent);
+}
+
+TEST(UvTimer, Repeat) {
+  auto loop = Loop::Create();
+  auto handle = Timer::Create(loop);
+
+  handle->SetRepeat(Timer::Time{42});
+  ASSERT_EQ(handle->GetRepeat(), Timer::Time{42});
+  handle->Close();
+
+  loop->Run();  // forces close callback to run
+}
+
+}  // namespace uv
+}  // namespace wpi