Skip to content

Commit

Permalink
feat: support getting value stored at the JSON node
Browse files Browse the repository at this point in the history
  • Loading branch information
wzc221207 committed Oct 7, 2024
1 parent ca07844 commit 811a5e3
Show file tree
Hide file tree
Showing 10 changed files with 239 additions and 23 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ jobs:
run: |
cd ./build
ls -al
valgrind --leak-check=yes ./*_test
valgrind --leak-check=yes ./miniJSON_tests
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 0.1.4 (2024-10-07)

Features
- Support getting value stored at the JSON node

## 0.1.3 (2024-10-06)

Features
Expand Down
8 changes: 4 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.14)
project(miniJSON VERSION 0.1.3)
project(miniJSON VERSION 0.1.4)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
Expand All @@ -14,8 +14,8 @@ set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)

enable_testing()
add_executable(basic_parse_test tests/basic_parse_test.cpp)
add_executable(miniJSON_tests tests/basic_parse_test.cpp tests/getter_test.cpp)
include_directories(include)
target_link_libraries(basic_parse_test GTest::gtest_main)
target_link_libraries(miniJSON_tests GTest::gtest_main)
include(GoogleTest)
gtest_discover_tests(basic_parse_test)
gtest_discover_tests(miniJSON_tests)
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

MiniJSON is a C++ header-only JSON parsing library I wrote for my own learning purposes. It is not intended to be used for production.

## Usage

```cpp
#include <iostream>
#include "miniJSON/miniJSON.h"

// parse JSON string
auto json = miniJSON::parse(
R"({"username": "alicia", "age": 32, "friends": ["Michael", "David"], "job": null})");
// access values in JSON node
std::cout << json["username"].get_string() << std::endl; // alicia
std::cout << json["age"].get_integer() << std::endl; // 32
std::cout << json["friends"][1].get_string() << std::endl; // David
```

## License

[MIT License](https://github.com/wzc221207/miniJSON/blob/main/LICENSE)
Expand Down
1 change: 1 addition & 0 deletions include/miniJSON/detail/ordered_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class ordered_map {
}
return m_map[key];
}
size_t count(const Key &key) { return m_map.count(key); }
size_t size() { return m_map.size(); }
typename std::vector<Key>::iterator begin() {
return insertion_order.begin();
Expand Down
2 changes: 0 additions & 2 deletions include/miniJSON/detail/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,9 @@ class parser {

json_parse_type parse_string(JSONNode *result) {
result->set_string();
*result->m_value.str += '\"';
bool string_closed = false;
while (remaining_parse_length() > 0) {
if (parse_and_compare('\"')) {
*result->m_value.str += R"(")";
m_parse_index++;
string_closed = true;
break;
Expand Down
15 changes: 15 additions & 0 deletions include/miniJSON/errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,19 @@ class json_parse_error : public std::exception {
std::string template_str = "json parse error: ";
std::string m_message;
};

/*
This is used to denote JSON type error when trying to access unmatched data
type values.
*/
class json_type_error : public std::exception {
public:
explicit json_type_error(std::string message)
: m_message(template_str + message) {}
const char *what() const noexcept override { return m_message.c_str(); }

private:
std::string template_str = "json type error: ";
std::string m_message;
};
} // namespace miniJSON
93 changes: 79 additions & 14 deletions include/miniJSON/miniJSON.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

#define MINIJSON_VERSION_MAJOR 0
#define MINIJSON_VERSION_MINOR 1
#define MINIJSON_VERSION_PATCH 3
#define MINIJSON_VERSION_PATCH 4

