Last active
June 26, 2022 14:09
-
-
Save willkill07/76268e7a88136705f7c2ea9177897cf1 to your computer and use it in GitHub Desktop.
JSON parser in modern C++
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "json.hpp" | |
namespace json { | |
Wrapper::Wrapper(Value const &value) : v{value} {} | |
Wrapper::Wrapper(Value &value) : v{value} {} | |
Wrapper::operator Value &() { return v; } | |
Wrapper::operator Value const &() const { return v; } | |
Value & | |
Wrapper::value() { | |
return v; | |
} | |
Value const & | |
Wrapper::value() const { | |
return v; | |
} | |
std::optional<Array> | |
detail::JSONParser::parseArray() { | |
Array array; | |
if (!accept('[')) | |
return {}; | |
if (expect(']')) | |
return array; | |
while (true) { | |
if (auto v = parseValue(); v) { | |
array.emplace_back(Wrapper(v.value())); | |
if (expect(',')) | |
continue; | |
if (expect(']')) | |
return array; | |
return {}; | |
} | |
return {}; | |
} | |
} | |
std::optional<Object> | |
detail::JSONParser::parseObject() { | |
Object object; | |
if (!accept('{')) | |
return {}; | |
if (expect('}')) | |
return object; | |
while (true) { | |
if (auto s = parseString(); s) { | |
if (!expect(':')) | |
return {}; | |
if (auto v = parseValue(); v) | |
object.emplace(s.value(), Wrapper(v.value())); | |
else | |
return {}; | |
if (expect(',')) | |
continue; | |
if (expect('}')) | |
return object; | |
return {}; | |
} | |
return {}; | |
} | |
} | |
std::optional<String> | |
detail::JSONParser::parseString() { | |
if (!accept('\"')) | |
return {}; | |
auto ptr = std::next(this->ptr); | |
unsigned length{0}; | |
while (true) { | |
advance(); | |
if (*(this->ptr - 1) != '\\' && *(this->ptr) == '"') | |
return String{ptr, length}; | |
++length; | |
} | |
return {}; | |
} | |
std::optional<Number> | |
detail::JSONParser::parseNumber() { | |
auto ptr = this->ptr; | |
accept('-'); | |
if (!check(::isdigit)) | |
return {}; | |
while (check(::isdigit)) | |
advance(); | |
if (accept('.')) { | |
if (!check(::isdigit)) | |
return {}; | |
while (check(::isdigit)) | |
advance(); | |
} | |
if (accept('e') || accept('E')) { | |
accept('+') || accept('-'); | |
if (!check(::isdigit)) | |
return {}; | |
while (check(::isdigit)) | |
advance(); | |
} | |
return std::stod( | |
std::string{ptr, static_cast<unsigned long>(this->ptr - ptr)}); | |
} | |
std::optional<Value> | |
detail::JSONParser::parseValue() { | |
auto matches = [](String string) { | |
return [&string](char &c) { return string.compare(&c) == 0; }; | |
}; | |
if (check(matches("true"))) { | |
advance(4); | |
return bool{true}; | |
} | |
if (check(matches("false"))) { | |
advance(5); | |
return bool{true}; | |
} | |
if (check(matches("null"))) { | |
advance(4); | |
return Null{}; | |
} | |
if (auto n = parseNumber(); n) | |
return n.value(); | |
if (auto s = parseString(); s) | |
return s.value(); | |
if (auto o = parseObject(); o) | |
return o.value(); | |
if (auto a = parseArray(); a) | |
return a.value(); | |
return {}; | |
} | |
detail::JSONParser::JSONParser(std::string const &str) | |
: ptr{nullptr}, data{str}, obj{parseObject()} {} | |
bool | |
detail::JSONParser::accept(char c) { | |
bool result = (*ptr == c); | |
ptr += result; | |
return result; | |
} | |
bool | |
detail::JSONParser::expect(char c) { | |
return (*ptr == c); | |
} | |
void | |
detail::JSONParser::advance(int i) { | |
ptr += i; | |
} | |
detail::JSONParser::operator std::optional<Object> &() { return obj; } | |
detail::JSONParser::operator std::optional<Object> const &() const { | |
return obj; | |
} | |
} // end namespace json |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// WIP -- not done yet :) | |
#include <iomanip> | |
#include <iostream> | |
#include <optional> | |
#include <string> | |
#include <string_view> | |
#include <unordered_map> | |
#include <variant> | |
#include <vector> | |
namespace json { | |
struct Wrapper; | |
using Null = std::nullptr_t; | |
using Bool = bool; | |
using Number = double; | |
using String = std::string_view; | |
using Array = std::vector<Wrapper>; | |
using Object = std::unordered_map<String, Wrapper>; | |
using Value = std::variant<Bool, Number, String, Array, Object, Null>; | |
struct Wrapper { | |
Wrapper() = default; | |
Wrapper(Wrapper &&) = default; | |
Wrapper(Wrapper const &) = default; | |
Wrapper(Value &value); | |
Wrapper(Value const &value); | |
Wrapper & | |
operator=(Wrapper const &) = default; | |
operator Value &(); | |
operator const Value &() const; | |
Value & | |
value(); | |
Value const & | |
value() const; | |
private: | |
Value v{Null{}}; | |
}; | |
namespace detail { | |
struct JSONParser { | |
JSONParser() = delete; | |
JSONParser(std::string const &); | |
JSONParser(JSONParser const &) = default; | |
JSONParser(JSONParser &&) = default; | |
JSONParser & | |
operator=(JSONParser const &) = default; | |
std::optional<Object> & | |
object(); | |
std::optional<Object> const & | |
object() const; | |
operator std::optional<Object> &(); | |
operator std::optional<Object> const &() const; | |
private: | |
std::optional<Array> | |
parseArray(); | |
std::optional<Object> | |
parseObject(); | |
std::optional<String> | |
parseString(); | |
std::optional<Number> | |
parseNumber(); | |
std::optional<Value> | |
parseValue(); | |
bool | |
accept(char); | |
bool | |
expect(char); | |
void | |
advance(int i = 1); | |
template <typename Fn> | |
bool | |
check(Fn &&fun) { | |
return fun(*ptr); | |
} | |
std::string::pointer ptr; | |
std::string data; | |
std::optional<Object> obj; | |
}; | |
} | |
using Parser = detail::JSONParser; | |
template <typename T> | |
void | |
print(std::optional<T> const &type) { | |
std::visit( | |
[](auto &&arg) { | |
using Type = std::decay_t<decltype(arg)>; | |
if | |
constexpr(std::is_same_v<Type, Number>) { | |
std::cout << std::get<Number>(arg).value(); | |
} | |
else if | |
constexpr(std::is_same_v<Type, Bool>) { | |
std::cout << std::boolalpha << std::get<Bool>(arg).value(); | |
} | |
else if | |
constexpr(std::is_same_v<Type, Null>) { std::cout << "null"; } | |
else if | |
constexpr(std::is_same_v<Type, String>) { | |
std::cout << '"' << std::quoted(std::get<String>(arg).value()) | |
<< '"'; | |
} | |
else if | |
constexpr(std::is_same_v<Type, Array>) { | |
std::cout << '['; | |
auto const &arr = std::get<Array>(arg).value(); | |
print(arr[0]); | |
for (unsigned int i = 1; i < arr.size(); ++i) { | |
std::cout << ','; | |
print(arr[i]); | |
} | |
std::cout << ']'; | |
} | |
else if | |
constexpr(std::is_same_v<Type, Object>) { | |
std::cout << '{'; | |
auto const &map = std::get<Object>(arg).value(); | |
unsigned int index = 0; | |
for (auto & kv : map) { | |
if (index++ != 0) { | |
std::cout << ','; | |
} | |
auto key = std::optional<String>(kv.first); | |
print(key); | |
std::cout << ':'; | |
print(kv.second); | |
} | |
std::cout << '}'; | |
} | |
else | |
static_assert("Whoops"); | |
}, | |
type.value()); | |
} | |
} // end namespace json |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <utility> | |
#include "json.hpp" | |
int | |
main() { | |
// std::string num = "-123.40e-2"; | |
// std::string trueString = "true"; | |
// std::string falseString = "false"; | |
// std::string string = "Hello \"a quote\" world!"; | |
// std::string arrNums = "[1,2,3,4]"; | |
// std::string arrString = "[\"hello\",\"goodbye\"]"; | |
std::string obj = "{\"key\":1.2,\"key2\":true}"; | |
{ | |
std::string object = "{\"key\":1.2,\"key2\":true}"; | |
auto p = json::Parser(object); | |
json::print(p.object()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@willkill07 Thank you for sharing your amazing work!
I tried it but it doesnt work with the following system environment : (ubuntu 18.04.3 LTS)
I am very interested in trying this since I am having this issue here with the existing json library:
nlohmann/json#1281
I hope the complimented example there is a helpful test case for us. I basically need to parse the json inside this file :
https://github.com/smogon/pokemon-showdown/blob/master/data/moves.js