summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDylan <boss@tehbox.org>2026-06-06 18:05:19 +1200
committerDylan <boss@tehbox.org>2026-06-06 18:05:19 +1200
commit46c896bcd78d31130321562b0659e28230261b8e (patch)
tree66174f484693763b7a7ca678fda8ea4b2b98cbe7
downloadtehjson-46c896bcd78d31130321562b0659e28230261b8e.tar.gz
tehjson-46c896bcd78d31130321562b0659e28230261b8e.zip
feat: Initial commit, json data structure and serializing to json
-rw-r--r--.envrc1
-rw-r--r--.gitignore8
-rw-r--r--GNUmakefile59
-rw-r--r--flake.lock61
-rw-r--r--flake.nix19
-rw-r--r--src/json.cpp69
-rw-r--r--src/json.h40
-rw-r--r--src/json.hpp28
-rwxr-xr-xtest.sh3
-rw-r--r--test/main.cpp110
10 files changed, 398 insertions, 0 deletions
diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..3550a30
--- /dev/null
+++ b/.envrc
@@ -0,0 +1 @@
+use flake
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..66ccce8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+.cache
+.clang-format
+.direnv
+build
+compile_commands.json
+include
+json_test
+lib \ No newline at end of file
diff --git a/GNUmakefile b/GNUmakefile
new file mode 100644
index 0000000..b8aab00
--- /dev/null
+++ b/GNUmakefile
@@ -0,0 +1,59 @@
+# Source
+OBJS_DIR := ./build
+SOURCE_DIR := ./src
+LIB_DIR := ./lib
+INCLUDE_DIR := ./include
+TEST_SRC_DIR := ./test
+out ?=
+INSTALL_DIR = $(out)
+
+# Flags
+CXX := g++
+CXXFLAGS := -std=c++23 # -fsanitize=address -g
+LINKFLAGS := #-static-libasan
+TEST_CXXFLAGS := -std=c++23 -I$(INCLUDE_DIR) -fsanitize=address -g
+TEST_LINKFLAGS := -L$(LIB_DIR) -ltehjson -static-libasan
+
+# Outputs
+LIB := $(LIB_DIR)/libtehjson.so
+TEST := json_test
+
+# Files
+SOURCE_FILES := $(wildcard $(SOURCE_DIR)/*.cpp)
+SOURCE_HEADERS := $(wildcard $(SOURCE_DIR)/*.h $(SOURCE_DIR)/*.hpp)
+OBJS := $(subst $(SOURCE_DIR),$(OBJS_DIR), $(patsubst %.cpp,%.o,$(SOURCE_FILES)))
+INCLUDE_HEADERS := $(subst $(SOURCE_DIR),$(INCLUDE_DIR), $(SOURCE_HEADERS))
+TEST_SOURCE := $(wildcard $(TEST_SRC_DIR)/*.cpp)
+TEST_HEADERS := $(wildcard $(TEST_SRC_DIR)/*.h)
+TEST_OBJS = $(subst $(TEST_SRC_DIR),$(OBJS_DIR), $(patsubst %.cpp,%.o,$(TEST_SOURCE)))
+
+# Main lib
+$(LIB): $(INCLUDE_HEADERS) $(OBJS)
+ $(CXX) $(OBJS) $(CXXFLAGS) $(LINKFLAGS) -shared -o $(LIB)
+
+$(OBJS_DIR)/%.o: $(SOURCE_DIR)/%.cpp $(SOURCE_DIR)/%.h
+ $(CXX) $(CXXFLAGS) -c $< -o $@
+
+$(INCLUDE_DIR)/%.h: $(SOURCE_DIR)/%.h lib
+ cp $< $@
+$(INCLUDE_DIR)/%.hpp: $(SOURCE_DIR)/%.hpp lib
+ cp $< $@
+
+# Test binary
+$(TEST): $(TEST_OBJS) $(LIB)
+ $(CXX) $(TEST_OBJS) $(TEST_CXXFLAGS) $(TEST_LINKFLAGS) -o $(TEST)
+
+$(OBJS_DIR)/%.o: $(TEST_SRC_DIR)/%.cpp $(TEST_SOURCE) $(TEST_HEADERS)
+ $(CXX) $(TEST_CXXFLAGS) -c $< -o $@
+
+
+# Phony
+.PHONY: clean
+clean:
+ rm -f $(LIB)
+ rm -f $(OBJS_DIR)/*.o
+ rm -f $(TEST)
+
+test: $(TEST)
+
+$(OBJS_DIR)/json.o: $(SOURCE_DIR)/json.hpp
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..032fb8b
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,61 @@
+{
+ "nodes": {
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1731533236,
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1751274312,
+ "narHash": "sha256-/bVBlRpECLVzjV19t5KMdMFWSwKLtb5RyXdjz3LJT+g=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "50ab793786d9de88ee30ec4e4c24fb4236fc2674",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-24.11",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..18ad6d2
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,19 @@
+{
+ description = "default c++ flake";
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
+ flake-utils.url = "github:numtide/flake-utils";
+ };
+ outputs = { self, nixpkgs, ... }@inputs:
+ inputs.flake-utils.lib.eachDefaultSystem (system: let
+ pkgs = nixpkgs.legacyPackages.${system};
+ in {
+ devShells.default = pkgs.mkShell {
+ nativeBuildInputs = [
+ pkgs.gcc
+ pkgs.gnumake
+ pkgs.clang-tools
+ ];
+ };
+ });
+}
diff --git a/src/json.cpp b/src/json.cpp
new file mode 100644
index 0000000..c11ca6e
--- /dev/null
+++ b/src/json.cpp
@@ -0,0 +1,69 @@
+#include "json.h"
+
+#include <cstddef>
+#include <stdexcept>
+
+namespace TehJSON
+{
+ JSON::JSON()
+ :children()
+ {
+ }
+
+ JSON::~JSON()
+ {
+ }
+
+ std::string JSON::leafType()
+ {
+ if(!isLeaf)
+ throw std::runtime_error("Node is not a leaf!");
+ throw std::runtime_error("Not implemented yet");
+ }
+
+ std::string JSON::getSerialized()
+ {
+ return _getSerialized(0);
+ }
+ std::string JSON::_getSerialized(int currIndent)
+ {
+ if(isLeaf)
+ return dataSerializer(data);
+
+ std::string serialized = "{\n";
+
+ int childrenLeft = childCount();
+ for(auto [childName, child]: children)
+ {
+ childrenLeft--;
+ for(int i = 0; i < currIndent + 1; i++)
+ serialized += '\t';
+ serialized += "\"" + childName + "\": ";
+ serialized += child._getSerialized(currIndent + 1);
+ if(childrenLeft != 0)
+ serialized += ',';
+ serialized += '\n';
+ }
+ for(int i = 0; i < currIndent; i++)
+ serialized += '\t';
+ serialized += "}";
+
+ return serialized;
+ }
+
+ JSON& JSON::operator[](std::string name)
+ {
+ if(isLeaf)
+ throw std::runtime_error("Node is a leaf!");
+ // if(children.count(name) == 0)
+ // throw std::out_of_range("Child \"" + name + "\" does not exist");
+ return children[name];
+ }
+
+ size_t JSON::childCount()
+ {
+ if(isLeaf)
+ throw std::runtime_error("Node is a leaf!");
+ return children.size();
+ }
+}
diff --git a/src/json.h b/src/json.h
new file mode 100644
index 0000000..d1d7938
--- /dev/null
+++ b/src/json.h
@@ -0,0 +1,40 @@
+#include <cstddef>
+#include <map>
+#include <memory>
+#include <string>
+
+namespace TehJSON
+{
+ class JSON
+ {
+ public:
+ JSON();
+ JSON(const JSON& other) = default;
+ ~JSON();
+
+ // Leaf methods
+ template <typename T>
+ T& get();
+ template <typename T>
+ void set(T value);
+ std::string leafType();
+ std::string getSerialized();
+ std::string _getSerialized(int currIndent);
+ template <typename T>
+ static std::string serializeData(std::shared_ptr<void> data);
+
+ // Non leaf methods
+ JSON& operator[](std::string name);
+ size_t childCount();
+ // std::vector<std::string> childNames();
+ private:
+ bool isLeaf = false;
+
+ // Leaf data fields
+ std::shared_ptr<void> data;
+ std::string (*dataSerializer)(std::shared_ptr<void>);
+
+ // Not leaf data fields
+ std::map<std::string, JSON> children;
+ };
+}
diff --git a/src/json.hpp b/src/json.hpp
new file mode 100644
index 0000000..6f19ddd
--- /dev/null
+++ b/src/json.hpp
@@ -0,0 +1,28 @@
+#include "json.h"
+
+#include <stdexcept>
+#include <iostream>
+
+namespace TehJSON
+{
+
+ template<typename T>
+ T& JSON::get()
+ {
+ if(!isLeaf)
+ throw std::runtime_error("Node is not a leaf!");
+
+ return *static_cast<T*>(data.get());
+ }
+
+ template<typename T>
+ void JSON::set(T value)
+ {
+ if(children.size() != 0)
+ throw std::runtime_error("Node is not a leaf (has children already)!");
+ isLeaf = true;
+ dataSerializer = JSON::serializeData<T>;
+ data = std::make_shared<T>(value);
+ }
+
+}
diff --git a/test.sh b/test.sh
new file mode 100755
index 0000000..634b0d4
--- /dev/null
+++ b/test.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+make test && LD_LIBRARY_PATH=./lib:$LD_LIBRARY_PATH ./json_test $@
diff --git a/test/main.cpp b/test/main.cpp
new file mode 100644
index 0000000..9430503
--- /dev/null
+++ b/test/main.cpp
@@ -0,0 +1,110 @@
+#include <json.hpp>
+
+#include <iostream>
+#include <memory>
+
+using std::cout, std::endl;
+
+class TestClass {
+private:
+ int id;
+ // std::string* testData;
+
+public:
+ // 1. Default Constructor: Sets initial "empty" state [20, 24]
+ TestClass() : id(-1)/*, testData(new std::string("default"))*/ {
+ std::cout << "Default Constructor called" << std::endl;
+ }
+
+ // 2. Parameterized Constructor: For dependency injection/direct testing [24, 25]
+ TestClass(int id/*, std::string val*/) : id(id)/*, testData(new std::string(val))*/ {
+ std::cout << "Parameterized Constructor (id: " << id << ")" << std::endl;
+ }
+
+ // 3. Copy Constructor (Rule of 5): Deep copies resource [14, 23]
+ TestClass(const TestClass& other) : id(other.id+1)/*, testData(new std::string(*other.testData))*/ {
+ std::cout << "Copy Constructor called" << id << std::endl;
+ }
+
+ // 4. Move Constructor (Rule of 5): Transfers ownership of resource [14, 16]
+ TestClass(TestClass&& other) noexcept : id(other.id+1)/*, testData(other.testData)*/ {
+ // other.testData = nullptr;
+ other.id = -1;
+ std::cout << "Move Constructor called" << id << std::endl;
+ }
+
+ // 5. Destructor: Essential for RAII and preventing memory leaks [14, 29]
+ ~TestClass() {
+ // delete testData;
+ std::cout << "Destructor called" << id << std::endl;
+ }
+
+ // Assignment Operators (Standard Rule of 5)
+ TestClass& operator=(const TestClass& other) {
+ if (this == &other) return *this;
+ // delete testData;
+ id = other.id + 1;
+ // testData = new std::string(*other.testData);
+ std::cout << "Assignment op 1 called" << id << std::endl;
+ return *this;
+ }
+
+ TestClass& operator=(TestClass&& other) noexcept {
+ if (this == &other) return *this;
+ // delete testData;
+ id = other.id + 1;
+ // testData = other.testData;
+ // other.testData = nullptr;
+ std::cout << "Assignment op 2 called" << id << std::endl;
+ return *this;
+ }
+
+ // Helper for verification [18, 21]
+ int getId() const { return id; }
+ // std::string getData() const { return testData ? *testData : "null"; }
+};
+
+
+template <> std::string TehJSON::JSON::serializeData<int>(std::shared_ptr<void> data)
+{
+ return std::to_string(*static_cast<int*>(data.get()));
+}
+template <> std::string TehJSON::JSON::serializeData<float>(std::shared_ptr<void> data)
+{
+ return std::to_string(*static_cast<float*>(data.get()));
+}
+template <> std::string TehJSON::JSON::serializeData<TestClass>(std::shared_ptr<void> data)
+{
+ return "test class";
+}
+template <> std::string TehJSON::JSON::serializeData<std::string>(std::shared_ptr<void> data)
+{
+ return '"' + *static_cast<std::string*>(data.get()) + '"';
+}
+
+
+int main()
+{
+ TehJSON::JSON json;
+ // TestClass test1;
+ // json["test1"].set(test1);
+ json["test_string"].set<std::string>("stringy");
+ json["test_int"].set<int>(123);
+ json["test_float"].set<float>(51.8);
+ json["test_object"]["test_int"].set<int>(100);
+ json["test_object"]["test_float"].set<float>(100);
+
+ cout << json.getSerialized() << endl;
+
+ // std::shared_ptr<void> test;
+ // test = std::make_shared<TestClass>(1);
+ // test = std::make_shared<TestClass>(2);
+ // test = std::make_shared<std::string>(std::string("test"));
+ // std::string& testRef = *(std::string*)test.get();
+ // cout << *(std::string*)test.get() << endl;
+ // testRef += " test2";
+ // cout << *(std::string*)test.get() << endl;
+
+
+ cout << "abc" << endl;
+}