namespace miniJSON {
/*
Expand Down Expand Up @@ -61,7 +61,7 @@ class json_node {
return "null";
}
if (m_type == json_value_type::string) {
return *m_value.str;
return '\"' + *m_value.str + '\"';
}
if (m_type == json_value_type::number_int) {
return std::to_string(m_value.number_int);
Expand All @@ -87,7 +87,7 @@ class json_node {
size_t sz = (*m_value.object).size();
int i = 0;
for (auto key : *m_value.object) {
s += key;
s += '\"' + key + '\"';
s += ":";
s += (*m_value.object)[key]->to_string();
if (i != sz - 1) {
Expand All @@ -101,28 +101,93 @@ class json_node {
return "";
}

public:
using json_object_t = ordered_map<std::string, json_node *>;
using json_array_t = std::vector<json_node *>;
using json_string_t = std::string;
using json_int_t = int64_t;
using json_double_t = double;
using json_boolean_t = bool;

public:
/*
Get the current JSON value type
*/
json_value_type get_type() const { return m_type; }

/*
Get the current boolean value
*/
const bool *get_boolean() const {
const bool get_boolean() const {
if (m_type == json_value_type::boolean) {
return &(m_value.boolean);
return m_value.boolean;
}
return nullptr;
throw json_type_error(
"trying to access boolean value from a non-boolean JSON node");
}

public:
using json_object_t = ordered_map<std::string, json_node *>;
using json_array_t = std::vector<json_node *>;
using json_string_t = std::string;
using json_int_t = int64_t;
using json_double_t = double;
using json_boolean_t = bool;
/*
Get the current boolean value
*/
const json_string_t get_string() const {
if (m_type == json_value_type::string) {
return *m_value.str;
}
throw json_type_error(
"trying to access string value from a non-string JSON node");
}

/*
Get the current integer value
*/
const json_int_t get_integer() const {
if (m_type == json_value_type::number_int) {
return m_value.number_int;
}
throw json_type_error(
"trying to access integer value from a non-integer JSON node");
}

/*
Get the current double value
*/
const json_double_t get_double() const {
if (m_type == json_value_type::number_double) {
return m_value.number_double;
}
throw json_type_error(
"trying to access double value from a non-double JSON node");
}

/*
Get the current object value
*/
json_node &operator[](json_string_t key) const {
if (m_type == json_value_type::object) {
if (m_value.object->count(key) == 0) {
throw std::out_of_range("key is not found in the object");
}
auto val = (*m_value.object)[key];
return *val;
}
throw json_type_error(
"trying to access object value from a non-object JSON node");
}

/*
Get the current array value
*/
json_node &operator[](size_t index) const {
if (m_type == json_value_type::array) {
if ((index >= m_value.array->size()) || index < 0) {
throw std::out_of_range("given index out of range");
}
auto val = (*m_value.array)[index];
return *val;
}
throw json_type_error(
"trying to access array value from a non-array JSON node");
}

public:
/*
Expand Down Expand Up @@ -202,7 +267,7 @@ class json_node {
/*
Parse JSON string into JSON node (Deserialization)
*/
json_node parse(const std::string &s) {
inline json_node parse(const std::string &s) {
json_node j;
detail::parser<json_node> parser{&j, s};
parser.parse();
Expand Down
2 changes: 0 additions & 2 deletions tests/basic_parse_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,11 @@ TEST(BasicParseTest, Empty) {
TEST(BasicParseTest, Boolean) {
{
auto json = miniJSON::parse("true");
EXPECT_EQ(*json.get_boolean(), true);
auto json_str = json.to_string();
EXPECT_EQ(json_str, "true");
}
{
auto json = miniJSON::parse("false");
EXPECT_EQ(*json.get_boolean(), false);
auto json_str = json.to_string();
EXPECT_EQ(json_str, "false");
}
Expand Down
119 changes: 119 additions & 0 deletions tests/getter_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright (c) 2024 Zhichen (Joshua) Wen
#include <gtest/gtest.h>

#include <cmath>
#include <limits>
#include <string>

#include "miniJSON/miniJSON.h"

bool double_equal(double a, double b) {
return std::fabs(a - b) < std::numeric_limits<double>::epsilon();
}

TEST(GetterTest, Boolean) {
{
auto json = miniJSON::parse("true");
EXPECT_EQ(json.get_boolean(), true);
}
{
auto json = miniJSON::parse("false");
EXPECT_EQ(json.get_boolean(), false);
}
{
// trying to access unmatched data type value
EXPECT_THROW(
{
auto json = miniJSON::parse("false");
json.get_integer();
},
miniJSON::json_type_error);
}
}

TEST(GetterTest, String) {
{
auto json = miniJSON::parse(R"("abc")");
EXPECT_EQ(json.get_string(), std::string{"abc"});
}
{
// trying to access unmatched data type value
EXPECT_THROW(
{
auto json = miniJSON::parse(R"("sgdf")");
json.get_boolean();
},
miniJSON::json_type_error);
}
}

TEST(GetterTest, Number) {
{
auto json = miniJSON::parse(R"(123)");
EXPECT_EQ(json.get_integer(), 123);
}
{
auto json = miniJSON::parse(R"(67.12)");
EXPECT_TRUE(double_equal(json.get_double(), 67.12));
}
{
// trying to access unmatched data type value
EXPECT_THROW(
{
auto json = miniJSON::parse("1390");
json.get_string();
},
miniJSON::json_type_error);
}
}

TEST(GetterTest, Object) {
{
auto json = miniJSON::parse(R"({"name": "Alicia"})");
EXPECT_EQ(json["name"].get_string(), std::string{"Alicia"});
}
{
// trying to access unmatched data type value
EXPECT_THROW(
{
auto json = miniJSON::parse(R"({"name": "Alicia"})");
json.get_string();
},
miniJSON::json_type_error);
}
{
// non-existent key
EXPECT_THROW(
{
auto json = miniJSON::parse(R"({"name": "Alicia"})");
json["name1"];
},
std::out_of_range);
}
}

TEST(GetterTest, Array) {
{
auto json = miniJSON::parse(R"({"friends": ["Alicia", "David"]})");
EXPECT_EQ(json["friends"][0].get_string(), std::string{"Alicia"});
EXPECT_EQ(json["friends"][1].get_string(), std::string{"David"});
}
{
// trying to access unmatched data type value
EXPECT_THROW(
{
auto json = miniJSON::parse(R"({"friends": ["Alicia", "David"]})");
json.get_string();
},
miniJSON::json_type_error);
}
{
// invalid index
EXPECT_THROW(
{
auto json = miniJSON::parse(R"({"friends": ["Alicia", "David"]})");
json["friends"][2];
},
std::out_of_range);
}
}

0 comments on commit 811a5e3

Please sign in to comment.