Skip to content

Commit 6535e84

Browse files
committed
JSON: improve the infrastructure code (and experiment with ctre :))
1 parent 4cf6d51 commit 6535e84

File tree

6 files changed

+133
-55
lines changed

6 files changed

+133
-55
lines changed

core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ set (COMMON_FILES
114114
src/GTIN.cpp
115115
src/ImageView.h
116116
src/JSON.h
117+
src/JSON.cpp
117118
src/Matrix.h
118119
src/Point.h
119120
src/Quadrilateral.h

core/src/JSON.cpp

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2025 Axel Waggershauser
3+
*/
4+
// SPDX-License-Identifier: Apache-2.0
5+
6+
#include "JSON.h"
7+
8+
#include <algorithm>
9+
#include <string_view>
10+
11+
// This code is trying to find the value of a key-value pair in a string of those.
12+
// The input could be valid JSON, like '{"key": "val"}' or a stipped down version like
13+
// 'key:val'. This is also compatible with the string serialization of a python dictionary.
14+
// This could easily be done with the following regex (see below).
15+
// But using std::regex adds 140k+ to the binary size. ctre is _very_ nice to use and has
16+
// an even smaller binary footpint than the hand-roled code below! But I don't want to add
17+
// 5k lines of c++ code for that.
18+
19+
// #define ZXING_USE_CTRE
20+
#ifdef ZXING_USE_CTRE
21+
#pragma GCC diagnostic ignored "-Wundef" // see https://github.com/hanickadot/compile-time-regular-expressions/issues/340
22+
#include "ctre.hpp"
23+
#pragma GCC diagnostic error "-Wundef"
24+
25+
static constexpr auto PATTERN = ctll::fixed_string{R"(\"?([[:alpha:]][[:alnum:]]*)\"?\s*(?:[:]\s*\"?([[:alnum:]]+)\"?)?(?:,|$))"};
26+
#else
27+
#include "ZXAlgorithms.h"
28+
#endif
29+
30+
namespace ZXing {
31+
32+
inline bool CmpCI(unsigned char ch1, unsigned char ch2)
33+
{
34+
return std::tolower(ch1) == std::tolower(ch2);
35+
}
36+
37+
inline size_t FindCaseInsensitive(std::string_view haystack, std::string_view needle)
38+
{
39+
if (haystack.empty() || needle.empty())
40+
return std::string_view::npos;
41+
42+
auto it = std::search(haystack.begin(), haystack.end(), needle.begin(), needle.end(), CmpCI);
43+
44+
return it != haystack.end() ? it - haystack.begin() : std::string_view::npos;
45+
}
46+
47+
inline bool IsEqualCaseInsensitive(std::string_view str1, std::string_view str2)
48+
{
49+
return str1.size() == str2.size() && std::equal(str1.begin(), str1.end(), str2.begin(), CmpCI);
50+
}
51+
52+
std::string_view JsonGetStr(std::string_view json, std::string_view key)
53+
{
54+
#ifdef ZXING_USE_CTRE
55+
for (auto [ma, mk, mv] : ctre::search_all<PATTERN>(json))
56+
if (IsEqualCaseInsensitive(key, mk))
57+
return mv;
58+
59+
return {};
60+
#else
61+
auto &npos = std::string_view::npos;
62+
auto posKey = FindCaseInsensitive(json, key);
63+
if (posKey == npos || (posKey > 0 && !Contains("\", \t\n\r", json[posKey - 1])))
64+
return {};
65+
66+
auto posVal = json.find_first_not_of("\" \t", posKey + key.size());
67+
68+
if (posVal == npos || json[posVal] == ',')
69+
return {json.data() + posKey, 0}; // return non-null data to signal that we found the key
70+
71+
if (posVal >= json.size() - 1 || json[posVal++] != ':')
72+
return {};
73+
74+
posVal = json.find_first_not_of("\" \t\n\r", posVal);
75+
auto posEnd = json.find_first_of(",\" \t\n\r", posVal);
76+
if (posEnd == npos)
77+
posEnd = json.size();
78+
79+
return json.substr(posVal, posEnd - posVal);
80+
#endif
81+
}
82+
83+
bool JsonGetBool(std::string_view json, std::string_view key)
84+
{
85+
#ifdef ZXING_USE_CTRE
86+
for (auto [ma, mk, mv] : ctre::search_all<PATTERN>(json))
87+
if (IsEqualCaseInsensitive(key, mk))
88+
return mv.size() == 0 || Contains("1tT", *mv.data());
89+
90+
return false;
91+
#else
92+
auto val = JsonGetStr(json, key);
93+
94+
return val.data() && (val.size() == 0 || Contains("1tT", val.front()));
95+
#endif
96+
}
97+
98+
} // ZXing

core/src/JSON.h

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55

66
#pragma once
77

8+
#include "Error.h"
9+
10+
#include <charconv>
811
#include <cstring>
12+
#include <stdexcept>
913
#include <string>
10-
11-
#include "ZXAlgorithms.h"
14+
#include <string_view>
1215

1316
namespace ZXing {
1417

@@ -27,36 +30,11 @@ inline std::string JsonValue(std::string_view key, T val, int indent = 0)
2730
return JsonValue(key, std::to_string(val), indent);
2831
}
2932

30-
inline bool JsonGetBool(std::string_view json, std::string_view key)
31-
{
32-
auto posKey = json.find(key);
33-
if (posKey == std::string_view::npos || key.empty())
34-
return false;
35-
36-
auto posSep = posKey + key.size();
37-
if (posSep == json.size() || json[posSep] == ',')
38-
return true;
39-
40-
if (json[posSep] != ':')
41-
return false;
42-
43-
return posSep < json.size() - 1 && Contains("1tT", json[posSep + 1]);
44-
}
45-
46-
inline std::string_view JsonGetStr(std::string_view json, std::string_view key)
47-
{
48-
auto posKey = json.find(key);
49-
if (posKey == std::string_view::npos)
50-
return {};
51-
52-
auto posSep = posKey + key.size();
53-
if (posSep + 1 >= json.size() || json[posSep] != ':')
54-
return {};
55-
56-
return json.substr(posSep + 1, json.find_first_of(',', posSep + 1) - posSep - 1);
57-
}
33+
bool JsonGetBool(std::string_view json, std::string_view key);
34+
std::string_view JsonGetStr(std::string_view json, std::string_view key);
5835

59-
template<typename T> T JsonGet(std::string_view json, std::string_view key)
36+
template <typename T>
37+
inline T JsonGet(std::string_view json, std::string_view key)
6038
{
6139
if constexpr (std::is_same_v<bool, T>)
6240
return JsonGetBool(json, key);
@@ -66,4 +44,14 @@ template<typename T> T JsonGet(std::string_view json, std::string_view key)
6644
throw UnsupportedError("internal error");
6745
}
6846

47+
inline int svtoi(std::string_view sv)
48+
{
49+
int val = 0;
50+
auto [ptr, ec] = std::from_chars(sv.data(), sv.data() + sv.size(), val);
51+
if (ec != std::errc() || ptr != sv.data() + sv.size())
52+
throw std::invalid_argument("failed to parse int from '" + std::string(sv) + "'");
53+
54+
return val;
55+
}
56+
6957
} // ZXing

