Skip to content

Instantly share code, notes, and snippets.

@willkill07
Last active June 26, 2022 14:09
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save willkill07/76268e7a88136705f7c2ea9177897cf1 to your computer and use it in GitHub Desktop.
Save willkill07/76268e7a88136705f7c2ea9177897cf1 to your computer and use it in GitHub Desktop.
JSON parser in modern C++
#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
// 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
#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());
}
}
@chakpongchung
Copy link

chakpongchung commented Jan 21, 2020

@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)

cpchung:~$ g++ --version
g++ (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0

#set(CMAKE_CXX_STANDARD 17) #tried this too
set(CMAKE_CXX_STANDARD 20)

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment