DotNetDupe 4.0.6
C++17/20 Implementation of the .NET Base Class Library (BCL)
Loading...
Searching...
No Matches
JsonElement.cpp
Go to the documentation of this file.
1#include "pch.h"
8#include <sstream>
9#include <iomanip>
10#include <stdexcept>
11#include <cctype>
12#include <string>
13
14namespace DotNetDupe {
15 namespace System {
16 namespace Text {
17 namespace Json {
18
19 class JsonElementImpl {
20 public:
22 bool bBoolValue = false;
23 double dNumValue = 0.0;
24 String sStrValue;
25 Collections::Generic::List<JsonElement> lstArray;
26 Collections::Generic::Dictionary<String, JsonElement> dictObject;
27
28 JsonElementImpl() = default;
29 JsonElementImpl(JsonValueKind kind) : eKind(kind) {}
30 };
31
32 static String EscapeString(const String& sInput) {
34 std::string sResult = "\"";
35 const char* pRaw = sInput.GetRawString();
36 while (*pRaw) {
37 char c = *pRaw;
38 switch (c) {
39 case '\"': sResult += "\\\""; break;
40 case '\\': sResult += "\\\\"; break;
41 case '\b': sResult += "\\b"; break;
42 case '\f': sResult += "\\f"; break;
43 case '\n': sResult += "\\n"; break;
44 case '\r': sResult += "\\r"; break;
45 case '\t': sResult += "\\t"; break;
46 default:
47 if (static_cast<unsigned char>(c) < 32) {
48 std::stringstream ss;
49 ss << "\\u" << std::setfill('0') << std::setw(4) << std::hex << static_cast<int>(c);
50 sResult += ss.str();
51 } else {
52 sResult += c;
53 }
54 break;
55 }
56 pRaw++;
57 }
58 sResult += "\"";
59 return String(sResult.c_str());
60 }
61
62 class JsonParser {
63 public:
64 static JsonElement Parse(const String& sJson) {
66 std::string s = sJson.GetRawString();
67 size_t index = 0;
68 SkipWhitespace(s, index);
69 JsonElement res = ParseValue(s, index);
70 SkipWhitespace(s, index);
71 if (index < s.length()) {
72 throw JsonException("Extra characters after JSON value");
73 }
74 return res;
75 }
76
77 private:
78 static void SkipWhitespace(const std::string& s, size_t& index) {
80 while (index < s.length() && (s[index] == ' ' || s[index] == '\t' || s[index] == '\n' || s[index] == '\r')) {
81 index++;
82 }
83 }
84
85 static JsonElement ParseValue(const std::string& s, size_t& index) {
87 SkipWhitespace(s, index);
88 if (index >= s.length()) {
89 throw JsonException("Unexpected end of JSON input");
90 }
91 char c = s[index];
92 if (c == '{') {
93 return ParseObject(s, index);
94 } else if (c == '[') {
95 return ParseArray(s, index);
96 } else if (c == '\"') {
97 return ParseString(s, index);
98 } else if (c == 't' || c == 'f') {
99 return ParseBool(s, index);
100 } else if (c == 'n') {
101 return ParseNull(s, index);
102 } else if (c == '-' || std::isdigit(static_cast<unsigned char>(c))) {
103 return ParseNumber(s, index);
104 } else {
105 throw JsonException((std::string("Unexpected character: ") + c).c_str());
106 }
107 }
108
109 static JsonElement ParseObject(const std::string& s, size_t& index) {
111 index++; // Skip '{'
112 JsonElement obj(JsonValueKind::Object);
113 SkipWhitespace(s, index);
114 if (index < s.length() && s[index] == '}') {
115 index++; // Empty object
116 return obj;
117 }
118 while (true) {
119 SkipWhitespace(s, index);
120 if (index >= s.length() || s[index] != '\"') {
121 throw JsonException("Expected string key in object");
122 }
123 JsonElement keyEl = ParseString(s, index);
124 String key = keyEl.GetString();
125 SkipWhitespace(s, index);
126 if (index >= s.length() || s[index] != ':') {
127 throw JsonException("Expected ':' after key in object");
128 }
129 index++; // Skip ':'
130 JsonElement val = ParseValue(s, index);
131 obj.SetProperty(key, val);
132 SkipWhitespace(s, index);
133 if (index < s.length() && s[index] == '}') {
134 index++;
135 break;
136 }
137 if (index >= s.length() || s[index] != ',') {
138 throw JsonException("Expected ',' or '}' in object");
139 }
140 index++; // Skip ','
141 }
142 return obj;
143 }
144
145 static JsonElement ParseArray(const std::string& s, size_t& index) {
147 index++; // Skip '['
148 JsonElement arr(JsonValueKind::Array);
149 SkipWhitespace(s, index);
150 if (index < s.length() && s[index] == ']') {
151 index++; // Empty array
152 return arr;
153 }
154 while (true) {
155 JsonElement val = ParseValue(s, index);
156 arr.AddArrayElement(val);
157 SkipWhitespace(s, index);
158 if (index < s.length() && s[index] == ']') {
159 index++;
160 break;
161 }
162 if (index >= s.length() || s[index] != ',') {
163 throw JsonException("Expected ',' or ']' in array");
164 }
165 index++; // Skip ','
166 }
167 return arr;
168 }
169
170 static void ParseUnicodeEscape(const std::string& s, size_t& index, std::string& res) {
172 if (index + 4 > s.length()) {
173 throw JsonException("Invalid unicode escape sequence");
174 }
175
176 std::string hexStr = s.substr(index, 4);
177 index += 4;
178 unsigned int codePoint = std::stoul(hexStr, nullptr, 16);
179
180 if (codePoint <= 0x7f) {
181 res += static_cast<char>(codePoint);
182 } else if (codePoint <= 0x7ff) {
183 res += static_cast<char>(0xc0 | ((codePoint >> 6) & 0x1f));
184 res += static_cast<char>(0x80 | (codePoint & 0x3f));
185 } else {
186 res += static_cast<char>(0xe0 | ((codePoint >> 12) & 0x0f));
187 res += static_cast<char>(0x80 | ((codePoint >> 6) & 0x3f));
188 res += static_cast<char>(0x80 | (codePoint & 0x3f));
189 }
190 }
191
192 static JsonElement ParseString(const std::string& s, size_t& index) {
194 index++; // Skip starting '\"'
195 std::string res;
196
197 while (index < s.length()) {
198 char c = s[index];
199 if (c == '\"') {
200 index++;
201 return JsonElement(String(res.c_str()));
202 }
203
204 if (c == '\\') {
205 if (index + 1 >= s.length()) {
206 throw JsonException("Unterminated escape sequence in string");
207 }
208 char escaped = s[index + 1];
209 index += 2;
210
211 switch (escaped) {
212 case '\"': res += '\"'; break;
213 case '\\': res += '\\'; break;
214 case '/': res += '/'; break;
215 case 'b': res += '\b'; break;
216 case 'f': res += '\f'; break;
217 case 'n': res += '\n'; break;
218 case 'r': res += '\r'; break;
219 case 't': res += '\t'; break;
220 case 'u': ParseUnicodeEscape(s, index, res); break;
221 default:
222 throw JsonException((std::string("Unknown escape sequence: \\") + escaped).c_str());
223 }
224 } else {
225 res += c;
226 index++;
227 }
228 }
229
230 throw JsonException("Unterminated string");
231 }
232
233 static JsonElement ParseBool(const std::string& s, size_t& index) {
235 if (s.compare(index, 4, "true") == 0) {
236 index += 4;
237 return JsonElement(true);
238 } else if (s.compare(index, 5, "false") == 0) {
239 index += 5;
240 return JsonElement(false);
241 }
242 throw JsonException("Expected boolean value");
243 }
244
245 static JsonElement ParseNull(const std::string& s, size_t& index) {
247 if (s.compare(index, 4, "null") == 0) {
248 index += 4;
249 return JsonElement(nullptr);
250 }
251 throw JsonException("Expected null value");
252 }
253
254 static JsonElement ParseNumber(const std::string& s, size_t& index) {
256 size_t start = index;
257 if (s[index] == '-') {
258 index++;
259 }
260 while (index < s.length() && (std::isdigit(static_cast<unsigned char>(s[index])) || s[index] == '.' || s[index] == 'e' || s[index] == 'E' || s[index] == '+' || s[index] == '-')) {
261 index++;
262 }
263 std::string numStr = s.substr(start, index - start);
264 try {
265 double val = std::stod(numStr);
266 return JsonElement(val);
267 } catch (const std::exception&) {
268 throw JsonException(("Invalid number format: " + numStr).c_str());
269 }
270 }
271 };
272
274 : m_pImpl(SmartPointer<JsonElementImpl>::New(JsonValueKind::Undefined)) {
276 }
277
281
283 : m_pImpl(SmartPointer<JsonElementImpl>::New()) {
285 if (objOther.m_pImpl) {
286 m_pImpl->eKind = objOther.m_pImpl->eKind;
287 m_pImpl->bBoolValue = objOther.m_pImpl->bBoolValue;
288 m_pImpl->dNumValue = objOther.m_pImpl->dNumValue;
289 m_pImpl->sStrValue = objOther.m_pImpl->sStrValue;
290
291 for (int i = 0; i < objOther.m_pImpl->lstArray.GetCount(); ++i) {
292 m_pImpl->lstArray.Add(objOther.m_pImpl->lstArray[i]);
293 }
294
295 auto keys = objOther.m_pImpl->dictObject.GetKeys();
296 for (int i = 0; i < keys.GetLength(); ++i) {
297 m_pImpl->dictObject.Add(keys[i], objOther.m_pImpl->dictObject[keys[i]]);
298 }
299 }
300 }
301
304 if (this != &objOther) {
306 if (objOther.m_pImpl) {
307 m_pImpl->eKind = objOther.m_pImpl->eKind;
308 m_pImpl->bBoolValue = objOther.m_pImpl->bBoolValue;
309 m_pImpl->dNumValue = objOther.m_pImpl->dNumValue;
310 m_pImpl->sStrValue = objOther.m_pImpl->sStrValue;
311
312 for (int i = 0; i < objOther.m_pImpl->lstArray.GetCount(); ++i) {
313 m_pImpl->lstArray.Add(objOther.m_pImpl->lstArray[i]);
314 }
315
316 auto keys = objOther.m_pImpl->dictObject.GetKeys();
317 for (int i = 0; i < keys.GetLength(); ++i) {
318 m_pImpl->dictObject.Add(keys[i], objOther.m_pImpl->dictObject[keys[i]]);
319 }
320 }
321 }
322 return *this;
323 }
324
326 : m_pImpl(std::move(objOther.m_pImpl)) {
328 }
329
332 if (this != &objOther) {
333 m_pImpl = std::move(objOther.m_pImpl);
334 }
335 return *this;
336 }
337
339 : m_pImpl(SmartPointer<JsonElementImpl>::New(eKind)) {
341 }
342
344 : m_pImpl(SmartPointer<JsonElementImpl>::New(bValue ? JsonValueKind::True : JsonValueKind::False)) {
346 m_pImpl->bBoolValue = bValue;
347 }
348
350 : m_pImpl(SmartPointer<JsonElementImpl>::New(JsonValueKind::Number)) {
352 m_pImpl->dNumValue = dValue;
353 }
354
356 : m_pImpl(SmartPointer<JsonElementImpl>::New(JsonValueKind::String)) {
358 m_pImpl->sStrValue = sValue;
359 }
360
362 : m_pImpl(SmartPointer<JsonElementImpl>::New(JsonValueKind::Null)) {
364 }
365
368 return m_pImpl ? m_pImpl->eKind : JsonValueKind::Undefined;
369 }
370
373 if (GetValueKind() == JsonValueKind::True) return true;
374 if (GetValueKind() == JsonValueKind::False) return false;
375 throw InvalidOperationException("JsonElement is not a boolean.");
376 }
377
378 double JsonElement::GetDouble() const {
380 if (GetValueKind() != JsonValueKind::Number) throw InvalidOperationException("JsonElement is not a number.");
381 return m_pImpl->dNumValue;
382 }
383
386 return static_cast<int>(GetDouble());
387 }
388
389 long long JsonElement::GetInt64() const {
391 return static_cast<long long>(GetDouble());
392 }
393
396 if (GetValueKind() != JsonValueKind::String) throw InvalidOperationException("JsonElement is not a string.");
397 return m_pImpl->sStrValue;
398 }
399
402 if (GetValueKind() != JsonValueKind::Array) throw InvalidOperationException("JsonElement is not an array.");
403 return m_pImpl->lstArray.GetCount();
404 }
405
408 if (GetValueKind() != JsonValueKind::Array) throw InvalidOperationException("JsonElement is not an array.");
409 return m_pImpl->lstArray[iIndex];
410 }
411
414 if (GetValueKind() != JsonValueKind::Array) throw InvalidOperationException("JsonElement is not an array.");
415 m_pImpl->lstArray.Add(objElement);
416 }
417
418 bool JsonElement::TryGetProperty(const String& sPropertyName, JsonElement& objValue) const {
420 if (GetValueKind() != JsonValueKind::Object) return false;
421 return m_pImpl->dictObject.TryGetValue(sPropertyName, objValue);
422 }
423
424 void JsonElement::SetProperty(const String& sPropertyName, const JsonElement& objValue) {
426 if (GetValueKind() != JsonValueKind::Object) throw InvalidOperationException("JsonElement is not an object.");
427 if (m_pImpl->dictObject.ContainsKey(sPropertyName)) {
428 m_pImpl->dictObject[sPropertyName] = objValue;
429 } else {
430 m_pImpl->dictObject.Add(sPropertyName, objValue);
431 }
432 }
433
436 if (GetValueKind() != JsonValueKind::Object) throw InvalidOperationException("JsonElement is not an object.");
437 return m_pImpl->dictObject.GetKeys();
438 }
439
442 std::string sRes = "[";
443 for (int i = 0; i < lstArray.GetCount(); ++i) {
444 if (i > 0) sRes += ",";
445 sRes += lstArray[i].ToString().GetRawString();
446 }
447 return String((sRes + "]").c_str());
448 }
449
452 std::string sRes = "{";
453 auto keys = dictObject.GetKeys();
454 for (int i = 0; i < keys.GetLength(); ++i) {
455 if (i > 0) sRes += ",";
456 sRes += EscapeString(keys[i]).GetRawString();
457 sRes += ":";
458 sRes += dictObject[keys[i]].ToString().GetRawString();
459 }
460 return String((sRes + "}").c_str());
461 }
462
463 static String FormatJsonNumber(double dVal) {
465 if (dVal == static_cast<long long>(dVal)) return String(std::to_string(static_cast<long long>(dVal)).c_str());
466 char buf[64];
467 snprintf(buf, sizeof(buf), "%g", dVal);
468 return String(buf);
469 }
470
473 if (!m_pImpl) return "null";
474 switch (m_pImpl->eKind) {
475 case JsonValueKind::Null: return "null";
476 case JsonValueKind::True: return "true";
477 case JsonValueKind::False: return "false";
478 case JsonValueKind::Number: return FormatJsonNumber(m_pImpl->dNumValue);
479 case JsonValueKind::String: return EscapeString(m_pImpl->sStrValue);
480 case JsonValueKind::Array: return FormatJsonArray(m_pImpl->lstArray);
481 case JsonValueKind::Object: return FormatJsonObject(m_pImpl->dictObject);
482 default: return "null";
483 }
484 }
485
488 return JsonParser::Parse(sJson);
489 }
490
491 }
492 }
493 }
494}
Defines the exception thrown when a method call is invalid for the object's current state.
Represents a specific JSON value within a JsonDocument.
Exception thrown when invalid JSON payload or token is encountered per RFC 8259.
Represents a strongly typed list of objects that can be accessed by index mirroring ....
Represents a mutable string of characters for efficient dynamic text composition.
Provides methods for creating, manipulating, searching, and sorting arrays, thereby serving as the ba...
Definition Array.h:29
Represents a collection of keys and values.
Definition Dictionary.h:66
Represents a strongly typed list of objects accessible by index.
Definition List.h:29
int GetCount() const
Gets the number of elements contained in the List.
Definition List.h:100
InvalidOperationException(const String &sMessage)
Initializes a new instance of the InvalidOperationException class with a specified error message.
A unified smart pointer that supports both unique and shared ownership semantics.
static SmartPointer< T > New()
Creates a new SmartPointer (default construction).
Represents text as a sequence of UTF-8 code units with culture-invariant operations.
Definition String.h:74
String()
Initializes a new instance of the String class to an empty string.
Definition String.cpp:64
const char * GetRawString() const
Definition String.cpp:230
String GetString() const
Gets the value of the element as a string.
int GetArrayLength() const
Gets the number of values contained within the current JSON array value.
int GetInt32() const
Gets the current JSON number as a 32-bit signed integer.
Array< String > GetPropertyNames() const
Gets an array containing the names of all properties on the current JSON object.
JsonValueKind GetValueKind() const
Gets the type of the current JSON value.
JsonElement & operator=(const JsonElement &objOther)
bool TryGetProperty(const String &sPropertyName, JsonElement &objValue) const
Looks for a property named propertyName in the current JSON object.
String ToString() const
Serializes the element into a JSON string representation.
JsonElement GetArrayElement(int iIndex) const
Gets the value at the specified index in the current JSON array.
JsonElement()
Initializes a new instance of the JsonElement class representing undefined.
void AddArrayElement(const JsonElement &objElement)
Adds an element to the current JSON array.
long long GetInt64() const
Gets the current JSON number as a 64-bit signed integer.
bool GetBoolean() const
Gets the value of the element as a Boolean.
~JsonElement()
Destructor releasing element resources.
static JsonElement Parse(const String &sJson)
Parses text representing a single JSON value into a JsonElement.
void SetProperty(const String &sPropertyName, const JsonElement &objValue)
Sets or replaces a property on the current JSON object.
double GetDouble() const
Gets the current JSON number as a double.
JsonException()
Initializes a new instance of the JsonException class with a default message.
static String FormatJsonNumber(double dVal)
static String FormatJsonObject(const Collections::Generic::Dictionary< String, JsonElement > &dictObject)
static String EscapeString(const String &sInput)
JsonValueKind
Specifies the data type of a JSON value.
Definition JsonElement.h:21
static String FormatJsonArray(const Collections::Generic::List< JsonElement > &lstArray)