core/src/WriteBarcode.cpp

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ struct CreatorOptions::Data
6868
ZX_RO_PROPERTY(bool, gs1);
6969
ZX_RO_PROPERTY(bool, stacked);
7070
ZX_RO_PROPERTY(std::string_view, version);
71-
ZX_RO_PROPERTY(std::string_view, datamask);
71+
ZX_RO_PROPERTY(std::string_view, dataMask);
7272

7373
#undef ZX_PROPERTY
7474

@@ -344,18 +344,6 @@ static std::string ECLevelZint2ZXing(const zint_symbol* zint)
344344
return {};
345345
}
346346

347-
static std::string NormalizedOptionsString(std::string_view sv)
348-
{
349-
std::string str(sv);
350-
std::transform(str.begin(), str.end(), str.begin(), [](char c) { return (char)std::tolower(c); });
351-
#ifdef __cpp_lib_erase_if
352-
std::erase_if(str, [](char c) { return Contains("\n \"", c); });
353-
#else
354-
str.erase(std::remove_if(str.begin(), str.end(), [](char c) { return Contains("\n \"", c); }), str.end());
355-
#endif
356-
return str;
357-
}
358-
359347
zint_symbol* CreatorOptions::zint() const
360348
{
361349
auto& zint = d->zint;
@@ -366,7 +354,6 @@ zint_symbol* CreatorOptions::zint() const
366354
#endif
367355
zint.reset(ZBarcode_Create());
368356

369-
d->options = NormalizedOptionsString(options());
370357
#ifdef PRINT_DEBUG
371358
printf("options: %s\n", options().c_str());
372359
#endif
@@ -390,15 +377,10 @@ zint_symbol* CreatorOptions::zint() const
390377
zint->option_1 = ParseECLevel(zint->symbology, ecLevel());
391378

392379
if (auto str = version(); str.size() && !IsLinearBarcode(format()))
393-
if (std::from_chars(str.data(), str.data() + str.size(), zint->option_2).ec != std::errc())
394-
throw std::invalid_argument("failed to parse version number from options");
395-
396-
if (auto str = datamask(); str.size() && (BarcodeFormat::QRCode | BarcodeFormat::MicroQRCode).testFlag(format())) {
397-
int val = 0;
398-
if (std::from_chars(str.data(), str.data() + str.size(), val).ec != std::errc())
399-
throw std::invalid_argument("failed to parse version number from options");
400-
zint->option_3 = (zint->option_3 & 0xFF) | (val + 1) << 8;
401-
}
380+
zint->option_2 = svtoi(str);
381+
382+
if (auto str = dataMask(); str.size() && (BarcodeFormat::QRCode | BarcodeFormat::MicroQRCode).testFlag(format()))
383+
zint->option_3 = (zint->option_3 & 0xFF) | (svtoi(str) + 1) << 8;
402384
}
403385

