diff --git a/.gitignore b/.gitignore
index 104befbe..eedc9641 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,5 @@ cmake-build-debug
 
 test/test-*
 .svn
+
+test/thirdparty/Fuzzer/libFuzzer.a
diff --git a/Makefile b/Makefile
index fcce453e..8278dbf2 100644
--- a/Makefile
+++ b/Makefile
@@ -49,14 +49,10 @@ doctest:
 fuzz_testing:
 	rm -fr fuzz-testing
 	mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out
-	$(MAKE) fuzz CXX=afl-clang++
-	mv fuzz fuzz-testing
+	$(MAKE) parse_afl_fuzzer -C test CXX=afl-clang++
+	mv test/fuzzer parse_afl_fuzzer
 	find test/data/json_tests -size -5k -name *json | xargs -I{} cp "{}" fuzz-testing/testcases
-	@echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzz"
-
-# the fuzzer binary
-fuzz: test/src/fuzz.cpp src/json.hpp
-	$(CXX) -std=c++11 $(CXXFLAGS) $(FLAGS) $(CPPFLAGS) -I src $< $(LDFLAGS) -o $@
+	@echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer"
 
 
 ##########################################################################
diff --git a/test/Makefile b/test/Makefile
index c1fb33f4..da679998 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -78,3 +78,11 @@ TEST_PATTERN = "*"
 TEST_PREFIX = ""
 check: $(TESTCASES)
 	@cd .. ; for testcase in $(TESTCASES); do echo "Executing $$testcase..."; $(TEST_PREFIX)test/$$testcase $(TEST_PATTERN) || exit 1; done
+
+
+##############################################################################
+# fuzzer
+##############################################################################
+
+parse_afl_fuzzer:
+	$(CXX) $(CXXFLAGS) $(CPPFLAGS) src/fuzzer-driver_afl.cpp src/fuzzer-parse_json.cpp -o $@
diff --git a/test/src/fuzz.cpp b/test/src/fuzz.cpp
deleted file mode 100644
index ef403ea8..00000000
--- a/test/src/fuzz.cpp
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
-    __ _____ _____ _____
- __|  |   __|     |   | |  JSON for Modern C++ (fuzz test support)
-|  |  |__   |  |  | | | |  version 2.0.9
-|_____|_____|_____|_|___|  https://github.com/nlohmann/json
-
-Run "make fuzz_testing" and follow the instructions.
-
-Licensed under the MIT License <http://opensource.org/licenses/MIT>.
-*/
-
-#include <json.hpp>
-
-using json = nlohmann::json;
-
-int main()
-{
-#ifdef __AFL_HAVE_MANUAL_CONTROL
-    while (__AFL_LOOP(1000))
-    {
-#endif
-        try
-        {
-            json j(std::cin);
-            std::cout << j << std::endl;
-        }
-        catch (std::invalid_argument& e)
-        {
-            std::cout << "Invalid argument in parsing" << e.what() << '\n';
-        }
-#ifdef __AFL_HAVE_MANUAL_CONTROL
-    }
-#endif
-}
diff --git a/test/src/fuzzer-driver_afl.cpp b/test/src/fuzzer-driver_afl.cpp
new file mode 100644
index 00000000..e386033a
--- /dev/null
+++ b/test/src/fuzzer-driver_afl.cpp
@@ -0,0 +1,33 @@
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (fuzz test support)
+|  |  |__   |  |  | | | |  version 2.0.9
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+This file implements a driver for American Fuzzy Lop (afl-fuzz). It relies on
+an implementation of the `LLVMFuzzerTestOneInput` function which processes a
+passed byte array.
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+*/
+
+#include <sstream>
+#include <cstdint>
+#include <iostream>
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
+
+int main()
+{
+#ifdef __AFL_HAVE_MANUAL_CONTROL
+    while (__AFL_LOOP(1000))
+    {
+#endif
+        // copy stdin to stringstream to pass it to fuzzer as byte array
+        std::stringstream ss;
+        ss << std::cin.rdbuf();
+        LLVMFuzzerTestOneInput(reinterpret_cast<const uint8_t*>(ss.str().c_str()), ss.str().size());
+#ifdef __AFL_HAVE_MANUAL_CONTROL
+    }
+#endif
+}
diff --git a/test/src/fuzzer-parse_json.cpp b/test/src/fuzzer-parse_json.cpp
index 20a824db..51ac440d 100644
--- a/test/src/fuzzer-parse_json.cpp
+++ b/test/src/fuzzer-parse_json.cpp
@@ -1,16 +1,23 @@
-// Copyright 2016 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (fuzz test support)
+|  |  |__   |  |  | | | |  version 2.0.9
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+This file implements a parser test suitable for fuzz testing. Given a byte
+array data, it performs the following steps:
+
+- j1 = parse(data)
+- s1 = serialize(j1)
+- j2 = parse(s1)
+- s2 = serialize(j2)
+- assert(s1 == s2)
+
+The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
+drivers.
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+*/
 
 #include <iostream>
 #include <sstream>
@@ -18,25 +25,41 @@
 
 using json = nlohmann::json;
 
+// see http://llvm.org/docs/LibFuzzer.html
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
 {
     try
     {
-        std::stringstream s;
-        s << json::parse(data, data + size);
+        // step 1: parse input
+        json j1 = json::parse(data, data + size);
+
         try
         {
-            auto j = json::parse(s.str());
-            std::stringstream s2;
-            s2 << j;
-            assert(s.str() == s2.str());
-            assert(j == json::parse(s.str()));
+            // step 2: round trip
+
+            // first serialization
+            std::string s1 = j1.dump();
+
+            // parse serialization
+            json j2 = json::parse(s1);
+
+            // second serialization
+            std::string s2 = j2.dump();
+
+            // serializations must match
+            assert(s1 == s2);
         }
         catch (const std::invalid_argument&)
         {
-            assert(0);
+            // parsing a JSON serialization must not fail
+            assert(false);
         }
     }
-    catch (const std::invalid_argument&) { }
+    catch (const std::invalid_argument&)
+    {
+        // parse errors are ok, because input may be random bytes
+    }
+
+    // return 0 - non-zero return values are reserved for future use
     return 0;
 }