diff --git a/DqnJson.cpp b/DqnJson.cpp new file mode 100644 index 0000000..0b52086 --- /dev/null +++ b/DqnJson.cpp @@ -0,0 +1,117 @@ +void DqnJson_Test() +{ + char const json[] = + R"FOO( + { + "result": { + "cumulative_difficulty": 282912831023, + "difficulty": 18293, + "name": "Block", + "array_of_objects": [{ + "hash": "83abdc3f", + "time": 102981029381, + }, { + "hash": "12acf73d", + "time": 123761239789, + }], + "time": 3498573485, + "embed_object": { + "proof": "axcbde", + "signature": "l9382kjabmznmx129aslzejs" + } + "bits": [1, 0, 1, 1, 0, 1, 0], + "hex": ["AF", "BE", "0C", "FF"], + "extra": [123], + "serialise": [], + }, + } + )FOO"; + + DqnJson result = DqnJson_Get(DqnSlice(json, DQN_ARRAY_COUNT(json)), DQN_SLICE("result")); + DqnJson cumulativeDifficulty = DqnJson_Get(result, DQN_SLICE("cumulative_difficulty")); + DqnJson difficulty = DqnJson_Get(result, DQN_SLICE("difficulty")); + DqnJson name = DqnJson_Get(result, DQN_SLICE("name")); + DqnJson arrayOfObjects = DqnJson_Get(result, DQN_SLICE("array_of_objects")); + DqnJson time = DqnJson_Get(result, DQN_SLICE("time")); + DqnJson embedObject = DqnJson_Get(result, DQN_SLICE("embed_object")); + DqnJson bits = DqnJson_Get(result, DQN_SLICE("bits")); + DqnJson hex = DqnJson_Get(result, DQN_SLICE("hex")); + DqnJson extra = DqnJson_Get(result, DQN_SLICE("extra")); + DqnJson serialise = DqnJson_Get(result, DQN_SLICE("serialise")); + + DQN_ASSERT(DQN_SLICE_CMP(cumulativeDifficulty.value, DQN_SLICE("282912831023"), Dqn::IgnoreCase::No)); + DQN_ASSERT(DQN_SLICE_CMP(difficulty.value, DQN_SLICE("18293"), Dqn::IgnoreCase::No)); + DQN_ASSERT(DQN_SLICE_CMP(name.value, DQN_SLICE("\"Block\""), Dqn::IgnoreCase::No)); + + { + DQN_ASSERT(arrayOfObjects.IsArray() && arrayOfObjects.numEntries == 2); + isize count = 0; + while(DqnJson it = DqnJson_GetNextArrayItem(&arrayOfObjects)) + { + DqnJson hash = DqnJson_Get(it, DQN_SLICE("hash")); + DqnJson time2 = DqnJson_Get(it, DQN_SLICE("time")); + if (count == 0) + { + DQN_ASSERT(DQN_SLICE_CMP(hash.value, DQN_SLICE("\"83abdc3f\""), Dqn::IgnoreCase::No)); + DQN_ASSERT(DQN_SLICE_CMP(time2.value, DQN_SLICE("102981029381"), Dqn::IgnoreCase::No)); + } + else + { + DQN_ASSERT(DQN_SLICE_CMP(hash.value, DQN_SLICE("\"12acf73d\""), Dqn::IgnoreCase::No)); + DQN_ASSERT(DQN_SLICE_CMP(time2.value, DQN_SLICE("123761239789"), Dqn::IgnoreCase::No)); + } + ++count; + } + + } + + { + DqnJson proof = DqnJson_Get(embedObject, DQN_SLICE("proof")); + DqnJson signature = DqnJson_Get(embedObject, DQN_SLICE("signature")); + DQN_ASSERT(DQN_SLICE_CMP(proof.value, DQN_SLICE("\"axcbde\""), Dqn::IgnoreCase::No)); + DQN_ASSERT(DQN_SLICE_CMP(signature.value, DQN_SLICE("\"l9382kjabmznmx129aslzejs\""), Dqn::IgnoreCase::No)); + } + + DQN_ASSERT(DQN_SLICE_CMP(time.value, DQN_SLICE("3498573485"), Dqn::IgnoreCase::No)); + + { + DQN_ASSERT(bits.IsArray() && bits.numEntries == 7); + DqnJson bitsArray[7]; + isize bitsIndex = 0; + + while(DqnJson it = DqnJson_GetNextArrayItem(&bits)) + bitsArray[bitsIndex++] = it; + + DQN_ASSERT(bitsIndex == DQN_ARRAY_COUNT(bitsArray)); + DQN_ASSERT(DQN_SLICE_CMP(bitsArray[0].value, DQN_SLICE("1"), Dqn::IgnoreCase::No)); + DQN_ASSERT(DQN_SLICE_CMP(bitsArray[1].value, DQN_SLICE("0"), Dqn::IgnoreCase::No)); + DQN_ASSERT(DQN_SLICE_CMP(bitsArray[2].value, DQN_SLICE("1"), Dqn::IgnoreCase::No)); + DQN_ASSERT(DQN_SLICE_CMP(bitsArray[3].value, DQN_SLICE("1"), Dqn::IgnoreCase::No)); + DQN_ASSERT(DQN_SLICE_CMP(bitsArray[4].value, DQN_SLICE("0"), Dqn::IgnoreCase::No)); + DQN_ASSERT(DQN_SLICE_CMP(bitsArray[5].value, DQN_SLICE("1"), Dqn::IgnoreCase::No)); + DQN_ASSERT(DQN_SLICE_CMP(bitsArray[6].value, DQN_SLICE("0"), Dqn::IgnoreCase::No)); + } + + { + DQN_ASSERT(hex.IsArray() && hex.numEntries == 4); + DqnJson hexArray[4]; + isize hexIndex = 0; + + while(DqnJson it = DqnJson_GetNextArrayItem(&hex)) + hexArray[hexIndex++] = it; + + DQN_ASSERT(hexIndex == DQN_ARRAY_COUNT(hexArray)); + DQN_ASSERT(DQN_SLICE_CMP(hexArray[0].value, DQN_SLICE("\"AF\""), Dqn::IgnoreCase::No)); + DQN_ASSERT(DQN_SLICE_CMP(hexArray[1].value, DQN_SLICE("\"BE\""), Dqn::IgnoreCase::No)); + DQN_ASSERT(DQN_SLICE_CMP(hexArray[2].value, DQN_SLICE("\"0C\""), Dqn::IgnoreCase::No)); + DQN_ASSERT(DQN_SLICE_CMP(hexArray[3].value, DQN_SLICE("\"FF\""), Dqn::IgnoreCase::No)); + } + + { + DQN_ASSERT(extra.IsArray() && extra.numEntries == 1); + while(DqnJson it = DqnJson_GetNextArrayItem(&extra)) + { + DQN_ASSERT(DQN_SLICE_CMP(it.value, DQN_SLICE("123"), Dqn::IgnoreCase::No)); + } + } +} diff --git a/DqnUnitTest.cpp b/DqnUnitTest.cpp index 22cad9b..e33073e 100644 --- a/DqnUnitTest.cpp +++ b/DqnUnitTest.cpp @@ -139,6 +139,7 @@ void LogHeader(char const *funcName) #include "DqnFixedString.cpp" #include "DqnOS.cpp" +#include "DqnJson.cpp" void HandmadeMathVerifyMat4(DqnMat4 dqnMat, hmm_mat4 hmmMat) { @@ -2897,6 +2898,7 @@ int main(void) Dqn_BSearch_Test(); DqnMemSet_Test(); DqnFixedString_Test(); + DqnJson_Test(); #ifdef DQN_PLATFORM_HEADER DqnOS_Test(); diff --git a/dqn.h b/dqn.h index 15cbce2..dbce9c4 100644 --- a/dqn.h +++ b/dqn.h @@ -2825,15 +2825,17 @@ struct DqnJson enum struct Type { Object, - Array, + ArrayOfObjects, + ArrayOfPrimitives, }; Type type; DqnSlice value; i32 numEntries; - operator bool () const { return (value.data != nullptr); } - i64 ToI64() const { return Dqn_StrToI64(value.data, value.len); } + operator bool () const { return (value.data != nullptr); } + bool IsArray() const { return (type == Type::ArrayOfObjects || type == Type::ArrayOfPrimitives); } + i64 ToI64() const { return Dqn_StrToI64(value.data, value.len); } }; // Zero allocation json finder. Returns the data of the value. @@ -6802,17 +6804,69 @@ DQN_FILE_SCOPE i64 Dqn_BSearch(i64 *array, i64 size, i64 find, } } +// TODO(doyle): This should maybe be a tokenizer ... DQN_FILE_SCOPE DqnJson DqnJson_Get(char const *buf, i32 bufLen, char const *findProperty, i32 findPropertyLen) { DqnJson result = {}; + char const *tmp = DqnChar_SkipWhitespace(buf); + bufLen = static_cast((buf + bufLen) - tmp); + buf = tmp; + + if (buf[0] == '{' || buf[1] == '[') + { + buf++; + bufLen--; + } TryNext: - char *locate = DqnStr_GetFirstOccurence(buf, bufLen, findProperty, findPropertyLen); + char const *locate = nullptr; + for (i32 indexIntoBuf = 0; indexIntoBuf < bufLen; ++indexIntoBuf) + { + i32 remainingLenInSrcStr = bufLen - indexIntoBuf; + if (remainingLenInSrcStr < findPropertyLen) break; + + char const *bufSubStr = buf + indexIntoBuf; + if (bufSubStr[0] == '{' || bufSubStr[0] == '[') + { + int bracketCount = 0; + int braceCount = 0; + int *searchCharCount = nullptr; + if (bufSubStr[0] == '[') + { + bracketCount++; + searchCharCount = &bracketCount; + } + else + { + braceCount++; + searchCharCount = &braceCount; + } + + for (++indexIntoBuf; (*searchCharCount) != 0; ++indexIntoBuf) + { + bufSubStr = buf + indexIntoBuf; + if (!bufSubStr[0]) + return result; + + if (bufSubStr[0] == '{') ++braceCount; + else if (bufSubStr[0] == '}') --braceCount; + else if (bufSubStr[0] == '[') ++bracketCount; + else if (bufSubStr[0] == ']') --bracketCount; + else continue; + } + } + + if (DqnStr_Cmp(bufSubStr, findProperty, findPropertyLen, Dqn::IgnoreCase::No) == 0) + { + locate = buf + indexIntoBuf; + break; + } + } if (!locate) return result; // NOTE: if find property is '{' we are looking for an object in array or the global scope etc // which doesn't have a specific property name - char *startOfValue = locate; + char const *startOfValue = locate; char const *bufPtr = startOfValue; if (findProperty[0] != '{' && findProperty[0] != '[') { @@ -6844,11 +6898,19 @@ TryNext: startOfValue++; i32 *searchCharCount = nullptr; - if (bufPtr[0] == '[') + if (*bufPtr++ == '[') { bracketCount++; - result.type = DqnJson::Type::Array; searchCharCount = &bracketCount; + + while(bufPtr[0] != '{' && bufPtr[0] != '[' && bufPtr[0] != '"' && !DqnChar_IsAlphaNum(bufPtr[0]) && !bufPtr[0]) + bufPtr++; + + if (!bufPtr[0]) + return result; + + const b32 arrayOfPrimitives = (DqnChar_IsAlphaNum(bufPtr[0]) || bufPtr[0] == '"'); + result.type = (arrayOfPrimitives) ? DqnJson::Type::ArrayOfPrimitives : DqnJson::Type::ArrayOfObjects; } else { @@ -6857,24 +6919,43 @@ TryNext: searchCharCount = &braceCount; } - result.numEntries = 0; - for (bufPtr++; (*searchCharCount) != 0; bufPtr++) + if (result.type == DqnJson::Type::ArrayOfPrimitives) { - bool countsChanged = true; - if (!bufPtr[0]) - return result; - - if (bufPtr[0] == '{') braceCount++; - else if (bufPtr[0] == '}') braceCount--; - else if (bufPtr[0] == '[') bracketCount++; - else if (bufPtr[0] == ']') bracketCount--; - else countsChanged = false; - - if (countsChanged) + for (result.numEntries = 1;;) { - if (result.type == DqnJson::Type::Array) + while(bufPtr[0] && (bufPtr[0] != ',' && bufPtr[0] != ']')) + bufPtr++; + + if (bufPtr[0] == ',') { - if (braceCount == 0 && bracketCount == 1) + result.numEntries++; + bufPtr++; + continue; + } + + if (!bufPtr[0]) + return result; + + if (bufPtr[0] == ']') + break; + } + } + else + { + for (; (*searchCharCount) != 0; ++bufPtr) + { + if (!bufPtr[0]) + return result; + + if (bufPtr[0] == '{') ++braceCount; + else if (bufPtr[0] == '}') --braceCount; + else if (bufPtr[0] == '[') ++bracketCount; + else if (bufPtr[0] == ']') --bracketCount; + else continue; + + if (result.type == DqnJson::Type::ArrayOfObjects) + { + if (braceCount == 0 && bracketCount == 1) { result.numEntries++; } @@ -6887,9 +6968,10 @@ TryNext: } } } + // Don't include the open and closing braces/brackets. + bufPtr--; } - // Don't include the open and closing braces/brackets. - bufPtr--; + } else if (bufPtr[0] == '"' || DqnChar_IsAlphaNum(bufPtr[0])) { @@ -6906,7 +6988,7 @@ TryNext: return result; } - result.value.data = startOfValue; + result.value.data = (char *)startOfValue; result.value.len = static_cast(bufPtr - result.value.data); result.value.data = DqnChar_TrimWhitespaceAround(result.value.data, result.value.len, &result.value.len); return result; @@ -6945,25 +7027,49 @@ DQN_FILE_SCOPE DqnJson DqnJson_Get(DqnJson const input, DqnSlice con DQN_FILE_SCOPE DqnJson DqnJson_GetNextArrayItem(DqnJson *iterator) { DqnJson result = {}; - if (iterator->type != DqnJson::Type::Array || iterator->numEntries <= 0) + if (!iterator->IsArray() || iterator->numEntries <= 0) return result; - result = DqnJson_Get(iterator->value, DQN_SLICE("{")); - if (result) + if (iterator->type == DqnJson::Type::ArrayOfObjects) { - char const *end = iterator->value.data + iterator->value.len; - iterator->value.data = result.value.data + result.value.len; - iterator->numEntries--; + if (result = DqnJson_Get(iterator->value, DQN_SLICE("{"))) + { + char const *end = iterator->value.data + iterator->value.len; + iterator->value.data = result.value.data + result.value.len; + --iterator->numEntries; - while (iterator->value.data[0] && *iterator->value.data++ != '}') - ; + while (iterator->value.data[0] && *iterator->value.data++ != '}') + ; - iterator->value.data = DqnChar_SkipWhitespace(iterator->value.data); - if (iterator->value.data[0] && iterator->value.data[0] == ',') - iterator->value.data++; + iterator->value.data = DqnChar_SkipWhitespace(iterator->value.data); + if (iterator->value.data[0] && iterator->value.data[0] == ',') + iterator->value.data++; - iterator->value.data = DqnChar_SkipWhitespace(iterator->value.data); - iterator->value.len = (iterator->value.data) ? static_cast(end - iterator->value.data) : 0; + iterator->value.data = DqnChar_SkipWhitespace(iterator->value.data); + iterator->value.len = (iterator->value.data) ? static_cast(end - iterator->value.data) : 0; + } + } + else + { + char const *end = iterator->value.data + iterator->value.len; + result.value.data = iterator->value.data; + --iterator->numEntries; + + if (iterator->numEntries == 0) + { + while (iterator->value.data[0] && iterator->value.data[0] != ']') + ++iterator->value.data; + } + else + { + while (iterator->value.data[0] && iterator->value.data[0] != ',') + ++iterator->value.data; + } + + + result.value.len = static_cast(iterator->value.data - result.value.data); + iterator->value.data = DqnChar_SkipWhitespace(++iterator->value.data); + iterator->value.len = (iterator->value.data) ? static_cast(end - iterator->value.data) : 0; } return result;