From 0e202ce89df91a10a9d0012dd9eec080cd5a8b0e Mon Sep 17 00:00:00 2001 From: Mike Nolan Date: Tue, 8 Apr 2025 22:00:31 -0500 Subject: [PATCH] Implement JSON --- apps/tjsonpretty.cpp | 73 +++ apps/tjsonunpretty.cpp | 73 +++ examples/printjsondecodedemoji.cpp | 13 + .../TessesFramework/Serialization/Json.hpp | 74 +++ src/Serialization/Json.cpp | 527 ++++++++++++++++++ 5 files changed, 760 insertions(+) create mode 100644 apps/tjsonpretty.cpp create mode 100644 apps/tjsonunpretty.cpp create mode 100644 examples/printjsondecodedemoji.cpp create mode 100644 include/TessesFramework/Serialization/Json.hpp create mode 100644 src/Serialization/Json.cpp diff --git a/apps/tjsonpretty.cpp b/apps/tjsonpretty.cpp new file mode 100644 index 0000000..751d813 --- /dev/null +++ b/apps/tjsonpretty.cpp @@ -0,0 +1,73 @@ +#include "TessesFramework/TessesFramework.hpp" +using namespace Tesses::Framework::Streams; +using namespace Tesses::Framework::Serialization::Json; +using namespace Tesses::Framework::TextStreams; +FileStream* OpenWrite(std::string dest) +{ + if(dest == "-") + { + return new FileStream(stdout,false,"w"); + } + else + { + FileStream* strm = new FileStream(dest,"w"); + if(!strm->CanWrite()) + { + delete strm; + return nullptr; + } + return strm; + } +} +FileStream* OpenRead(std::string src) +{ + if(src == "-") + { + return new FileStream(stdin,false,"r"); + } + else + { + FileStream* strm = new FileStream(src,"r"); + if(!strm->CanRead()) + { + delete strm; + return nullptr; + } + return strm; + } +} +int main(int argc, char** argv) +{ + if(argc < 3) + { + std::cout << "USAGE: " << argv[0] << " SRC DEST" << std::endl; + std::cout << "SRC: json file or - for stdin to make pretty" << std::endl; + std::cout << "DEST: prettied file or - for stdout" << std::endl; + return 0; + } + FileStream* src = OpenRead(argv[1]); + + FileStream* dest = OpenWrite(argv[2]); + + if(src == nullptr) + { + if(dest != nullptr) delete dest; + std::cerr << "ERROR: Input could not be read" << std::endl; + return 1; + } + if(dest == nullptr) + { + delete src; + std::cerr << "ERROR: Output could not be read" << std::endl; + return 1; + } + + StreamReader reader(src,true); + StreamWriter writer(dest,true); + + auto str = reader.ReadToEnd(); + writer.WriteLine(Json::Encode(Json::Decode(str))); + + + return 0; +} \ No newline at end of file diff --git a/apps/tjsonunpretty.cpp b/apps/tjsonunpretty.cpp new file mode 100644 index 0000000..880c479 --- /dev/null +++ b/apps/tjsonunpretty.cpp @@ -0,0 +1,73 @@ +#include "TessesFramework/TessesFramework.hpp" +using namespace Tesses::Framework::Streams; +using namespace Tesses::Framework::Serialization::Json; +using namespace Tesses::Framework::TextStreams; +FileStream* OpenWrite(std::string dest) +{ + if(dest == "-") + { + return new FileStream(stdout,false,"w"); + } + else + { + FileStream* strm = new FileStream(dest,"w"); + if(!strm->CanWrite()) + { + delete strm; + return nullptr; + } + return strm; + } +} +FileStream* OpenRead(std::string src) +{ + if(src == "-") + { + return new FileStream(stdin,false,"r"); + } + else + { + FileStream* strm = new FileStream(src,"r"); + if(!strm->CanRead()) + { + delete strm; + return nullptr; + } + return strm; + } +} +int main(int argc, char** argv) +{ + if(argc < 3) + { + std::cout << "USAGE: " << argv[0] << " SRC DEST" << std::endl; + std::cout << "SRC: json file or - for stdin to make unpretty" << std::endl; + std::cout << "DEST: unprettied file or - for stdout" << std::endl; + return 0; + } + FileStream* src = OpenRead(argv[1]); + + FileStream* dest = OpenWrite(argv[2]); + + if(src == nullptr) + { + if(dest != nullptr) delete dest; + std::cerr << "ERROR: Input could not be read" << std::endl; + return 1; + } + if(dest == nullptr) + { + delete src; + std::cerr << "ERROR: Output could not be read" << std::endl; + return 1; + } + + StreamReader reader(src,true); + StreamWriter writer(dest,true); + + auto str = reader.ReadToEnd(); + writer.WriteLine(Json::Encode(Json::Decode(str),false)); + + + return 0; +} \ No newline at end of file diff --git a/examples/printjsondecodedemoji.cpp b/examples/printjsondecodedemoji.cpp new file mode 100644 index 0000000..ab17533 --- /dev/null +++ b/examples/printjsondecodedemoji.cpp @@ -0,0 +1,13 @@ +#include "TessesFramework/Serialization/Json.hpp" + +using namespace Tesses::Framework::Serialization::Json; + +int main(int argc, char** argv) +{ + auto json = Json::Decode("\"\\uD83D\\uDE44\""); + std::string str; + if(TryGetJToken(json,str)) + { + std::cout << str << std::endl; + } +} \ No newline at end of file diff --git a/include/TessesFramework/Serialization/Json.hpp b/include/TessesFramework/Serialization/Json.hpp new file mode 100644 index 0000000..bd32f36 --- /dev/null +++ b/include/TessesFramework/Serialization/Json.hpp @@ -0,0 +1,74 @@ +#pragma once +#include +#include +#include +#include +#include +namespace Tesses::Framework::Serialization::Json +{ + class JArray; + class JObject; + class JUndefined { + public: + }; + + using JToken = std::variant; + + class JOItem; + + class JObject { + std::map map; + public: + JObject(); + JObject(std::initializer_list items); + void SetValue(std::string key, JToken item); + JToken GetValue(std::string key); + void Remove(std::string key); + std::map& GetMap(); + std::map::iterator begin(); + std::map::iterator end(); + }; + class JArray + { + std::vector items; + public: + JArray(); + JArray(std::initializer_list items); + std::vector& GetVector(); + void Add(JToken item); + void RemoveAt(size_t index); + size_t Count(); + void SetAt(size_t index, JToken item); + JToken GetAt(size_t index); + void Clear(); + + std::vector::iterator begin(); + std::vector::iterator end(); + }; + class JOItem { + public: + JOItem(); + JOItem(std::string key, JToken value); + + std::string key; + JToken value; + }; + template + bool TryGetJToken(JToken json, T& item) + { + if(std::holds_alternative(json)) + { + item = std::get(json); + return true; + } + return false; + } + + class Json + { + static std::string tab(std::string str); + public: + static JToken Decode(std::string str); + static std::string Encode(JToken tkn, bool indent=true); + }; +} \ No newline at end of file diff --git a/src/Serialization/Json.cpp b/src/Serialization/Json.cpp new file mode 100644 index 0000000..7b01f2c --- /dev/null +++ b/src/Serialization/Json.cpp @@ -0,0 +1,527 @@ +#include "TessesFramework/Serialization/Json.hpp" +#include "TessesFramework/Http/HttpUtils.hpp" +#include + +#include "TessesFramework/Common.hpp" +using namespace Tesses::Framework::Http; +namespace Tesses::Framework::Serialization::Json +{ + void JObject::Remove(std::string key) + { + map.erase(key); + } + void JObject::SetValue(std::string key, JToken item) + { + if(!std::holds_alternative(item)) + map[key] = item; + else + Remove(key); + } + JToken JObject::GetValue(std::string key) + { + if(map.count(key)>0) + return map[key]; + else + return JUndefined(); + } + std::map& JObject::GetMap() + { + return map; + } + std::map::iterator JObject::begin() + { + return map.begin(); + } + std::map::iterator JObject::end() + { + return map.end(); + } + JArray::JArray() + { + + } + JArray::JArray(std::initializer_list tokens) + { + this->items = tokens; + } + std::vector& JArray::GetVector() + { + return items; + } + void JArray::Add(JToken item) + { + if(!std::holds_alternative(item)) + items.push_back(item); + } + void JArray::RemoveAt(size_t index) + { + if(index < items.size()) + items.erase(items.begin()+index); + } + size_t JArray::Count() + { + return items.size(); + } + void JArray::SetAt(size_t index, JToken item) + { + if(index < items.size() && !std::holds_alternative(item)) + items[index] = item; + } + JToken JArray::GetAt(size_t index) + { + if(index < items.size()) + return items.at(index); + return JUndefined(); + } + void JArray::Clear() + { + items.clear(); + } + + std::vector::iterator JArray::begin() + { + return items.begin(); + } + std::vector::iterator JArray::end() + { + return items.end(); + } + JOItem::JOItem() + { + + } + JOItem::JOItem(std::string key, JToken value) + { + this->key = key; + this->value = value; + } + + JObject::JObject() + { + + } + JObject::JObject(std::initializer_list items) + { + for(auto item : items) + this->map[item.key] = item.value; + } + + std::string Json::tab(std::string txt) + { + std::string newStr="\t"; + for(auto item : txt) + { + if(item == '\n') + { + newStr.append("\n\t"); + } + else + { + newStr.push_back(item); + } + } + return newStr; + } + + JToken Json::Decode(std::string str) + { + std::vector> tokens; + + std::string buff={}; + + std::function flush = [&buff,&tokens]()->void { + if(!buff.empty()) + { + tokens.push_back(std::pair(buff,false)); + buff={}; + } + }; + + for(size_t i = 0; i < str.size(); i++) + { + switch(str[i]) + { + case '\"': + { + flush(); + i++; + std::string str2={}; + for(; i < str.size() && str[i] != '\"'; i++) + { + if(str[i] == '\\') + { + i++; + if(i < str.size()) + { + if(str[i] == 'n') + { + str2.push_back('\n'); + } + else if(str[i] == 'r') + { + str2.push_back('\r'); + } + else if(str[i] == 't') + { + str2.push_back('\t'); + } + else if(str[i] == 'f') + { + str2.push_back('\f'); + } + else if(str[i] == 'b') + { + str2.push_back('\b'); + } + else if(str[i] == 'u') + { + i++; + //we need to get four of these + uint16_t v = 0; + if(i + 4 <= str.size()) + { + for(size_t i2 = 0; i2 < 4; i2++,i++) + { + v |= HttpUtils::HexToNibble(str[i]) << ((3-i2) * 4); + } + + + uint32_t v2 = v; + + if((v & 0xFC00) == 0xD800) + { + + v2 = (v & 0x03FF) << 10; + if(i + 6 <= str.size() && str.substr(i,2) == "\\u") + { + i+=2; + v=0; + + for(size_t i2 = 0; i2 < 4; i2++, i++) + { + v |= HttpUtils::HexToNibble(str[i]) << ((3-i2) * 4); + } + if((v & 0xFC00) != 0xDC00) + { + throw TextException("Not a lower utf-16 surrogate pair."); + } + + + v2 |= (v & 0x03FF); + + v2 += 0x10000; + } + else + { + + throw TextException("Could not read lower utf-16 surrogate pair."); + } + if(v2 <= 0x7F) + { + str2.push_back((char)v2); + } + else if(v2 >= 0x80 && v2 <= 0x7FF) + { + uint8_t high = 0b11000000 | ((v2 >> 6) & 0b00011111); + uint8_t low = 0b10000000 | (v2 & 0b00111111); + str2.push_back((char)high); + str2.push_back((char)low); + } + else if(v2 >= 0x800 && v2 <= 0xFFFF) + { + uint8_t highest = 0b11100000 | ((v2 >> 12) & 0b00001111); + uint8_t high = 0b10000000 | ((v2 >> 6) & 0b00111111); + uint8_t low = 0b10000000 | (v2 & 0b00111111); + str2.push_back((char)highest); + str2.push_back((char)high); + str2.push_back((char)low); + } + else if(v2 >= 0x010000 && v2 <= 0x10FFFF) + { + uint8_t highest = 0b11110000 | ((v2 >> 18) & 0b00000111); + uint8_t high = 0b10000000 | ((v2 >> 12) & 0b00111111); + uint8_t low = 0b10000000 | ((v2 >> 6) & 0b00111111); + uint8_t lowest = 0b10000000 | (v2 & 0b00111111); + str2.push_back((char)highest); + str2.push_back((char)high); + str2.push_back((char)low); + str2.push_back((char)lowest); + } + } + + + } + } + else + { + str2.push_back(str[i]); + } + } + else break; + } + else + { + str2.push_back(str[i]); + } + } + tokens.push_back(std::pair(str2,true)); + } + break; + case ',': + case '[': + case ']': + case '(': + case ')': + case '{': + case '}': + case ':': + flush(); + tokens.push_back(std::pair(str.substr(i,1),false)); + break; + case ' ': + case '\n': + case '\r': + case '\t': + flush(); + break; + default: + buff.push_back(str[i]); + break; + + } + } + + flush(); + if(tokens.size() == 0) return JUndefined(); + + size_t tokenIndex=0; + + std::function()> pop_token=[&tokens,&tokenIndex]()->std::pair { + if(tokenIndex >= tokens.size()) throw TextException("Json tokens out of bounds."); + return tokens[tokenIndex++]; + }; + + std::function read_token; + read_token= [&pop_token,&read_token,&tokenIndex,&tokens]()->JToken{ + auto tkn = pop_token(); + if(tkn.second) + { + return tkn.first; + } + else + { + if(tkn.first == "{") + { + + JObject obj; + while(true) + { + begin_dict: + tkn = pop_token(); + if(tkn.second) + { + std::string key = tkn.first; + tkn = pop_token(); + if(tkn.second || tkn.first != ":") + { + throw TextException("Invalid JSON key value seperator."); + } + + auto value = read_token(); + obj.SetValue(key,value); + } + else if(tkn.first == ",") + { + goto begin_dict; + } + else if(tkn.first == "}") + { + break; + } + else + { + + throw TextException("Invalid JSON object."); + } + } + return obj; + } + else if(tkn.first == "[") + { + JArray arr; + while(tokenIndex < tokens.size()) + { + auto tkn = tokens[tokenIndex]; + if(!tkn.second) + { + if(tkn.first == ",") tokenIndex++; + if(tkn.first == "]") {tokenIndex++; break;} + } + arr.Add(read_token()); + } + return arr; + } + else if(tkn.first == "true") + { + return true; + } + else if(tkn.first == "false") + { + return false; + } + else if(tkn.first == "null") + { + return nullptr; + } + else { + //number + if(tkn.first.find(".") != std::string::npos) + { + return std::stod(tkn.first); + } + else + { + return std::stoll(tkn.first); + } + } + + } + }; + + return read_token(); + } + std::string Json::Encode(JToken tkn, bool indent) + { + int64_t i64; + double f64; + std::string str; + bool b; + JArray ls; + JObject dict; + if(std::holds_alternative(tkn)) return "null"; + if(TryGetJToken(tkn,b)) return b ? "true" : "false"; + if(TryGetJToken(tkn,f64)) + { + auto str = std::to_string(f64); + + if(str.find(".") != std::string::npos) + { + size_t i; + for(i = str.size()-1; str[i] != '.'; i--) + { + if(str[i] != '0') + { + i++; + break; + } + } + return str.substr(0,i); + } + return str; + } + if(TryGetJToken(tkn,i64)) return std::to_string(i64); + if(TryGetJToken(tkn,str)) + { + std::string str2 = "\""; + for(auto c : str) + { + if(c == '\"') + { + str2.append("\\\""); + } + else if(c == '\n') + { + str2.append("\\n"); + } + else if(c == '\t') + { + str2.append("\\t"); + } + else if(c == '\r') + { + str2.append("\\r"); + } + else if(c == '/') + { + str2.append("\\/"); + } + else if(c == '\\') + { + str2.append("\\\\"); + } + else if(c == '\f') + { + str2.append("\\f"); + } + else if(c == '\b') + { + str2.append("\\b"); + } + else if((c >= 0 && c < 32) || c == 127 ) + { + str2.append("\\\\u00"); + uint8_t c2 = (uint8_t)c; + + str2.push_back(HttpUtils::NibbleToHex((c2 >> 4) & 0x0F)); + str2.push_back(HttpUtils::NibbleToHex(c2 & 0x0F)); + } + else + { + str2.push_back(c); + } + } + str2.push_back('\"'); + return str2; + } + if(TryGetJToken(tkn,dict)) + { + + std::string str="{"; + bool first=true; + for(auto item : dict) + { + if(!first) { + str.push_back(','); + } + if(indent) { + str.append("\n"); + str.append(tab(Encode(item.first,true) + ": " + Encode(item.second,true))); + } + else + { + str.append(Encode(item.first,false)+": "+Encode(item.second,false)); + } + first=false; + } + if(indent) str.append("\n"); + str.push_back('}'); + return str; + } + if(TryGetJToken(tkn,ls)) + { + std::string str="["; + bool first=true; + for(auto item : ls) + { + + if(!first) { + str.push_back(','); + } + if(indent) { + str.append("\n"); + str.append(tab(Encode(item,true))); + } + else + { + str.append(Encode(item,false)); + } + first=false; + } + if(indent) str.append("\n"); + str.push_back(']'); + return str; + } + return ""; + } + +} \ No newline at end of file