404386
return zint.get();

core/src/WriteBarcode.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class CreatorOptions
5353
ZX_RO_PROPERTY(bool, gs1);
5454
ZX_RO_PROPERTY(bool, stacked);
5555
ZX_RO_PROPERTY(std::string_view, version);
56-
ZX_RO_PROPERTY(std::string_view, datamask);
56+
ZX_RO_PROPERTY(std::string_view, dataMask);
5757
#undef ZX_RO_PROPERTY
5858
};
5959

test/unit/JSONTest.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,19 @@ TEST(JSONTest, GetBool)
2222
EXPECT_TRUE(JsonGetBool("key:true", "key"));
2323
EXPECT_TRUE(JsonGetBool("key:1", "key"));
2424
EXPECT_TRUE(JsonGetBool("key,other", "key"));
25+
EXPECT_TRUE(JsonGetBool("key", "KEY"));
26+
EXPECT_TRUE(JsonGetBool("key1", "key1"));
2527

2628
EXPECT_FALSE(JsonGetBool("", ""));
2729
EXPECT_FALSE(JsonGetBool("", "key"));
2830
EXPECT_FALSE(JsonGetBool("key:", "key"));
2931
EXPECT_FALSE(JsonGetBool("key:false", "key"));
3032
EXPECT_FALSE(JsonGetBool("key:0", "key"));
3133
EXPECT_FALSE(JsonGetBool("keys", "key"));
34+
EXPECT_FALSE(JsonGetBool("thekey", "key"));
35+
36+
EXPECT_TRUE(JsonGetBool("key , other", "key"));
37+
EXPECT_TRUE(JsonGetBool("\"key\": \"true\"", "key"));
3238
}
3339

3440
TEST(JSONTest, GetStr)
@@ -40,4 +46,7 @@ TEST(JSONTest, GetStr)
4046
EXPECT_EQ(JsonGetStr("key:abc", "key"), "abc");
4147
EXPECT_EQ(JsonGetStr("key:abc,", "key"), "abc");
4248
EXPECT_EQ(JsonGetStr("key:abc,key2", "key"), "abc");
49+
EXPECT_EQ(JsonGetStr("key:abc", "KEY"), "abc");
50+
51+
EXPECT_EQ(JsonGetStr("\"key\": \"abc\"", "KEY"), "abc");
4352
}

0 commit comments

Comments
 (0)