Squashed 'third_party/allwpilib_2019/' content from commit bd05dfa1c

Change-Id: I2b1c2250cdb9b055133780c33593292098c375b7
git-subtree-dir: third_party/allwpilib_2019
git-subtree-split: bd05dfa1c7cca74c4fac451e7b9d6a37e7b53447
diff --git a/wpiutil/src/test/native/cpp/Base64Test.cpp b/wpiutil/src/test/native/cpp/Base64Test.cpp
new file mode 100644
index 0000000..99aecb9
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/Base64Test.cpp
@@ -0,0 +1,105 @@
+/*----------------------------------------------------------------------------*/
+/* 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"
+#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_CASE_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_CASE_P(Base64Standard, Base64Test,
+                        ::testing::ValuesIn(standard), );
+
+}  // namespace wpi
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/WebSocketClientTest.cpp b/wpiutil/src/test/native/cpp/WebSocketClientTest.cpp
new file mode 100644
index 0000000..692e2a7
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/WebSocketClientTest.cpp
@@ -0,0 +1,299 @@
+/*----------------------------------------------------------------------------*/
+/* 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/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,
+        ArrayRef<StringRef>{"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_CASE_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..f51e8fc
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/WebSocketIntegrationTest.cpp
@@ -0,0 +1,148 @@
+/*----------------------------------------------------------------------------*/
+/* 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/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..7d723e3
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/WebSocketServerTest.cpp
@@ -0,0 +1,736 @@
+/*----------------------------------------------------------------------------*/
+/* 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/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
+  auto message = BuildMessage(0x08, true, true, {0x03u, 0xe8u});
+  handleData = [&](StringRef) {
+    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  };
+
+  loop->Run();
+
+  auto expectData = BuildMessage(0x08, true, false, {0x03u, 0xe8u});
+  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
+  auto message = BuildMessage(0x08, true, true,
+                              {0x03u, 0xe8u, 'h', 'a', 'n', 'g', 'u', 'p'});
+  handleData = [&](StringRef) {
+    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  };
+
+  loop->Run();
+
+  auto expectData = BuildMessage(0x08, true, false,
+                                 {0x03u, 0xe8u, 'h', 'a', 'n', 'g', 'u', 'p'});
+  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;
+    });
+  };
+  auto message = BuildMessage(0x08, true, true, {0x03u, 0xe8u});
+  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, {0x03u, 0xe8u});
+  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");
+    });
+  };
+  auto message = BuildMessage(0x08, true, true,
+                              {0x03u, 0xe8u, 'h', 'a', 'n', 'g', 'u', 'p'});
+  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,
+                                 {0x03u, 0xe8u, 'h', 'a', 'n', 'g', 'u', 'p'});
+  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_CASE_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_CASE_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_CASE_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..84e98eb
--- /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_CASE_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_CASE_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_CASE_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_CASE_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_CASE_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_CASE_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_CASE_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_CASE_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<char>(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<char>(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_CASE_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_CASE_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_CASE_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_CASE_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_CASE_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_CASE_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_CASE_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_CASE_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..71c4678
--- /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_CASE_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_CASE(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_CASE(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_CASE(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..e4981f2
--- /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_CASE(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_CASE(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_CASE(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_CASE(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_CASE(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_CASE(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..12ac280
--- /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_CASE_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..0c03ee0
--- /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_CASE_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_CASE_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_CASE_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_CASE_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_CASE_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_CASE_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_CASE_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_CASE_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_CASE_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/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..c5c86b6
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/priority_mutex_test.cpp
@@ -0,0 +1,271 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2016-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/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<priority_mutex> lock(m_mutex);
+    while (!m_set) {
+      m_condition.wait(lock);
+    }
+  }
+  // Sets the condition to true, and wakes all waiting threads.
+  void Notify() {
+    std::lock_guard<priority_mutex> 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..2280a44
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/spinlock_bench.cpp
@@ -0,0 +1,166 @@
+/*----------------------------------------------------------------------------*/
+/* 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/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::lock_guard<std::mutex> 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::lock_guard<std::mutex> 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::lock_guard<std::recursive_mutex> 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::lock_guard<wpi::mutex> 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::lock_guard<wpi::recursive_mutex> 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::lock_guard<wpi::spinlock> 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::lock_guard<wpi::recursive_spinlock1> 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::lock_guard<wpi::recursive_spinlock2> 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::lock_guard<wpi::recursive_spinlock> 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/test_optional.cpp b/wpiutil/src/test/native/cpp/test_optional.cpp
new file mode 100644
index 0000000..e64f0c2
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/test_optional.cpp
@@ -0,0 +1,1523 @@
+// Copyright (C) 2011 - 2017 Andrzej Krzemienski.
+//
+// Use, modification, and distribution is subject to the Boost Software
+// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+//
+// The idea and interface is based on Boost.Optional library
+// authored by Fernando Luis Cacciola Carballal
+
+# include "wpi/optional.h"
+
+#include "gtest/gtest.h"
+
+# include <vector>
+# include <iostream>
+# include <functional>
+# include <complex>
+
+
+
+enum  State 
+{
+    sDefaultConstructed,
+    sValueCopyConstructed,
+    sValueMoveConstructed,
+    sCopyConstructed,
+    sMoveConstructed,
+    sMoveAssigned,
+    sCopyAssigned,
+    sValueCopyAssigned,
+    sValueMoveAssigned,
+    sMovedFrom,
+    sValueConstructed
+};
+
+struct OracleVal
+{
+    State s;
+    int i;
+    OracleVal(int i = 0) : s(sValueConstructed), i(i) {}
+};
+
+struct Oracle
+{
+    State s;
+    OracleVal val;
+
+    Oracle() : s(sDefaultConstructed) {}
+    Oracle(const OracleVal& v) : s(sValueCopyConstructed), val(v) {}
+    Oracle(OracleVal&& v) : s(sValueMoveConstructed), val(std::move(v)) {v.s = sMovedFrom;}
+    Oracle(const Oracle& o) : s(sCopyConstructed), val(o.val) {}
+    Oracle(Oracle&& o) : s(sMoveConstructed), val(std::move(o.val)) {o.s = sMovedFrom;}
+
+    Oracle& operator=(const OracleVal& v) { s = sValueCopyConstructed; val = v; return *this; }
+    Oracle& operator=(OracleVal&& v) { s = sValueMoveConstructed; val = std::move(v); v.s = sMovedFrom; return *this; }
+    Oracle& operator=(const Oracle& o) { s = sCopyConstructed; val = o.val; return *this; }
+    Oracle& operator=(Oracle&& o) { s = sMoveConstructed; val = std::move(o.val); o.s = sMovedFrom; return *this; }
+};
+
+struct Guard
+{
+    std::string val;
+    Guard() : val{} {}
+    explicit Guard(std::string s, int = 0) : val(s) {}
+    Guard(const Guard&) = delete;
+    Guard(Guard&&) = delete;
+    void operator=(const Guard&) = delete;
+    void operator=(Guard&&) = delete;
+};
+
+struct ExplicitStr
+{
+    std::string s;
+    explicit ExplicitStr(const char* chp) : s(chp) {};
+};
+
+struct Date
+{
+    int i;
+    Date() = delete;
+    Date(int i) : i{i} {};
+    Date(Date&& d) : i(d.i) { d.i = 0; }
+    Date(const Date&) = delete;
+    Date& operator=(const Date&) = delete;
+    Date& operator=(Date&& d) { i = d.i; d.i = 0; return *this;};
+};
+
+bool operator==( Oracle const& a, Oracle const& b ) { return a.val.i == b.val.i; }
+bool operator!=( Oracle const& a, Oracle const& b ) { return a.val.i != b.val.i; }
+
+
+namespace tr2 = wpi;
+
+
+TEST(Optional, disengaged_ctor)
+{
+    tr2::optional<int> o1;
+    assert (!o1);
+
+    tr2::optional<int> o2 = tr2::nullopt;
+    assert (!o2);
+
+    tr2::optional<int> o3 = o2;
+    assert (!o3);
+
+    assert (o1 == tr2::nullopt);
+    assert (o1 == tr2::optional<int>{});
+    assert (!o1);
+    assert (bool(o1) == false);
+
+    assert (o2 == tr2::nullopt);
+    assert (o2 == tr2::optional<int>{});
+    assert (!o2);
+    assert (bool(o2) == false);
+
+    assert (o3 == tr2::nullopt);
+    assert (o3 == tr2::optional<int>{});
+    assert (!o3);
+    assert (bool(o3) == false);
+
+    assert (o1 == o2);
+    assert (o2 == o1);
+    assert (o1 == o3);
+    assert (o3 == o1);
+    assert (o2 == o3);
+    assert (o3 == o2);
+}
+
+
+TEST(Optional, value_ctor)
+{
+  OracleVal v;
+  tr2::optional<Oracle> oo1(v);
+  assert (oo1 != tr2::nullopt);
+  assert (oo1 != tr2::optional<Oracle>{});
+  assert (oo1 == tr2::optional<Oracle>{v});
+  assert (!!oo1);
+  assert (bool(oo1));
+  // NA: assert (oo1->s == sValueCopyConstructed);
+  assert (oo1->s == sMoveConstructed);
+  assert (v.s == sValueConstructed);
+  
+  tr2::optional<Oracle> oo2(std::move(v));
+  assert (oo2 != tr2::nullopt);
+  assert (oo2 != tr2::optional<Oracle>{});
+  assert (oo2 == oo1);
+  assert (!!oo2);
+  assert (bool(oo2));
+  // NA: assert (oo2->s == sValueMoveConstructed);
+  assert (oo2->s == sMoveConstructed);
+  assert (v.s == sMovedFrom);
+
+  {
+      OracleVal v;
+      tr2::optional<Oracle> oo1{tr2::in_place, v};
+      assert (oo1 != tr2::nullopt);
+      assert (oo1 != tr2::optional<Oracle>{});
+      assert (oo1 == tr2::optional<Oracle>{v});
+      assert (!!oo1);
+      assert (bool(oo1));
+      assert (oo1->s == sValueCopyConstructed);
+      assert (v.s == sValueConstructed);
+
+      tr2::optional<Oracle> oo2{tr2::in_place, std::move(v)};
+      assert (oo2 != tr2::nullopt);
+      assert (oo2 != tr2::optional<Oracle>{});
+      assert (oo2 == oo1);
+      assert (!!oo2);
+      assert (bool(oo2));
+      assert (oo2->s == sValueMoveConstructed);
+      assert (v.s == sMovedFrom);
+  }
+}
+
+
+TEST(Optional, assignment)
+{
+    tr2::optional<int> oi;
+    oi = tr2::optional<int>{1};
+    assert (*oi == 1);
+
+    oi = tr2::nullopt;
+    assert (!oi);
+
+    oi = 2;
+    assert (*oi == 2);
+
+    oi = {};
+    assert (!oi);
+}
+
+
+template <class T>
+struct MoveAware
+{
+  T val;
+  bool moved;
+  MoveAware(T val) : val(val), moved(false) {}
+  MoveAware(MoveAware const&) = delete;
+  MoveAware(MoveAware&& rhs) : val(rhs.val), moved(rhs.moved) {
+    rhs.moved = true;
+  }
+  MoveAware& operator=(MoveAware const&) = delete;
+  MoveAware& operator=(MoveAware&& rhs) {
+    val = (rhs.val);
+    moved = (rhs.moved);
+    rhs.moved = true;
+    return *this;
+  }
+};
+
+TEST(Optional, moved_from_state)
+{
+  // first, test mock:
+  MoveAware<int> i{1}, j{2};
+  assert (i.val == 1);
+  assert (!i.moved);
+  assert (j.val == 2);
+  assert (!j.moved);
+  
+  MoveAware<int> k = std::move(i);
+  assert (k.val == 1);
+  assert (!k.moved);
+  assert (i.val == 1);
+  assert (i.moved);
+  
+  k = std::move(j);
+  assert (k.val == 2);
+  assert (!k.moved);
+  assert (j.val == 2);
+  assert (j.moved);
+  
+  // now, test optional
+  tr2::optional<MoveAware<int>> oi{1}, oj{2};
+  assert (oi);
+  assert (!oi->moved);
+  assert (oj);
+  assert (!oj->moved);
+  
+  tr2::optional<MoveAware<int>> ok = std::move(oi);
+  assert (ok);
+  assert (!ok->moved);
+  assert (oi);
+  assert (oi->moved);
+  
+  ok = std::move(oj);
+  assert (ok);
+  assert (!ok->moved);
+  assert (oj);
+  assert (oj->moved);
+}
+
+
+TEST(Optional, copy_move_ctor_optional_int)
+{
+  tr2::optional<int> oi;
+  tr2::optional<int> oj = oi;
+  
+  assert (!oj);
+  assert (oj == oi);
+  assert (oj == tr2::nullopt);
+  assert (!bool(oj));
+  
+  oi = 1;
+  tr2::optional<int> ok = oi;
+  assert (!!ok);
+  assert (bool(ok));
+  assert (ok == oi);
+  assert (ok != oj);
+  assert (*ok == 1);
+  
+  tr2::optional<int> ol = std::move(oi);
+  assert (!!ol);
+  assert (bool(ol));
+  assert (ol == oi);
+  assert (ol != oj);
+  assert (*ol == 1);
+}
+
+
+TEST(Optional, optional_optional)
+{
+  tr2::optional<tr2::optional<int>> oi1 = tr2::nullopt;
+  assert (oi1 == tr2::nullopt);
+  assert (!oi1);
+  
+  {
+  tr2::optional<tr2::optional<int>> oi2 {tr2::in_place};
+  assert (oi2 != tr2::nullopt);
+  assert (bool(oi2));
+  assert (*oi2 == tr2::nullopt);
+  //assert (!(*oi2));
+  //std::cout << typeid(**oi2).name() << std::endl;
+  }
+  
+  {
+  tr2::optional<tr2::optional<int>> oi2 {tr2::in_place, tr2::nullopt};
+  assert (oi2 != tr2::nullopt);
+  assert (bool(oi2));
+  assert (*oi2 == tr2::nullopt);
+  assert (!*oi2);
+  }
+  
+  {
+  tr2::optional<tr2::optional<int>> oi2 {tr2::optional<int>{}};
+  assert (oi2 != tr2::nullopt);
+  assert (bool(oi2));
+  assert (*oi2 == tr2::nullopt);
+  assert (!*oi2);
+  }
+  
+  tr2::optional<int> oi;
+  auto ooi = tr2::make_optional(oi);
+  static_assert( std::is_same<tr2::optional<tr2::optional<int>>, decltype(ooi)>::value, "");
+
+}
+
+TEST(Optional, example_guard)
+{
+  using namespace tr2;
+  //FAILS: optional<Guard> ogx(Guard("res1")); 
+  //FAILS: optional<Guard> ogx = "res1"; 
+  //FAILS: optional<Guard> ogx("res1"); 
+  optional<Guard> oga;                     // Guard is non-copyable (and non-moveable)     
+  optional<Guard> ogb(in_place, "res1");   // initialzes the contained value with "res1"  
+  assert (bool(ogb));
+  assert (ogb->val == "res1");
+            
+  optional<Guard> ogc(in_place);           // default-constructs the contained value
+  assert (bool(ogc));
+  assert (ogc->val == "");
+
+  oga.emplace("res1");                     // initialzes the contained value with "res1"  
+  assert (bool(oga));
+  assert (oga->val == "res1");
+  
+  oga.emplace();                           // destroys the contained value and 
+                                           // default-constructs the new one
+  assert (bool(oga));
+  assert (oga->val == "");
+  
+  oga = nullopt;                        // OK: make disengaged the optional Guard
+  assert (!(oga));
+  //FAILS: ogb = {};                          // ERROR: Guard is not Moveable
+}
+
+
+void process(){}
+void process(int ){}
+void processNil(){}
+
+
+TEST(Optional, example1)
+{
+  using namespace tr2;
+  optional<int> oi;                 // create disengaged object
+  optional<int> oj = nullopt;          // alternative syntax
+  oi = oj;                          // assign disengaged object
+  optional<int> ok = oj;            // ok is disengaged
+
+  if (oi)  assert(false);           // 'if oi is engaged...'
+  if (!oi) assert(true);            // 'if oi is disengaged...'
+
+  if (oi != nullopt) assert(false);    // 'if oi is engaged...'
+  if (oi == nullopt) assert(true);     // 'if oi is disengaged...'
+
+  assert(oi == ok);                 // two disengaged optionals compare equal
+  
+  ///////////////////////////////////////////////////////////////////////////
+  optional<int> ol{1};              // ol is engaged; its contained value is 1
+  ok = 2;                           // ok becomes engaged; its contained value is 2
+  oj = ol;                          // oj becomes engaged; its contained value is 1
+
+  assert(oi != ol);                 // disengaged != engaged
+  assert(ok != ol);                 // different contained values
+  assert(oj == ol);                 // same contained value
+  assert(oi < ol);                  // disengaged < engaged
+  assert(ol < ok);                  // less by contained value
+  
+  /////////////////////////////////////////////////////////////////////////////
+  optional<int> om{1};              // om is engaged; its contained value is 1
+  optional<int> on = om;            // on is engaged; its contained value is 1
+  om = 2;                           // om is engaged; its contained value is 2
+  assert (on != om);                // on still contains 3. They are not pointers
+
+  /////////////////////////////////////////////////////////////////////////////
+  int i = *ol;                      // i obtains the value contained in ol
+  assert (i == 1);
+  *ol = 9;                          // the object contained in ol becomes 9
+  assert(*ol == 9);
+  assert(ol == make_optional(9));  
+  
+  ///////////////////////////////////
+  int p = 1;
+  optional<int> op = p;
+  assert(*op == 1);
+  p = 2;                         
+  assert(*op == 1);                 // value contained in op is separated from p
+
+  ////////////////////////////////
+  if (ol)                      
+    process(*ol);                   // use contained value if present
+  else
+    process();                      // proceed without contained value
+    
+  if (!om)   
+    processNil();
+  else  
+    process(*om);   
+  
+  /////////////////////////////////////////
+  process(ol.value_or(0));     // use 0 if ol is disengaged
+  
+  ////////////////////////////////////////////
+  ok = nullopt;                         // if ok was engaged calls T's dtor
+  oj = {};                           // assigns a temporary disengaged optional
+}
+
+
+TEST(Optional, example_guard2)
+{
+  using wpi::optional;
+  const optional<int> c = 4; 
+  int i = *c;                        // i becomes 4
+  assert (i == 4);
+  // FAILS: *c = i;                            // ERROR: cannot assign to const int&
+}
+
+
+TEST(Optional, example_ref)
+{
+  using namespace wpi;
+  int i = 1;
+  int j = 2;
+  optional<int&> ora;                 // disengaged optional reference to int
+  optional<int&> orb = i;             // contained reference refers to object i
+
+  *orb = 3;                          // i becomes 3
+  // FAILS: ora = j;                           // ERROR: optional refs do not have assignment from T
+  // FAILS: ora = {j};                         // ERROR: optional refs do not have copy/move assignment
+  // FAILS: ora = orb;                         // ERROR: no copy/move assignment
+  ora.emplace(j);                    // OK: contained reference refers to object j
+  ora.emplace(i);                    // OK: contained reference now refers to object i
+
+  ora = nullopt;                        // OK: ora becomes disengaged
+}
+
+
+template <typename T>
+T getValue( tr2::optional<T> newVal = tr2::nullopt, tr2::optional<T&> storeHere = tr2::nullopt )
+{
+  T cached{};
+  
+  if (newVal) {
+    cached = *newVal;
+    
+    if (storeHere) {
+      *storeHere = *newVal; // LEGAL: assigning T to T
+    }      
+  }
+  return cached;      
+}
+
+TEST(Optional, example_optional_arg)
+{
+  int iii = 0;
+  iii = getValue<int>(iii, iii);
+  iii = getValue<int>(iii);
+  iii = getValue<int>();
+  
+  {
+    using namespace wpi;
+    optional<Guard> grd1{in_place, "res1", 1};   // guard 1 initialized
+    optional<Guard> grd2;
+
+    grd2.emplace("res2", 2);                     // guard 2 initialized
+    grd1 = nullopt;                                 // guard 1 released
+
+  }                                              // guard 2 released (in dtor)
+}
+
+
+std::tuple<Date, Date, Date> getStartMidEnd() { return std::tuple<Date, Date, Date>{Date{1}, Date{2}, Date{3}}; }
+void run(Date const&, Date const&, Date const&) {}
+
+TEST(Optional, example_date)
+{
+  using namespace wpi;
+  optional<Date> start, mid, end;           // Date doesn't have default ctor (no good default date)
+
+  std::tie(start, mid, end) = getStartMidEnd();
+  run(*start, *mid, *end); 
+}
+
+
+wpi::optional<char> readNextChar(){ return{}; }
+
+void run(wpi::optional<std::string>) {}
+void run(std::complex<double>) {}
+
+
+template <class T>
+void assign_norebind(tr2::optional<T&>& optref, T& obj)
+{
+  if (optref) *optref = obj;
+  else        optref.emplace(obj);
+}
+
+template <typename T> void unused(T&&) {}
+
+TEST(Optional, example_conceptual_model)
+{
+  using namespace wpi;
+  
+  optional<int> oi = 0;
+  optional<int> oj = 1;
+  optional<int> ok = nullopt;
+
+  oi = 1;
+  oj = nullopt;
+  ok = 0;
+
+  unused(oi == nullopt);
+  unused(oj == 0);
+  unused(ok == 1);
+}
+
+TEST(Optional, example_rationale)
+{
+  using namespace wpi;
+  if (optional<char> ch = readNextChar()) {
+    unused(ch);
+    // ...
+  }
+  
+  //////////////////////////////////
+  optional<int> opt1 = nullopt; 
+  optional<int> opt2 = {}; 
+
+  opt1 = nullopt;
+  opt2 = {};
+
+  if (opt1 == nullopt) {}
+  if (!opt2) {}
+  if (opt2 == optional<int>{}) {}
+  
+  
+  
+  ////////////////////////////////
+
+  run(nullopt);            // pick the second overload
+  // FAILS: run({});              // ambiguous
+
+  if (opt1 == nullopt) {} // fine
+  // FAILS: if (opt2 == {}) {}   // ilegal
+  
+  ////////////////////////////////
+  assert (optional<unsigned>{}  < optional<unsigned>{0});
+  assert (optional<unsigned>{0} < optional<unsigned>{1});
+  assert (!(optional<unsigned>{}  < optional<unsigned>{}) );
+  assert (!(optional<unsigned>{1} < optional<unsigned>{1}));
+
+  assert (optional<unsigned>{}  != optional<unsigned>{0});
+  assert (optional<unsigned>{0} != optional<unsigned>{1});
+  assert (optional<unsigned>{}  == optional<unsigned>{} );
+  assert (optional<unsigned>{0} == optional<unsigned>{0});
+  
+  /////////////////////////////////
+  optional<int> o;
+  o = make_optional(1);         // copy/move assignment
+  o = 1;           // assignment from T
+  o.emplace(1);    // emplacement 
+  
+  ////////////////////////////////////
+  int isas = 0, i = 9;
+  optional<int&> asas = i;
+  assign_norebind(asas, isas);
+  
+  /////////////////////////////////////
+  ////tr2::optional<std::vector<int>> ov2 = {2, 3};
+  ////assert (bool(ov2));
+  ////assert ((*ov2)[1] == 3);
+  ////
+  ////////////////////////////////
+  ////std::vector<int> v = {1, 2, 4, 8};
+  ////optional<std::vector<int>> ov = {1, 2, 4, 8};
+
+  ////assert (v == *ov);
+  ////
+  ////ov = {1, 2, 4, 8};
+
+  ////std::allocator<int> a;
+  ////optional<std::vector<int>> ou { in_place, {1, 2, 4, 8}, a };
+
+  ////assert (ou == ov);
+
+  //////////////////////////////
+  // inconvenient syntax:
+  {
+    
+      tr2::optional<std::vector<int>> ov2{tr2::in_place, {2, 3}};
+    
+      assert (bool(ov2));
+      assert ((*ov2)[1] == 3);
+  
+      ////////////////////////////
+
+      std::vector<int> v = {1, 2, 4, 8};
+      optional<std::vector<int>> ov{tr2::in_place, {1, 2, 4, 8}};
+
+      assert (v == *ov);
+  
+      ov.emplace({1, 2, 4, 8});
+/*
+      std::allocator<int> a;
+      optional<std::vector<int>> ou { in_place, {1, 2, 4, 8}, a };
+
+      assert (ou == ov);
+*/
+  }
+
+  /////////////////////////////////
+  {
+  typedef int T;
+  optional<optional<T>> ot {in_place};
+  optional<optional<T>> ou {in_place, nullopt};
+  optional<optional<T>> ov {optional<T>{}};
+
+  (void) ot;
+  (void) ou;
+  
+  optional<int> oi;
+  auto ooi = make_optional(oi);
+  static_assert( std::is_same<optional<optional<int>>, decltype(ooi)>::value, "");
+  }
+}
+
+
+bool fun(std::string , wpi::optional<int> oi = wpi::nullopt) 
+{
+  return bool(oi);
+}
+
+TEST(Optional, example_converting_ctor)
+{
+  using namespace wpi;
+  
+  assert (true == fun("dog", 2));
+  assert (false == fun("dog"));
+  assert (false == fun("dog", nullopt)); // just to be explicit
+}
+
+
+TEST(Optional, bad_comparison)
+{
+  tr2::optional<int> oi, oj;
+  int i;
+  bool b = (oi == oj);
+  b = (oi >= i);
+  b = (oi == i);
+  unused(b);
+}
+
+
+//// NOT APPLICABLE ANYMORE
+////TEST(Optional, perfect_ctor)
+////{
+////  //tr2::optional<std::string> ois = "OS";
+////  assert (*ois == "OS");
+////  
+////  // FAILS: tr2::optional<ExplicitStr> oes = "OS"; 
+////  tr2::optional<ExplicitStr> oes{"OS"};
+////  assert (oes->s == "OS");
+////}
+
+TEST(Optional, value_or)
+{
+  tr2::optional<int> oi = 1;
+  int i = oi.value_or(0);
+  assert (i == 1);
+  
+  oi = tr2::nullopt;
+  assert (oi.value_or(3) == 3);
+  
+  tr2::optional<std::string> os{"AAA"};
+  assert (os.value_or("BBB") == "AAA");
+  os = {};
+  assert (os.value_or("BBB") == "BBB");
+}
+
+TEST(Optional, reset)
+{
+  using namespace wpi;
+  optional<int> oi {1};
+  oi.reset();
+  assert (!oi);
+
+  int i = 1;
+  optional<const int&> oir {i};
+  oir.reset();
+  assert (!oir);
+}
+
+TEST(Optional, mixed_order)
+{
+  using namespace wpi;
+  
+  optional<int> oN {nullopt};
+  optional<int> o0 {0};
+  optional<int> o1 {1};
+  
+  assert ( (oN <   0));
+  assert ( (oN <   1));
+  assert (!(o0 <   0));
+  assert ( (o0 <   1));
+  assert (!(o1 <   0));
+  assert (!(o1 <   1));
+  
+  assert (!(oN >=  0));
+  assert (!(oN >=  1));
+  assert ( (o0 >=  0));
+  assert (!(o0 >=  1));
+  assert ( (o1 >=  0));
+  assert ( (o1 >=  1));
+  
+  assert (!(oN >   0));
+  assert (!(oN >   1));
+  assert (!(o0 >   0));
+  assert (!(o0 >   1));
+  assert ( (o1 >   0));
+  assert (!(o1 >   1));
+  
+  assert ( (oN <=  0));
+  assert ( (oN <=  1));
+  assert ( (o0 <=  0));
+  assert ( (o0 <=  1));
+  assert (!(o1 <=  0));
+  assert ( (o1 <=  1));
+  
+  assert ( (0 >  oN));
+  assert ( (1 >  oN));
+  assert (!(0 >  o0));
+  assert ( (1 >  o0));
+  assert (!(0 >  o1));
+  assert (!(1 >  o1));
+  
+  assert (!(0 <= oN));
+  assert (!(1 <= oN));
+  assert ( (0 <= o0));
+  assert (!(1 <= o0));
+  assert ( (0 <= o1));
+  assert ( (1 <= o1));
+  
+  assert (!(0 <  oN));
+  assert (!(1 <  oN));
+  assert (!(0 <  o0));
+  assert (!(1 <  o0));
+  assert ( (0 <  o1));
+  assert (!(1 <  o1));
+  
+  assert ( (0 >= oN));
+  assert ( (1 >= oN));
+  assert ( (0 >= o0));
+  assert ( (1 >= o0));
+  assert (!(0 >= o1));
+  assert ( (1 >= o1));
+}
+
+struct BadRelops
+{
+  int i;
+};
+
+constexpr bool operator<(BadRelops a, BadRelops b) { return a.i < b.i; }
+constexpr bool operator>(BadRelops a, BadRelops b) { return a.i < b.i; } // intentional error!
+
+TEST(Optional, bad_relops)
+{
+  using namespace wpi;
+  BadRelops a{1}, b{2};
+  assert (a < b);
+  assert (a > b);
+  
+  optional<BadRelops> oa = a, ob = b;
+  assert (oa < ob);
+  assert (!(oa > ob));
+  
+  assert (oa < b);
+  assert (oa > b);
+  
+  optional<BadRelops&> ra = a, rb = b;
+  assert (ra < rb);
+  assert (!(ra > rb));
+  
+  assert (ra < b);
+  assert (ra > b);
+}
+
+
+TEST(Optional, mixed_equality)
+{
+  using namespace wpi;
+  
+  assert (make_optional(0) == 0);
+  assert (make_optional(1) == 1);
+  assert (make_optional(0) != 1);
+  assert (make_optional(1) != 0);
+  
+  optional<int> oN {nullopt};
+  optional<int> o0 {0};
+  optional<int> o1 {1};
+  
+  assert (o0 ==  0);
+  assert ( 0 == o0);
+  assert (o1 ==  1);
+  assert ( 1 == o1);
+  assert (o1 !=  0);
+  assert ( 0 != o1);
+  assert (o0 !=  1);
+  assert ( 1 != o0);
+  
+  assert ( 1 != oN);
+  assert ( 0 != oN);
+  assert (oN !=  1);
+  assert (oN !=  0);
+  assert (!( 1 == oN));
+  assert (!( 0 == oN));
+  assert (!(oN ==  1));
+  assert (!(oN ==  0));
+  
+  std::string cat{"cat"}, dog{"dog"};
+  optional<std::string> oNil{}, oDog{"dog"}, oCat{"cat"};
+  
+  assert (oCat ==  cat);
+  assert ( cat == oCat);
+  assert (oDog ==  dog);
+  assert ( dog == oDog);
+  assert (oDog !=  cat);
+  assert ( cat != oDog);
+  assert (oCat !=  dog);
+  assert ( dog != oCat);
+  
+  assert ( dog != oNil);
+  assert ( cat != oNil);
+  assert (oNil !=  dog);
+  assert (oNil !=  cat);
+  assert (!( dog == oNil));
+  assert (!( cat == oNil));
+  assert (!(oNil ==  dog));
+  assert (!(oNil ==  cat));
+}
+
+TEST(Optional, const_propagation)
+{
+  using namespace wpi;
+  
+  optional<int> mmi{0};
+  static_assert(std::is_same<decltype(*mmi), int&>::value, "WTF");
+  
+  const optional<int> cmi{0};
+  static_assert(std::is_same<decltype(*cmi), const int&>::value, "WTF");
+  
+  optional<const int> mci{0};
+  static_assert(std::is_same<decltype(*mci), const int&>::value, "WTF");
+  
+  optional<const int> cci{0};
+  static_assert(std::is_same<decltype(*cci), const int&>::value, "WTF");
+}
+
+
+static_assert(std::is_base_of<std::logic_error, wpi::bad_optional_access>::value, "");
+
+TEST(Optional, safe_value)
+{
+  using namespace wpi;
+  
+  try {
+    optional<int> ovN{}, ov1{1};
+    
+    int& r1 = ov1.value();
+    assert (r1 == 1);
+    
+    try { 
+      ovN.value();
+      assert (false);
+    }
+    catch (bad_optional_access const&) {
+    }
+    
+    { // ref variant
+      int i1 = 1;
+      optional<int&> orN{}, or1{i1};
+      
+      int& r2 = or1.value();
+      assert (r2 == 1);
+      
+      try { 
+        orN.value();
+        assert (false);
+      }
+      catch (bad_optional_access const&) {
+      }
+    }
+  }  
+  catch(...) {
+    assert (false);
+  }
+}
+
+TEST(Optional, optional_ref)
+{
+  using namespace tr2;
+  // FAILS: optional<int&&> orr;
+  // FAILS: optional<nullopt_t&> on;
+  int i = 8;
+  optional<int&> ori;
+  assert (!ori);
+  ori.emplace(i);
+  assert (bool(ori));
+  assert (*ori == 8);
+  assert (&*ori == &i);
+  *ori = 9;
+  assert (i == 9);
+  
+  // FAILS: int& ir = ori.value_or(i);
+  int ii = ori.value_or(i);
+  assert (ii == 9);
+  ii = 7;
+  assert (*ori == 9);
+  
+  int j = 22;
+  auto&& oj = make_optional(std::ref(j));
+  *oj = 23;
+  assert (&*oj == &j);
+  assert (j == 23);
+}
+
+TEST(Optional, optional_ref_const_propagation)
+{
+  using namespace wpi;
+  
+  int i = 9;
+  const optional<int&> mi = i;
+  int& r = *mi; 
+  optional<const int&> ci = i;
+  static_assert(std::is_same<decltype(*mi), int&>::value, "WTF");
+  static_assert(std::is_same<decltype(*ci), const int&>::value, "WTF");
+  
+  unused(r);
+}
+
+TEST(Optional, optional_ref_assign)
+{
+  using namespace wpi;
+  
+  int i = 9;
+  optional<int&> ori = i;
+  
+  int j = 1;
+  ori = optional<int&>{j};
+  ori = {j};
+  // FAILS: ori = j;
+  
+  optional<int&> orx = ori;
+  ori = orx;
+  
+  optional<int&> orj = j;
+  
+  assert (ori);
+  assert (*ori == 1);
+  assert (ori == orj);
+  assert (i == 9);
+  
+  *ori = 2;
+  assert (*ori == 2);
+  assert (ori == 2);
+  assert (2 == ori);
+  assert (ori != 3);
+  
+  assert (ori == orj);
+  assert (j == 2);
+  assert (i == 9);
+  
+  ori = {};
+  assert (!ori);
+  assert (ori != orj);
+  assert (j == 2);
+  assert (i == 9);
+}
+
+TEST(Optional, optional_swap)
+{
+  namespace tr2 = wpi;
+  tr2::optional<int> oi {1}, oj {};
+  swap(oi, oj);
+  assert (oj);
+  assert (*oj == 1);
+  assert (!oi);
+  static_assert(noexcept(swap(oi, oj)), "swap() is not noexcept");
+}
+
+
+TEST(Optional, optional_ref_swap)
+{
+  using namespace wpi;
+  int i = 0;
+  int j = 1;
+  optional<int&> oi = i;
+  optional<int&> oj = j;
+  
+  assert (&*oi == &i);
+  assert (&*oj == &j);
+  
+  swap(oi, oj);
+  assert (&*oi == &j);
+  assert (&*oj == &i);
+}
+
+TEST(Optional, optional_initialization)
+{
+    using namespace tr2;
+    using std::string;
+    string s = "STR";
+
+    optional<string> os{s};
+    optional<string> ot = s;
+    optional<string> ou{"STR"};
+    optional<string> ov = string{"STR"};
+    
+}
+
+#include <unordered_set>
+
+TEST(Optional, optional_hashing)
+{
+    using namespace tr2;
+    using std::string;
+    
+    std::hash<int> hi;
+    std::hash<optional<int>> hoi;
+    std::hash<string> hs;
+    std::hash<optional<string>> hos;
+    
+    assert (hi(0) == hoi(optional<int>{0}));
+    assert (hi(1) == hoi(optional<int>{1}));
+    assert (hi(3198) == hoi(optional<int>{3198}));
+    
+    assert (hs("") == hos(optional<string>{""}));
+    assert (hs("0") == hos(optional<string>{"0"}));
+    assert (hs("Qa1#") == hos(optional<string>{"Qa1#"}));
+    
+    std::unordered_set<optional<string>> set;
+    assert(set.find({"Qa1#"}) == set.end());
+    
+    set.insert({"0"});
+    assert(set.find({"Qa1#"}) == set.end());
+    
+    set.insert({"Qa1#"});
+    assert(set.find({"Qa1#"}) != set.end());
+}
+
+
+// optional_ref_emulation
+template <class T>
+struct generic
+{
+  typedef T type;
+};
+
+template <class U>
+struct generic<U&>
+{
+  typedef std::reference_wrapper<U> type;
+};
+
+template <class T>
+using Generic = typename generic<T>::type;
+
+template <class X>
+bool generic_fun()
+{
+  wpi::optional<Generic<X>> op;
+  return bool(op);
+}
+
+TEST(Optional, optional_ref_emulation)
+{
+  using namespace wpi;
+  optional<Generic<int>> oi = 1;
+  assert (*oi == 1);
+  
+  int i = 8;
+  int j = 4;
+  optional<Generic<int&>> ori {i};
+  assert (*ori == 8);
+  assert ((void*)&*ori != (void*)&i); // !DIFFERENT THAN optional<T&>
+
+  *ori = j;
+  assert (*ori == 4);
+}
+
+
+TEST(Optional, moved_on_value_or)
+{
+  using namespace tr2;
+  optional<Oracle> oo{in_place};
+  
+  assert (oo);
+  assert (oo->s == sDefaultConstructed);
+  
+  Oracle o = std::move(oo).value_or( Oracle{OracleVal{}} );
+  assert (oo);
+  assert (oo->s == sMovedFrom);
+  assert (o.s == sMoveConstructed);
+  
+  optional<MoveAware<int>> om {in_place, 1};
+  assert (om);
+  assert (om->moved == false);
+  
+  /*MoveAware<int> m =*/ std::move(om).value_or( MoveAware<int>{1} );
+  assert (om);
+  assert (om->moved == true);
+
+# if OPTIONAL_HAS_MOVE_ACCESSORS == 1  
+  {
+    Date d = optional<Date>{in_place, 1}.value();
+    assert (d.i); // to silence compiler warning
+	
+	Date d2 = *optional<Date>{in_place, 1};
+    assert (d2.i); // to silence compiler warning
+  }
+# endif
+}
+
+
+TEST(Optional, optional_ref_hashing)
+{
+    using namespace tr2;
+    using std::string;
+    
+    std::hash<int> hi;
+    std::hash<optional<int&>> hoi;
+    std::hash<string> hs;
+    std::hash<optional<string&>> hos;
+    
+    int i0 = 0;
+    int i1 = 1;
+    assert (hi(0) == hoi(optional<int&>{i0}));
+    assert (hi(1) == hoi(optional<int&>{i1}));
+    
+    string s{""};
+    string s0{"0"};
+    string sCAT{"CAT"};
+    assert (hs("") == hos(optional<string&>{s}));
+    assert (hs("0") == hos(optional<string&>{s0}));
+    assert (hs("CAT") == hos(optional<string&>{sCAT}));
+    
+    std::unordered_set<optional<string&>> set;
+    assert(set.find({sCAT}) == set.end());
+    
+    set.insert({s0});
+    assert(set.find({sCAT}) == set.end());
+    
+    set.insert({sCAT});
+    assert(set.find({sCAT}) != set.end());
+}
+
+struct Combined
+{
+  int m = 0;
+  int n = 1;
+  
+  constexpr Combined() : m{5}, n{6} {}
+  constexpr Combined(int m, int n) : m{m}, n{n} {}
+};
+
+struct Nasty
+{
+  int m = 0;
+  int n = 1;
+  
+  constexpr Nasty() : m{5}, n{6} {}
+  constexpr Nasty(int m, int n) : m{m}, n{n} {}
+  
+  int operator&() { return n; }
+  int operator&() const { return n; }
+};
+
+TEST(Optional, arrow_operator)
+{
+  using namespace wpi;
+  
+  optional<Combined> oc1{in_place, 1, 2};
+  assert (oc1);
+  assert (oc1->m == 1);
+  assert (oc1->n == 2);
+  
+  optional<Nasty> on{in_place, 1, 2};
+  assert (on);
+  assert (on->m == 1);
+  assert (on->n == 2);
+}
+
+TEST(Optional, arrow_wit_optional_ref)
+{
+  using namespace wpi;
+  
+  Combined c{1, 2};
+  optional<Combined&> oc = c;
+  assert (oc);
+  assert (oc->m == 1);
+  assert (oc->n == 2);
+  
+  Nasty n{1, 2};
+  Nasty m{3, 4};
+  Nasty p{5, 6};
+  
+  optional<Nasty&> on{n};
+  assert (on);
+  assert (on->m == 1);
+  assert (on->n == 2);
+  
+  on = {m};
+  assert (on);
+  assert (on->m == 3);
+  assert (on->n == 4);
+  
+  on.emplace(p);
+  assert (on);
+  assert (on->m == 5);
+  assert (on->n == 6);
+  
+  optional<Nasty&> om{in_place, n};
+  assert (om);
+  assert (om->m == 1);
+  assert (om->n == 2);
+}
+
+TEST(Optional, no_dangling_reference_in_value)
+{
+  // this mostly tests compiler warnings
+  using namespace wpi;
+  optional<int> oi {2};
+  unused (oi.value());
+  const optional<int> coi {3};
+  unused (coi.value());
+}
+
+struct CountedObject
+{
+  static int _counter;
+  bool _throw;
+  CountedObject(bool b) : _throw(b) { ++_counter; }
+  CountedObject(CountedObject const& rhs) : _throw(rhs._throw) { if (_throw) throw int(); }
+  ~CountedObject() { --_counter; }
+};
+
+int CountedObject::_counter = 0;
+
+TEST(Optional, exception_safety)
+{
+  using namespace wpi;
+  try {
+    optional<CountedObject> oo(in_place, true); // throw
+    optional<CountedObject> o1(oo);
+  }
+  catch(...)
+  {
+    //
+  }
+  assert(CountedObject::_counter == 0);
+  
+  try {
+    optional<CountedObject> oo(in_place, true); // throw
+    optional<CountedObject> o1(std::move(oo));  // now move
+  }
+  catch(...)
+  {
+    //
+  }
+  assert(CountedObject::_counter == 0);
+}
+
+TEST(Optional, nested_optional)
+{
+   using namespace wpi;
+	
+   optional<optional<optional<int>>> o1 {nullopt};
+   assert (!o1);
+    
+   optional<optional<optional<int>>> o2 {in_place, nullopt};
+   assert (o2);
+   assert (!*o2);
+    
+   optional<optional<optional<int>>> o3 (in_place, in_place, nullopt);
+   assert (o3);
+   assert (*o3);
+   assert (!**o3);
+}
+
+TEST(Optional, three_ways_of_having_value)
+{
+  using namespace wpi;
+  optional<int> oN, o1 (1);
+  
+  assert (!oN);
+  assert (!oN.has_value());
+  assert (oN == nullopt);
+  
+  assert (o1);
+  assert (o1.has_value());
+  assert (o1 != nullopt);
+  
+  assert (bool(oN) == oN.has_value());
+  assert (bool(o1) == o1.has_value());
+  
+  int i = 1;
+  optional<int&> rN, r1 (i);
+  
+  assert (!rN);
+  assert (!rN.has_value());
+  assert (rN == nullopt);
+  
+  assert (r1);
+  assert (r1.has_value());
+  assert (r1 != nullopt);
+  
+  assert (bool(rN) == rN.has_value());
+  assert (bool(r1) == r1.has_value());
+}
+
+//// constexpr tests
+
+// these 4 classes have different noexcept signatures in move operations
+struct NothrowBoth {
+  NothrowBoth(NothrowBoth&&) noexcept(true) {};
+  void operator=(NothrowBoth&&) noexcept(true) {};
+};
+struct NothrowCtor {
+  NothrowCtor(NothrowCtor&&) noexcept(true) {};
+  void operator=(NothrowCtor&&) noexcept(false) {};
+};
+struct NothrowAssign {
+  NothrowAssign(NothrowAssign&&) noexcept(false) {};
+  void operator=(NothrowAssign&&) noexcept(true) {};
+};
+struct NothrowNone {
+  NothrowNone(NothrowNone&&) noexcept(false) {};
+  void operator=(NothrowNone&&) noexcept(false) {};
+};
+
+void test_noexcept()
+{
+  {
+    tr2::optional<NothrowBoth> b1, b2;
+    static_assert(noexcept(tr2::optional<NothrowBoth>{tr2::constexpr_move(b1)}), "bad noexcept!");
+    static_assert(noexcept(b1 = tr2::constexpr_move(b2)), "bad noexcept!");
+  }
+  {
+    tr2::optional<NothrowCtor> c1, c2;
+    static_assert(noexcept(tr2::optional<NothrowCtor>{tr2::constexpr_move(c1)}), "bad noexcept!");
+    static_assert(!noexcept(c1 = tr2::constexpr_move(c2)), "bad noexcept!");
+  }
+  {
+    tr2::optional<NothrowAssign> a1, a2;
+    static_assert(!noexcept(tr2::optional<NothrowAssign>{tr2::constexpr_move(a1)}), "bad noexcept!");
+    static_assert(!noexcept(a1 = tr2::constexpr_move(a2)), "bad noexcept!");
+  }
+  {
+    tr2::optional<NothrowNone> n1, n2;
+    static_assert(!noexcept(tr2::optional<NothrowNone>{tr2::constexpr_move(n1)}), "bad noexcept!");
+    static_assert(!noexcept(n1 = tr2::constexpr_move(n2)), "bad noexcept!");
+  }
+}
+
+
+void constexpr_test_disengaged()
+{
+  constexpr tr2::optional<int> g0{};
+  constexpr tr2::optional<int> g1{tr2::nullopt};
+  static_assert( !g0, "initialized!" );
+  static_assert( !g1, "initialized!" );
+  
+  static_assert( bool(g1) == bool(g0), "ne!" );
+  
+  static_assert( g1 == g0, "ne!" );
+  static_assert( !(g1 != g0), "ne!" );
+  static_assert( g1 >= g0, "ne!" );
+  static_assert( !(g1 > g0), "ne!" );
+  static_assert( g1 <= g0, "ne!" );
+  static_assert( !(g1 <g0), "ne!" );
+  
+  static_assert( g1 == tr2::nullopt, "!" );
+  static_assert( !(g1 != tr2::nullopt), "!" );
+  static_assert( g1 <= tr2::nullopt, "!" );
+  static_assert( !(g1 < tr2::nullopt), "!" );
+  static_assert( g1 >= tr2::nullopt, "!" );
+  static_assert( !(g1 > tr2::nullopt), "!" );
+  
+  static_assert(  (tr2::nullopt == g0), "!" );
+  static_assert( !(tr2::nullopt != g0), "!" );
+  static_assert(  (tr2::nullopt >= g0), "!" );
+  static_assert( !(tr2::nullopt >  g0), "!" );
+  static_assert(  (tr2::nullopt <= g0), "!" );
+  static_assert( !(tr2::nullopt <  g0), "!" );
+  
+  static_assert(  (g1 != tr2::optional<int>(1)), "!" );
+  static_assert( !(g1 == tr2::optional<int>(1)), "!" );
+  static_assert(  (g1 <  tr2::optional<int>(1)), "!" );
+  static_assert(  (g1 <= tr2::optional<int>(1)), "!" );
+  static_assert( !(g1 >  tr2::optional<int>(1)), "!" );
+  static_assert( !(g1 >  tr2::optional<int>(1)), "!" );
+}
+
+
+constexpr tr2::optional<int> g0{};
+constexpr tr2::optional<int> g2{2};
+static_assert( g2, "not initialized!" );
+static_assert( *g2 == 2, "not 2!" );
+static_assert( g2 == tr2::optional<int>(2), "not 2!" );
+static_assert( g2 != g0, "eq!" );
+
+# if OPTIONAL_HAS_MOVE_ACCESSORS == 1
+static_assert( *tr2::optional<int>{3} == 3, "WTF!" );
+static_assert( tr2::optional<int>{3}.value() == 3, "WTF!" );
+static_assert( tr2::optional<int>{3}.value_or(1) == 3, "WTF!" );
+static_assert( tr2::optional<int>{}.value_or(4) == 4, "WTF!" );
+# endif
+
+constexpr tr2::optional<Combined> gc0{tr2::in_place};
+static_assert(gc0->n == 6, "WTF!");
+
+// optional refs
+int gi = 0;
+constexpr tr2::optional<int&> gori = gi;
+constexpr tr2::optional<int&> gorn{};
+constexpr int& gri = *gori;
+static_assert(gori, "WTF");
+static_assert(!gorn, "WTF");
+static_assert(gori != tr2::nullopt, "WTF");
+static_assert(gorn == tr2::nullopt, "WTF");
+static_assert(&gri == &*gori, "WTF");
+
+constexpr int gci = 1;
+constexpr tr2::optional<int const&> gorci = gci;
+constexpr tr2::optional<int const&> gorcn{};
+
+static_assert(gorcn <  gorci, "WTF");
+static_assert(gorcn <= gorci, "WTF");
+static_assert(gorci == gorci, "WTF");
+static_assert(*gorci == 1, "WTF");
+static_assert(gorci == gci, "WTF");
+
+namespace constexpr_optional_ref_and_arrow 
+{
+  using namespace wpi;
+  constexpr Combined c{1, 2};
+  constexpr optional<Combined const&> oc = c;
+  static_assert(oc, "WTF!");
+  static_assert(oc->m == 1, "WTF!");
+  static_assert(oc->n == 2, "WTF!");
+}
+
+#if OPTIONAL_HAS_CONSTEXPR_INIT_LIST
+
+namespace InitList
+{
+  using namespace wpi;
+  
+  struct ConstInitLister
+  {
+    template <typename T>
+	constexpr ConstInitLister(std::initializer_list<T> il) : len (il.size()) {}
+    size_t len;
+  };
+  
+  constexpr ConstInitLister CIL {2, 3, 4};
+  static_assert(CIL.len == 3, "WTF!");
+  
+  constexpr optional<ConstInitLister> oil {in_place, {4, 5, 6, 7}};
+  static_assert(oil, "WTF!");
+  static_assert(oil->len == 4, "WTF!");
+}
+
+#endif // OPTIONAL_HAS_CONSTEXPR_INIT_LIST
+
+// end constexpr tests
+
+
+#include <string>
+
+
+struct VEC
+{
+    std::vector<int> v;
+    template <typename... X>
+    VEC( X&&...x) : v(std::forward<X>(x)...) {}
+
+    template <typename U, typename... X>
+    VEC(std::initializer_list<U> il, X&&...x) : v(il, std::forward<X>(x)...) {}
+};
+
+
+
+TEST(Optional, Vector) {
+  tr2::optional<int> oi = 1;
+  assert (bool(oi));
+  oi.operator=({});
+  assert (!oi);
+
+  VEC v = {5, 6};
+
+  if (OPTIONAL_HAS_CONSTEXPR_INIT_LIST)
+    std::cout << "Optional has constexpr initializer_list" << std::endl;
+  else
+    std::cout << "Optional doesn't have constexpr initializer_list" << std::endl;
+
+  if (OPTIONAL_HAS_MOVE_ACCESSORS)
+    std::cout << "Optional has constexpr move accessors" << std::endl;
+  else
+    std::cout << "Optional doesn't have constexpr move accessors" << std::endl;	
+}
+
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..78abf69
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/uv/UvLoopWalkTest.cpp
@@ -0,0 +1,70 @@
+/*----------------------------------------------------------------------------*/
+/* 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/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..706e1eb
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/uv/UvTimerTest.cpp
@@ -0,0 +1,74 @@
+/*----------------------------------------------------------------------------*/
+/* 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/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