/
RaftJson.h
1130 lines (1056 loc) · 46.6 KB
/
RaftJson.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// RaftJson - JSON parser and field extractor
//
// Many of the methods here support a pDataPath parameter. This uses a syntax like a much simplified XPath:
// [0] returns the 0th element of an array
// / is a separator of nodes
//
// Rob Dobson 2017-2023
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma once
#include <vector>
#include <string>
#include <stdlib.h>
#include <limits.h>
#include "RaftJsonIF.h"
// Accommodate usage standalone (outside RaftCore)
#if __has_include("RaftArduino.h")
#include "Logger.h"
// #define DEBUG_JSON_BY_SPECIFIC_PATH ""
// #define DEBUG_JSON_BY_SPECIFIC_PATH_PART ""
// #define DEBUG_JSON_LOCATE_ELEMENT_BOUNDS
// #define DEBUG_EXTRACT_NAME_VALUES
// #define DEBUG_CHAINED_RAFT_JSON
#ifdef ESP_PLATFORM
#include "SpiramAwareAllocator.h"
#define USE_RAFT_SPIRAM_AWARE_ALLOCATOR
#endif
#endif
// Treat strings as numbers in JSON documents
// Change this value to false if you want to treat strings as strings in ALL JSON documents
#define RAFT_JSON_TREAT_STRINGS_AS_NUMBERS true
// RaftJson class
class RaftJson : public RaftJsonIF
{
public:
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Construct an empty RaftJson object
RaftJson()
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Construct a new RaftJson object
/// @param pJsonStr
/// @param makeCopy when false the string pointer must remain valid for the lifetime of this object
/// @param pChainedRaftJson a chained RaftJson object to use if the key is not found in this object
/// @note The makeCopy option is provided to avoid copying strings in flash memory - please don't use it in other cases
RaftJson(const char* pJsonStr, bool makeCopy = true, RaftJsonIF* pChainedRaftJson = nullptr)
{
// Store the source string
setSourceStr(pJsonStr, makeCopy, strlen(pJsonStr));
_pChainedRaftJson = pChainedRaftJson;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Construct a new RaftJson object from Arduino String
/// @param pJsonStr
/// @param pChainedRaftJson a chained RaftJson object to use if the key is not found in this object
/// @note makes a copy
RaftJson(const String& jsonStr, RaftJsonIF* pChainedRaftJson = nullptr)
{
// Store the source string
setSourceStr(jsonStr.c_str(), true, jsonStr.length());
_pChainedRaftJson = pChainedRaftJson;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Construct a new RaftJson object from std::string
/// @param pJsonStr
/// @param pChainedRaftJson a chained RaftJson object to use if the key is not found in this object
/// @note makes a copy
RaftJson(const std::string& jsonStr, RaftJsonIF* pChainedRaftJson = nullptr)
{
// Store the source string
setSourceStr(jsonStr.c_str(), true, jsonStr.length());
_pChainedRaftJson = pChainedRaftJson;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Destructor
virtual ~RaftJson()
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Assignment from string types
/// @param pJsonStr
/// @note makes a copy
RaftJson& operator=(const char* pJsonStr)
{
// Store the source string
setSourceStr(pJsonStr, true, strlen(pJsonStr));
return *this;
}
RaftJson& operator=(const String& jsonStr)
{
// Store the source string
setSourceStr(jsonStr.c_str(), true, jsonStr.length());
return *this;
}
RaftJson& operator=(const std::string& jsonStr)
{
// Store the source string
setSourceStr(jsonStr.c_str(), true, jsonStr.length());
return *this;
}
virtual bool setJsonDoc(const char* pJsonDoc) override
{
// Store the source string
setSourceStr(pJsonDoc, true, strlen(pJsonDoc));
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Get string value using the member variable JSON document
/// @param pDataPath the path of the required variable in XPath-like syntax (e.g. "a/b/c[0]/d")
/// @param defaultValue the default value to return if the variable is not found
/// @return the value of the variable or the default value if not found
virtual String getString(const char* pDataPath, const char* defaultValue) const override
{
return getStringIm(_pSourceStr, pDataPath, defaultValue, _pChainedRaftJson);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Get double value using the member variable JSON document
/// @param pDataPath the path of the required variable in XPath-like syntax (e.g. "a/b/c[0]/d")
/// @param defaultValue the default value to return if the variable is not found
/// @return the value of the variable or the default value if not found
virtual double getDouble(const char* pDataPath, double defaultValue) const override
{
return getDoubleIm(_pSourceStr, pDataPath, defaultValue, _pChainedRaftJson);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Get long value using the member variable JSON document
/// @param pDataPath the path of the required variable in XPath-like syntax (e.g. "a/b/c[0]/d")
/// @param defaultValue the default value to return if the variable is not found
/// @return the value of the variable or the default value if not found
virtual long getLong(const char* pDataPath, long defaultValue) const override
{
return getLongIm(_pSourceStr, pDataPath, defaultValue, _pChainedRaftJson);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Get boolean value using the member variable JSON document
/// @param pDataPath the path of the required variable in XPath-like syntax (e.g. "a/b/c[0]/d")
/// @param defaultValue the default value to return if the variable is not found
/// @return the value of the variable or the default value if not found
virtual bool getBool(const char* pDataPath, bool defaultValue) const override
{
return getBoolIm(_pSourceStr, pDataPath, defaultValue, _pChainedRaftJson);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Get array elements using the member variable JSON document
/// @param pDataPath the path of the required variable in XPath-like syntax (e.g. "a/b/c[0]/d")
/// @param strList a vector which is filled with the array elements
/// @return true if the array was found
virtual bool getArrayElems(const char *pDataPath, std::vector<String>& strList) const override
{
return getArrayElemsIm(_pSourceStr, pDataPath, strList, _pChainedRaftJson);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Get keys of an object using the member variable JSON document
/// @param pDataPath the path of the required variable in XPath-like syntax (e.g. "a/b/c[0]/d")
/// @param keysVector a vector which is filled with the keys
/// @return true if the object was found
virtual bool getKeys(const char *pDataPath, std::vector<String>& keysVector) const override
{
return getKeysIm(_pSourceStr, pDataPath, keysVector, _pChainedRaftJson);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Check if the member JSON document contains the key specified by the path
/// @param pDataPath the path of the required variable in XPath-like syntax (e.g. "a/b/c[0]/d")
/// @return true if the key was found
virtual bool contains(const char* pDataPath) const override
{
int arrayLen = 0;
RaftJsonType elemType = getTypeIm(_pSourceStr, pDataPath, arrayLen, _pChainedRaftJson);
return elemType != RAFT_JSON_UNDEFINED;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Get type of element from a JSON document at the specified path
/// @param pDataPath the path of the required object in XPath-like syntax (e.g. "a/b/c[0]/d")
/// @param arrayLen the length of the array if the element is an array
/// @return the type of the element
virtual RaftJsonType getType(const char* pDataPath, int &arrayLen) const override
{
return getTypeIm(_pSourceStr, pDataPath, arrayLen, _pChainedRaftJson);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Static methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief gets a string from a JSON document (immediate - i.e. static function, doc passed in)
/// @param pJsonDoc the JSON document (string)
/// @param pDataPath the path of the required variable in XPath-like syntax (e.g. "a/b/c[0]/d")
/// @param defaultValue the default value to return if the variable is not found
/// @param pChainedRaftJson a chained RaftJson object to use if the key is not found in this object
/// @return the value of the variable or the default value if not found
static String getStringIm(const char* pJsonDoc,
const char* pDataPath, const char* defaultValue,
const RaftJsonIF* pChainedRaftJson = nullptr)
{
// Locate the element
const char* pJsonDocPos = pJsonDoc;
pJsonDocPos = RaftJson::locateElementByPath(pJsonDocPos, pDataPath, pChainedRaftJson);
// Check if we found the element
if (!pJsonDocPos)
return defaultValue;
const char* pElemStart = nullptr;
const char* pElemEnd = nullptr;
if (!RaftJson::locateElementBounds(pJsonDocPos, pElemStart, pElemEnd))
return defaultValue;
// Skip quotes
if (*pElemStart == '"')
pElemStart++;
String outStr = String(pElemStart, pElemEnd - pElemStart);
unescapeString(outStr);
return outStr;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief gets a double from a JSON document (immediate - i.e. static function, doc passed in)
/// @param pJsonDoc the JSON document (string)
/// @param pDataPath the path of the required variable in XPath-like syntax (e.g. "a/b/c[0]/d")
/// @param defaultValue the default value to return if the variable is not found
/// @param pChainedRaftJson a chained RaftJson object to use if the key is not found in this object
/// @return the value of the variable or the default value if not found
static double getDoubleIm(const char* pJsonDoc,
const char* pDataPath, double defaultValue,
const RaftJsonIF* pChainedRaftJson = nullptr)
{
// Locate the element
const char* pJsonDocPos = pJsonDoc;
pJsonDocPos = RaftJson::locateElementByPath(pJsonDocPos, pDataPath, pChainedRaftJson);
// Check if we found the element
if (!pJsonDocPos)
return defaultValue;
// Check if it's a boolean or null
int retValue = 0;
if (RaftJson::isBooleanIm(pJsonDocPos, retValue))
return retValue;
if (RaftJson::isNullIm(pJsonDocPos))
return defaultValue;
// Check for a string value - if so skip quotes
if ((*pJsonDocPos == '"') && RAFT_JSON_TREAT_STRINGS_AS_NUMBERS)
pJsonDocPos++;
// Convert to double
return strtod(pJsonDocPos, NULL);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief gets a long from a JSON document (immediate - i.e. static function, doc passed in)
/// @param pJsonDoc the JSON document (string)
/// @param pDataPath the path of the required variable in XPath-like syntax (e.g. "a/b/c[0]/d")
/// @param defaultValue the default value to return if the variable is not found
/// @param pChainedRaftJson a chained RaftJson object to use if the key is not found in this object
/// @return the value of the variable or the default value if not found
static long getLongIm(const char* pJsonDoc,
const char* pDataPath, long defaultValue,
const RaftJsonIF* pChainedRaftJson = nullptr)
{
// Locate the element
const char* pJsonDocPos = pJsonDoc;
pJsonDocPos = RaftJson::locateElementByPath(pJsonDocPos, pDataPath, pChainedRaftJson);
// Check if we found the element
if (!pJsonDocPos)
return defaultValue;
// Check if it's a boolean or null
int retValue = 0;
if (RaftJson::isBooleanIm(pJsonDocPos, retValue))
return retValue;
if (RaftJson::isNullIm(pJsonDocPos))
return defaultValue;
// Check for a string value - if so skip quotes
if ((*pJsonDocPos == '"') && RAFT_JSON_TREAT_STRINGS_AS_NUMBERS)
pJsonDocPos++;
// Convert to long
return strtol(pJsonDocPos, NULL, 0);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief gets a boolean from a JSON document (immediate - i.e. static function, doc passed in)
/// @param pJsonDoc the JSON document (string)
/// @param pDataPath the path of the required variable in XPath-like syntax (e.g. "a/b/c[0]/d")
/// @param defaultValue the default value to return if the variable is not found
/// @param pChainedRaftJson a chained RaftJson object to use if the key is not found in this object
/// @return the value of the variable or the default value if not found
static bool getBoolIm(const char* pJsonDoc,
const char* pDataPath, bool defaultValue,
const RaftJsonIF* pChainedRaftJson = nullptr)
{
// Use long method to get value
return RaftJson::getLongIm(pJsonDoc, pDataPath, defaultValue, pChainedRaftJson) != 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief gets the elements of an array from the JSON (immediate - i.e. static function, doc passed in)
/// @param pJsonDoc the JSON document (string)
/// @param pDataPath the path of the required array in XPath-like syntax (e.g. "a/b/c[0]/d")
/// @param strList a vector which is filled with the array elements
/// @param pChainedRaftJson a chained RaftJson object to use if the key is not found in this object
/// @return true if the array was found
static bool getArrayElemsIm(const char* pJsonDoc,
const char *pDataPath, std::vector<String>& strList,
const RaftJsonIF* pChainedRaftJson = nullptr)
{
// Locate the element
const char* pJsonDocPos = pJsonDoc;
pJsonDocPos = RaftJson::locateElementByPath(pJsonDocPos, pDataPath, pChainedRaftJson);
// Check if we found the element
if (!pJsonDocPos)
return false;
// Check if it's an array
if (*pJsonDocPos != '[')
return false;
// Skip over array start
pJsonDocPos++;
// Iterate over array elements
while (*pJsonDocPos)
{
// Skip whitespace
while (*pJsonDocPos && (*pJsonDocPos <= ' '))
pJsonDocPos++;
// Check for end of array
if (*pJsonDocPos == ']')
return true;
// Locate element
const char* pElemStart = nullptr;
const char* pElemEnd = nullptr;
pJsonDocPos = RaftJson::locateElementBounds(pJsonDocPos, pElemStart, pElemEnd);
if (!pJsonDocPos)
return false;
// Skip quotes
if (*pElemStart == '"')
pElemStart++;
// Add to list
strList.push_back(String(pElemStart, pElemEnd - pElemStart));
}
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief gets the keys of an object from the JSON (immediate - i.e. static function, doc passed in)
/// @param pJsonDoc the JSON document (string)
/// @param pDataPath the path of the required object in XPath-like syntax (e.g. "a/b/c[0]/d")
/// @param keysVector a vector which is filled with the keys
/// @param pChainRaftJson a chained RaftJson object to use if the key is not found in this object
/// @return true if the object was found
static bool getKeysIm(const char* pJsonDoc,
const char *pDataPath, std::vector<String>& keysVector,
const RaftJsonIF* pChainedRaftJson = nullptr)
{
// Locate the element
const char* pJsonDocPos = pJsonDoc;
pJsonDocPos = RaftJson::locateElementByPath(pJsonDocPos, pDataPath, pChainedRaftJson);
// Check if we found the element
if (!pJsonDocPos)
return false;
// Check if it's an object
if (*pJsonDocPos != '{')
return false;
// Skip over object start
pJsonDocPos++;
// Iterate over object elements
while (*pJsonDocPos)
{
// Skip whitespace
while (*pJsonDocPos && (*pJsonDocPos <= ' '))
pJsonDocPos++;
// Check for end of object
if (*pJsonDocPos == '}')
return true;
// Locate key
const char* pKeyStart = nullptr;
const char* pKeyEnd = nullptr;
pJsonDocPos = locateStringElement(pJsonDocPos, pKeyStart, pKeyEnd, false);
if (!pJsonDocPos)
return false;
// Add to list
keysVector.push_back(String(pKeyStart, pKeyEnd - pKeyStart));
// Skip to end of element
const char* pElemEnd = nullptr;
const char* pElemStart = nullptr;
pJsonDocPos = RaftJson::locateElementBounds(pJsonDocPos, pElemStart, pElemEnd);
if (!pJsonDocPos)
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Check if element at the current position in a JSON document is a boolean value (static)
/// @param pJsonDocPos the current position in the JSON document
/// @param retValue the value of the boolean
/// @return true if the element is a boolean
static bool isBooleanIm(const char* pJsonDocPos, int &retValue)
{
if (!pJsonDocPos)
return false;
if (strncmp(pJsonDocPos, "true", 4) == 0)
{
retValue = 1;
return true;
}
if (strncmp(pJsonDocPos, "false", 5) == 0)
{
retValue = 0;
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Check if element at the current position in a JSON document is a null value (static)
/// @param pJsonDocPos the current position in the JSON document
/// @return true if the element is a null
static bool isNullIm(const char* pJsonDocPos)
{
if (!pJsonDocPos)
return false;
if (strncmp(pJsonDocPos, "null", 4) == 0)
{
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Get type of element from a JSON document at the specified path
/// @param pJsonDoc the JSON document (string)
/// @param pDataPath the path of the required object in XPath-like syntax (e.g. "a/b/c[0]/d")
/// @param arrayLen the length of the array if the element is an array
/// @param pChainedRaftJson a chained RaftJson object to use if the key is not found in this object
/// @return the type of the element
static RaftJsonType getTypeIm(const char* pJsonDoc,
const char* pDataPath, int &arrayLen,
const RaftJsonIF* pChainedRaftJson = nullptr)
{
// Locate the element
const char* pJsonDocPos = pJsonDoc;
pJsonDocPos = RaftJson::locateElementByPath(pJsonDocPos, pDataPath, pChainedRaftJson);
// Check if we found the element
if (!pJsonDocPos)
return RAFT_JSON_UNDEFINED;
// Check if it's an object
if (*pJsonDocPos == '{')
return RAFT_JSON_OBJECT;
// Check if it's an array
if (*pJsonDocPos == '[')
{
// Skip over array start
pJsonDocPos++;
// Iterate over array elements
arrayLen = 0;
while (*pJsonDocPos)
{
// Skip whitespace
while (*pJsonDocPos && (*pJsonDocPos <= ' '))
pJsonDocPos++;
// Check for end of array
if (*pJsonDocPos == ']')
return RAFT_JSON_ARRAY;
// Locate element
const char* pElemStart = nullptr;
const char* pElemEnd = nullptr;
pJsonDocPos = RaftJson::locateElementBounds(pJsonDocPos, pElemStart, pElemEnd);
if (!pJsonDocPos)
return RAFT_JSON_UNDEFINED;
// Count elements
arrayLen++;
}
return RAFT_JSON_ARRAY;
}
// Check if it's a string
if (*pJsonDocPos == '"')
return RAFT_JSON_STRING;
// Check if it's a boolean
int retValue = 0;
if (RaftJson::isBooleanIm(pJsonDocPos, retValue))
return RAFT_JSON_BOOLEAN;
// Check for null
if (RaftJson::isNullIm(pJsonDocPos))
return RAFT_JSON_NULL;
// Check if it's a number
if ((*pJsonDocPos >= '0') && (*pJsonDocPos <= '9'))
return RAFT_JSON_NUMBER;
// Must be undefined
return RAFT_JSON_UNDEFINED;
}
////////////////////////////////////////////////////////////////////////
// Name value pair handling methods
////////////////////////////////////////////////////////////////////////
struct NameValuePair
{
public:
NameValuePair()
{
}
NameValuePair(const String& name, const String& value)
{
this->name = name;
this->value = value;
}
String name;
String value;
};
// Get JSON from NameValue pairs
static String getJSONFromNVPairs(std::vector<NameValuePair>& nameValuePairs, bool includeOuterBraces)
{
// Calculate length for efficiency
uint32_t reserveLen = 0;
for (NameValuePair& pair : nameValuePairs)
reserveLen += 6 + pair.name.length() + pair.value.length();
// Generate JSON
String jsonStr;
jsonStr.reserve(reserveLen);
for (NameValuePair& pair : nameValuePairs)
{
if (jsonStr.length() > 0)
jsonStr += ',';
if (pair.value.startsWith("[") || pair.value.startsWith("{"))
jsonStr += "\"" + pair.name + "\":" + pair.value;
else
jsonStr += "\"" + pair.name + "\":\"" + pair.value + "\"";
}
if (includeOuterBraces)
return "{" + jsonStr + "}";
return jsonStr;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Escape a string
/// @param strToEsc : string in which to replace characters which are invalid in JSON
static void escapeString(String& strToEsc)
{
// Replace characters which are invalid in JSON
strToEsc.replace("\\", "\\\\");
strToEsc.replace("\"", "\\\"");
strToEsc.replace("\n", "\\n");
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Unescape a string
/// @param strToUnEsc : string in which to restore characters which are invalid in JSON
static void unescapeString(String& strToUnEsc)
{
// Replace characters which are invalid in JSON
strToUnEsc.replace("\\\"", "\"");
strToUnEsc.replace("\\\\", "\\");
strToUnEsc.replace("\\n", "\n");
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Convert JSON object to HTML query string syntax
/// @param jsonStr : JSON string
/// @return String : HTML query string
static String getHTMLQueryFromJSON(const String& jsonStr)
{
// Get keys of object
std::vector<String> keyStrs;
RaftJson::getKeysIm(jsonStr.c_str(), "", keyStrs);
if (keyStrs.size() == 0)
return "";
// Fill object
String outStr;
for (String& keyStr : keyStrs)
{
String valStr = RaftJson::getStringIm(jsonStr.c_str(), keyStr.c_str(), "");
if (valStr.length() == 0)
continue;
if (outStr.length() != 0)
outStr += "&";
outStr += keyStr + "=" + valStr;
}
return outStr;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Extract name-value pairs from string
/// @param inStr : input string
/// @param pNameValueSep : separator between name and value, e.g. "=" for HTTP
/// @param pPairDelim : separator between pairs, e.g. "&" for HTTP
/// @param pPairDelimAlt : alternate separator between pairs (pass 0 if not needed), e.g. ";" for HTTP
/// @param nameValuePairs : vector of name-value pairs
/// @return void
static void extractNameValues(const String& inStr,
const char* pNameValueSep, const char* pPairDelim, const char* pPairDelimAlt,
std::vector<RaftJson::NameValuePair>& nameValuePairs)
{
// Count the pairs
uint32_t pairCount = 0;
const char* pCurSep = inStr.c_str();
while(pCurSep)
{
pCurSep = strstr(pCurSep, pNameValueSep);
if (pCurSep)
{
pairCount++;
pCurSep++;
}
}
#ifdef DEBUG_EXTRACT_NAME_VALUES
// Debug
LOG_I(RAFT_JSON_PREFIX, "extractNameValues found %d nameValues", pairCount);
#endif
// Extract the pairs
nameValuePairs.resize(pairCount);
pCurSep = inStr.c_str();
bool sepTypeIsEqualsSign = true;
uint32_t pairIdx = 0;
String name, val;
while(pCurSep)
{
// Each pair has the form "name=val;" (semicolon missing on last pair)
const char* pElemStart = pCurSep;
if (sepTypeIsEqualsSign)
{
// Check for missing =
pCurSep = strstr(pElemStart, pNameValueSep);
if (!pCurSep)
break;
name = String((uint8_t*)pElemStart, pCurSep-pElemStart);
pCurSep++;
}
else
{
// Handle two alternatives - sep or no sep
pCurSep = strstr(pElemStart, pPairDelim);
if (!pCurSep && pPairDelimAlt)
pCurSep = strstr(pElemStart, pPairDelimAlt);
if (pCurSep)
{
val = String((uint8_t*)pElemStart, pCurSep-pElemStart);
pCurSep++;
}
else
{
val = pElemStart;
}
}
// Next separator
sepTypeIsEqualsSign = !sepTypeIsEqualsSign;
if (!sepTypeIsEqualsSign)
continue;
// Store and move on
if (pairIdx >= pairCount)
break;
name.trim();
val.trim();
nameValuePairs[pairIdx] = {name,val};
pairIdx++;
}
#ifdef DEBUG_EXTRACT_NAME_VALUES
// Debug
for (RaftJson::NameValuePair& pair : nameValuePairs)
LOG_I(RAFT_JSON_PREFIX, "extractNameValues name %s val %s", pair.name.c_str(), pair.value.c_str());
#endif
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Get string representation of element type
/// @param type : element type
/// @return const char* : string representation of element type
static const char* getElemTypeStr(RaftJsonType type)
{
switch (type)
{
case RAFT_JSON_STRING: return "STR";
case RAFT_JSON_OBJECT: return "OBJ";
case RAFT_JSON_ARRAY: return "ARRY";
case RAFT_JSON_BOOLEAN: return "BOOL";
case RAFT_JSON_NUMBER: return "NUM";
case RAFT_JSON_NULL: return "NULL";
case RAFT_JSON_UNDEFINED: return "UNDEF";
}
return "UNKN";
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Get JSON doc contents
/// @return const char* : JSON doc contents
const char* getJsonDoc() const override
{
return _pSourceStr;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Get chained RaftJson object
/// @return RaftJsonIF* : chained RaftJson object (may be null if no chaining)
const RaftJsonIF* getChainedRaftJson() const override
{
return _pChainedRaftJson;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Set chained RaftJson object
/// @param pChainedRaftJson chained RaftJson object (may be null if chaining is to be disabled)
virtual void setChainedRaftJson(const RaftJsonIF* pChainedRaftJson) override
{
_pChainedRaftJson = pChainedRaftJson;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Set source string - the main JSON doc string used by this object
/// @param pSourceStr source string
/// @param makeCopy when false the string pointer must remain valid for the lifetime of this object
/// @param sourceStrLen length of source string
void setSourceStr(const char* pSourceStr, bool makeCopy, uint32_t sourceStrLen)
{
// Check valid
if (!pSourceStr)
return;
// Make copy if required
if (makeCopy)
{
#ifdef USE_RAFT_SPIRAM_AWARE_ALLOCATOR
_jsonStr = std::vector<char, SpiramAwareAllocator<char>>(pSourceStr, pSourceStr + sourceStrLen + 1);
#else
_jsonStr = std::vector<char>(pSourceStr, pSourceStr + sourceStrLen + 1);
#endif
// Reference the copy
_pSourceStr = _jsonStr.data();
}
else
{
// Reference the source string
_pSourceStr = pSourceStr;
}
}
protected:
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Locate an element in a JSON document using a path
/// @param pPath the path of the required variable in XPath-like syntax (e.g. "a/b/c[0]/d")
/// @return the position of the element or nullptr if not found
virtual const char* locateElementByPath(const char* pPath) const override
{
return locateElementByPath(_pSourceStr, pPath, _pChainedRaftJson);
}
private:
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Locate a string element from the current position in a JSON document
/// @param pJsonDocPos the current position in the JSON document
/// @param pElemStart [out] the start of the element
/// @param pElemEnd [out] the end of the element
/// @param includeQuotes if true include the quotes in the returned element
/// @return a position in the document after the end of the element or nullptr if not found
static const char* locateStringElement(const char* pJsonDocPos, const char*& pElemStart, const char*& pElemEnd, bool includeQuotes = false)
{
// Skip quote if at start of string
if (!includeQuotes && (*pJsonDocPos == '"'))
pJsonDocPos++;
// Find end of string
pElemStart = pJsonDocPos;
bool isEscaped = false;
while (*pJsonDocPos && (isEscaped || (*pJsonDocPos != '"')))
{
isEscaped = (*pJsonDocPos == '\\');
pJsonDocPos++;
}
// Return string start and end
if (*pJsonDocPos == '"')
{
pElemEnd = includeQuotes ? pJsonDocPos + 1 : pJsonDocPos;
return pJsonDocPos + 1;
}
return nullptr;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Locate the bounds of an element in a JSON document
/// @param pJsonDocPos the current position in the JSON document
/// @param pElemStart [out] the start of the element
/// @param pElemEnd [out] the end of the element
/// @return a position in the document after the end of the element or nullptr if not found
static const char* locateElementBounds(const char* pJsonDocPos, const char*& pElemStart, const char*& pElemEnd)
{
// Skip whitespace, commas and colons
while (*pJsonDocPos && ((*pJsonDocPos <= ' ') || (*pJsonDocPos == ',') || (*pJsonDocPos == ':')))
pJsonDocPos++;
if (!*pJsonDocPos)
return nullptr;
pElemStart = pJsonDocPos;
// Check for kind of element
if ((*pJsonDocPos == '{') || (*pJsonDocPos == '['))
{
#ifdef DEBUG_JSON_LOCATE_ELEMENT_BOUNDS
LOG_I("RaftJson", "locateElementBounds found object/array pJsonDocPos %p jsonDoc ",
pJsonDocPos, pJsonDocPos ? pJsonDocPos : "null");
#endif
// Find end of object
char braceChar = *pJsonDocPos;
int numBraces = 1;
pJsonDocPos++;
// Skip to end of object
bool insideString = false;
while (*pJsonDocPos && (numBraces > 0))
{
if (*pJsonDocPos == '"')
{
insideString = !insideString;
#ifdef DEBUG_JSON_LOCATE_ELEMENT_BOUNDS
LOG_I("RaftJson", "locateElementBounds idx %d %s",
pJsonDocPos-pElemStart,
insideString ? "INSIDE_STR" : "OUTSIDE_STR");
#endif
}
if (!insideString)
{
if (*pJsonDocPos == braceChar)
{
numBraces++;
#ifdef DEBUG_JSON_LOCATE_ELEMENT_BOUNDS
LOG_I("RaftJson", "locateElementBounds idx %d OPEN_BRACE %d",
pJsonDocPos-pElemStart, numBraces);
#endif
}
else if (*pJsonDocPos == ((braceChar == '{') ? '}' : ']'))
{
numBraces--;
#ifdef DEBUG_JSON_LOCATE_ELEMENT_BOUNDS
LOG_I("RaftJson", "locateElementBounds idx %d CLOSE_BRACE %d",
pJsonDocPos-pElemStart, numBraces);
#endif
}
}
pJsonDocPos++;
#ifdef DEBUG_JSON_LOCATE_ELEMENT_BOUNDS
LOG_I("RaftJson", "locateElementBounds LOOPING idx %d ch <<<%c>>>",
pJsonDocPos-pElemStart, *pJsonDocPos ? *pJsonDocPos : '~');
#endif
}
if ((numBraces > 0) && (!*pJsonDocPos))
{
#ifdef DEBUG_JSON_LOCATE_ELEMENT_BOUNDS
LOG_I("RaftJson", "locateElementBounds obj unexpectedly reached end of document pJsonDocPos %p", pJsonDocPos);
#endif
return nullptr;
}
pElemEnd = pJsonDocPos;
// Skip whitespace and commas
while (*pJsonDocPos && ((*pJsonDocPos <= ' ') || (*pJsonDocPos == ',')))
pJsonDocPos++;
return pJsonDocPos;
}
else if (*pJsonDocPos == '"')
{
#ifdef DEBUG_JSON_LOCATE_ELEMENT_BOUNDS
LOG_I("RaftJson", "locateElementBounds found string pJsonDocPos %p", pJsonDocPos);
#endif
// Find end of string
pJsonDocPos++;
bool isEscaped = false;
while (*pJsonDocPos && (isEscaped || (*pJsonDocPos != '"')))
{
isEscaped = (*pJsonDocPos == '\\');
pJsonDocPos++;
}
if (!*pJsonDocPos)
{
#ifdef DEBUG_JSON_LOCATE_ELEMENT_BOUNDS
LOG_I("RaftJson", "locateElementBounds str unexpectedly reached end of document pJsonDocPos %p", pJsonDocPos);
#endif
return nullptr;
}
pElemEnd = pJsonDocPos;
pJsonDocPos++;
// Skip whitespace and commas
while (*pJsonDocPos && ((*pJsonDocPos <= ' ') || (*pJsonDocPos == ',')))
pJsonDocPos++;
return pJsonDocPos;
}
else
{
#ifdef DEBUG_JSON_LOCATE_ELEMENT_BOUNDS
LOG_I("RaftJson", "locateElementBounds found number pJsonDocPos %p", pJsonDocPos);
#endif
// Find end of element
while (*pJsonDocPos && (*pJsonDocPos > ' ') && (*pJsonDocPos != ',') && (*pJsonDocPos != '}') && (*pJsonDocPos != ']'))
pJsonDocPos++;
pElemEnd = pJsonDocPos;
// Skip whitespace and commas
while (*pJsonDocPos && ((*pJsonDocPos <= ' ') || (*pJsonDocPos == ',')))
pJsonDocPos++;
if (!*pJsonDocPos)
{
#ifdef DEBUG_JSON_LOCATE_ELEMENT_BOUNDS
LOG_I("RaftJson", "locateElementBounds num unexpectedly reached end of document pJsonDocPos %p", pJsonDocPos);
#endif
}
return pJsonDocPos;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Locate an element in a JSON document using a single key (part of a path)
/// @param pReqdKey [in/out] the key of the required variable (note this is modified by the function)
/// @return the position of the element or nullptr if not found
/// @note The key can be empty in which case the entire object is returned
/// @note The key can be an array index (e.g. "[0]") in which case the value at that index is returned if the element is an array
/// @note The key can be a string in which case the value for that key is returned if the element is an object
static const char* locateElementValueWithKey(const char* pJsonDocPos, const char*& pReqdKey)
{
// Check valid
if (!pJsonDocPos)
return nullptr;
// If key is empty return the entire element
if (!pReqdKey || !*pReqdKey || (*pReqdKey == '/'))
{
// Skip any whitespace
while (*pJsonDocPos && (*pJsonDocPos <= ' '))
pJsonDocPos++;
#ifdef DEBUG_JSON_BY_SPECIFIC_PATH_PART
if (strcmp(pReqdKey, DEBUG_JSON_BY_SPECIFIC_PATH_PART) == 0)
{
LOG_I(RAFT_JSON_PREFIX, "locateElementValueWithKey key <<<%s>>> jsonDoc %s", pReqdKey, pJsonDocPos);
}
#endif
// Move key position to next part of path
if (pReqdKey && (*pReqdKey == '/'))
pReqdKey++;
return pJsonDocPos;
}
// Skip any whitespace
while (*pJsonDocPos && (*pJsonDocPos <= ' '))
pJsonDocPos++;
if ((*pJsonDocPos != '{') && (*pJsonDocPos != '['))
return nullptr;
// Check for the type of element - object or array
const char* pReqdKeyStart = pReqdKey;
const char* pReqdKeyEnd = nullptr;
bool isObject = true;
uint32_t arrayIdx = 0;
uint32_t elemCount = 0;
if (*pJsonDocPos == '[')
{
isObject = false;
// Check the key is an array index
if (*pReqdKey != '[')
return nullptr;
pReqdKey++;
// Extract array index from key
arrayIdx = atoi(pReqdKey);
// Move key position to next part of path
while (*pReqdKey && (*pReqdKey != '/'))
pReqdKey++;
}
else
{
// Find the end of this part of the key path
while (*pReqdKey && (*pReqdKey != '/') && (*pReqdKey != '['))
pReqdKey++;
pReqdKeyEnd = pReqdKey;
}
// Move past the used part of the key path
if (*pReqdKey == '/')
pReqdKey++;
// Move into the object or array
pJsonDocPos++;
// Skip over elements until we get to the one we want
const char* pKeyStart = pJsonDocPos;
const char* pKeyEnd = nullptr;
while (*pJsonDocPos)
{