JSON Lexer Programming Guide
Overview
The JSON Lexer provides a powerful interface for parsing and accessing JSON data in NetBurner applications. This guide covers version 3.3.9 and later, which includes significant improvements over earlier versions.
Core Components
The JSON Lexer interface is defined in json_lexer.h and provides two primary object types:
ParsedJsonDataSet
Represents a complete JSON object that has been parsed and stored internally. This object contains the entire tokenized JSON structure in an optimized format using NetBurner poolBuffers.
JsonRef
A reference object that acts as a pointer to a location within a ParsedJsonDataSet. It simplifies navigation through complex JSON hierarchies by providing a clean, intuitive interface for accessing nested data.
┌─────────────────────────────────────────┐
│ ┌───────────────────────────────────┐ │
│ │ Internal tokenized JSON storage │ │
│ └───────────────────────────────────┘ │
│ ^ │
│ | │
│ ┌─────┴─────┐ │
│ │ (pointer) │ │
│ └───────────┘ │
└─────────────────────────────────────────┘
Represents a positional reference (pointer) of a location inside a ParsedJsonDataSet object.
Definition json_lexer.h:113
A class to create, read, and modify a JSON object.
Definition json_lexer.h:535
Version Notes
The interface introduced in version 3.3.9 supersedes the previous API. While legacy code continues to work, all new development should use the JsonRef-based interface described in this guide for improved clarity and ease of use.
Loading JSON Data
There are three primary methods to populate a ParsedJsonDataSet object:
Method 1: From Text Blob
Load JSON directly from a text string or character array.
const char* jdata = "{\"key\": \"value\"}";
size_t jlen = strlen(jdata);
Method 2: From Web Server
Retrieve JSON from an external HTTP endpoint.
Method 3: From File Descriptor
Read JSON from any file descriptor source (web socket, serial port, etc.).
virtual int ReadFrom(int fd)
Reads in data from the specified file descriptor and parses it into the JSON data set.
Data Flow Diagram
┌────────────┐ ┌─────────────────┐ ┌──────────────┐
│ Text Blob │ │ │ │ │
│ or │────>──────│ Tokenization │───<────│ Navigate & │
│ Network │ │ & Storage │ │ Extract │
│ or │ │ │ │ │
│ File Desc. │ └─────────────────┘ └──────────────┘
└────────────┘
Accessing JSON Data
Example JSON Structure
Consider the following JSON data:
{
"Anumber": 1234,
"AString": "ImaString",
"AnObject": {
"Item1": 1,
"Item2": "TheItem2"
},
"AnArray": [1, 2, 3, 4]
}
Basic Access Patterns
Simple Values
int i = pjds("ANumber");
const char* pStr = pjds("AString");
Lightweight alternative to C++ CString class.
Definition nbstring.h:118
Nested Objects
Use JsonRef to simplify access to nested structures:
Array Access
Use array indexing to access elements:
for(int i = 0; i < 4; i++) {
printf("Item[%d] = %d\n", i, pjds("AnArray")[i]);
}
Access Operator Summary
The ParsedJsonDataSet and JsonRef objects use the following notation:
("name") - Find element by name
[index] - Access array element by index
Navigation Example
pjds
│
├─> ("ANumber") ──> int value
│
├─> ("AString") ──> string value
│
│ │
│ ├─> ("Item1") ──> int value
│ └─> ("Item2") ──> string value
│
└─> ("AnArray")
│
├─> [0] ──> value
├─> [1] ──> value
├─> [2] ──> value
└─> [3] ──> value
Type Conversion Operators
The ParsedJsonDataSet supports automatic type conversion through operator overloading:
operator bool() const { return PermissiveCurrentBool(); }
operator float() const { return (float)CurrentNumber(); }
operator double() const { return CurrentNumber(); }
operator uint8_t() const { return (uint8_t)CurrentNumber(); }
operator int() const { return (int)CurrentNumber(); }
operator uint16_t() const { return (uint16_t)CurrentNumber(); }
operator uint32_t() const { return (uint32_t)CurrentNumber(); }
operator int8_t() const { return (int8_t)CurrentNumber(); }
operator int16_t() const { return (int16_t)CurrentNumber(); }
operator int32_t() const { return (uint32_t)CurrentNumber(); }
operator time_t() const { return (time_t)CurrentNumber(); }
operator const char*() const { return CurrentString(); }
Validation Functions
IsValid() Method
Verify that a JsonRef points to valid data:
pjds("AnArray")[3].IsValid();
pjds("AnArray")[4].IsValid();
Type Checking Functions
Determine the type of JSON data:
IsNumber()
IsObject()
IsString()
IsBool()
IsNull()
IsArray()
Validation Flow
┌──────────────┐
│ Access JSON │
│ Element │
└──────┬───────┘
│
v
┌──────────────┐ Yes ┌──────────────┐
│ IsValid()? │─────>──────│ Check Type │
└──────┬───────┘ └──────┬───────┘
│ │
│ No v
v ┌──────────────┐
┌──────────────┐ │ Extract Data │
│ Handle Error │ └──────────────┘
└──────────────┘
Example References
Complete working examples demonstrating these concepts:
- Basic Parsing:
\nburn\examples\JsonLexer\ParseTest
- Complex Parsing:
\nburn\examples\webclient\Earthquake
The Earthquake example demonstrates parsing a large, complex JSON structure from a live web service and extracting specific fields efficiently.
Earthquake Example Application
Overview
This example retrieves earthquake data from the USGS API and parses the complex JSON response to display recent seismic activity.
Source Code
#include <init.h>
#include <stdio.h>
#include <ctype.h>
#include <buffers.h>
#include <json_lexer.h>
#include <webclient/http_funcs.h>
#include <nbtime.h>
const char* url = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson";
const char* AppName = "EarthQuake";
void UserMain(void* pd)
{
printf("Application started\n");
bool bTimeSet = SetTimeNTPFromPool();
if(bTimeSet) {
printf("Time Set\r\n");
}
bool result =
DoGet(url, JsonResult);
if (result)
{
int32_t nQuakes = JsonResult("metadata")("count");
printf("In the last Day we have had %ld Earthquakes > 4.5\r\n", nQuakes);
for (int i = 0; i < nQuakes; i++)
{
JsonRef OneQuakeProperties = JsonResult(
"features")[i](
"properties");
if (OneQuakeProperties.
Valid())
{
double magnitude = OneQuakeProperties("mag");
NBString place_name = OneQuakeProperties(
"place");
printf(
"Magnitude :%2.1f at %s", magnitude, place_name.
c_str());
if (bTimeSet)
{
time_t when = OneQuakeProperties("time");
when /= 1000;
time_t delta = (now - when);
int sec = delta % 60;
int mins = (delta / 60) % 60;
int hour = (delta / 3600);
printf(" %02d:%02d:%02d ago", hour, mins, sec);
}
printf("\r\n");
}
else
{
printf("Parse error failed to find array element %d\r\n", i);
}
}
}
else
{
printf("Failed to contact server\r\n");
}
while (1)
{
}
}
bool Valid() const
Check if the current JsonRef is a valid JSON position.
Definition json_lexer.h:402
const char * c_str() const
Method to pass a NBString as a constant char *.
#define TICKS_PER_SECOND
System clock ticks per second.
Definition constants.h:49
time_t time(time_t *pt)
Gets the current system GMT time.
bool DoGet(ParsedURI &TheUri, buffer_object &result_buffer, uint16_t TIMEOUT_WAIT=10 *TICKS_PER_SECOND)
Executes an HTTP GET request using a pre-parsed URI and stores the response in a buffer object.
Definition http_funcs.h:2070
void init()
System initialization. Ideally called at the beginning of all applications, since the easiest Recover...
void EnableSystemDiagnostics()
Turn on the diagnostic reports from the config page.
bool WaitForActiveNetwork(uint32_t ticks_to_wait=120 *TICKS_PER_SECOND, int interface=-1)
Wait for an active network connection on at least one interface.
JSON Structure Navigation
The earthquake data follows this hierarchy:
root
│
├─> ("metadata")
│ └─> ("count") ──> number of earthquakes
│
└─> ("features") ──> array
│
└─> [i] ──> array element
│
└─> ("properties")
├─> ("mag") ──> magnitude
├─> ("place") ──> location string
└─> ("time") ──> timestamp
Data Extraction Flow
│
v
│
├─> metadata
│ └─> count ────────> Loop Control
│
└─> features[i]
│
└─> properties
├─> mag ──────> Display
├─> place ────> Display
└─> time ─────> Calculate & Display
Example Output
Application started
Time Set
In the last Day we have had 17 Earthquakes > 4.5
Magnitude :4.5 at Fiji region 01:44:12 ago
Magnitude :4.6 at 128 km W of San Juan, Peru 10:06:35 ago
Magnitude :4.5 at 7 km NW of Papayal, Peru 10:32:50 ago
Magnitude :4.9 at 56 km SSE of Sand Point, Alaska 10:47:22 ago
Magnitude :4.9 at 22 km SSE of Padong, Philippines 11:05:28 ago
Magnitude :5.1 at 70 km WNW of San Antonio de los Cobres, Argentina 12:25:58 ago
Magnitude :5.3 at 116 km SSE of Kushiro, Japan 13:12:09 ago
Magnitude :4.6 at 112 km SSE of Kushiro, Japan 13:13:00 ago
Magnitude :4.8 at 129 km WNW of Pangai, Tonga 13:49:43 ago
Magnitude :4.9 at 256 km ESE of Ust'-Kamchatsk Staryy, Russia 20:15:25 ago
Magnitude :4.6 at 120 km NNE of Tobelo, Indonesia 20:23:53 ago
Magnitude :4.9 at 57 km ESE of Koseda, Japan 21:28:56 ago
Magnitude :4.8 at 24 km NE of La Paz, Philippines 22:38:44 ago
Magnitude :4.5 at northern Qinghai, China 23:10:02 ago
Magnitude :4.6 at 8 km S of Padong, Philippines 23:13:24 ago
Magnitude :4.5 at Fiji region 23:19:04 ago
Magnitude :5.1 at 108 km SSE of Hihifo, Tonga 23:39:10 ago
USGS JSON Response Structure
Response Schema
The USGS earthquake API returns data in GeoJSON format with the following structure:
{
"type": "FeatureCollection",
"metadata": { ... },
"features": [ ... ],
"bbox": [ ... ]
}
Metadata Section
"metadata": {
"generated": <timestamp>,
"url": <string>,
"title": <string>,
"status": <number>,
"api": <version>,
"count": <number>
}
Features Array
Each earthquake event is represented as a feature:
"features": [
{
"type": "Feature",
"properties": {
"mag": <number>,
"place": <string>,
"time": <timestamp>,
"updated": <timestamp>,
"url": <string>,
"status": <string>,
"tsunami": <0|1>,
...
},
"geometry": {
"type": "Point",
"coordinates": [<lon>, <lat>, <depth>]
},
"id": <string>
},
...
]
Parsing Strategy
Step 1: Validate Response
│
v
Step 2: Extract Metadata
│
├─> Get earthquake count
└─> Verify API status
│
v
Step 3: Iterate Features Array
│
v
Step 4: For Each Feature
│
├─> Get properties reference
├─> Extract magnitude
├─> Extract location
├─> Extract timestamp
└─> Format output
Complete JSON Sample
The following shows a condensed version of the actual JSON returned by the USGS API. To view the complete response structure when running the example, uncomment this line in the source code:
Sample Structure
{
"type": "FeatureCollection",
"metadata": {
"generated": 1666815488000,
"url": "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson",
"title": "USGS Magnitude 4.5+ Earthquakes, Past Day",
"status": 200,
"api": "1.10.3",
"count": 17
},
"features": [
{
"type": "Feature",
"properties": {
"mag": 4.5,
"place": "Fiji region",
"time": 1666809240464,
"updated": 1666814388040,
"url": "https://earthquake.usgs.gov/earthquakes/eventpage/us7000ikie",
"status": "reviewed",
"tsunami": 0,
"type": "earthquake"
},
"geometry": {
"type": "Point",
"coordinates": [-178.3577, -20.3848, 551.981]
},
"id": "us7000ikie"
}
],
"bbox": [-178.3577, -24.0942, 10, 166.3788, 55.5213, 590.211]
}
Best Practices
Memory Management
- JsonRef objects are lightweight references, not copies
- const char* strings are valid only while the ParsedJsonDataSet exists
- Use NBString for persistent storage
Error Handling
Always validate before accessing data:
if (element.IsValid()) {
int value = element;
} else {
}
Performance Considerations
┌─────────────────────────────────────────────────────────────────┐
│ Best Practice: Cache
JsonRef Objects │
│ │
│ Good: │
│
JsonRef props = pjds(
"features")[i](
"properties"); │
│ double mag = props("mag"); │
│ │
│ Avoid: │
│ double mag = pjds("features")[i]("properties")("mag"); │
│
NBString place = pjds(
"features")[i](
"properties")(
"place"); │
│ │
└─────────────────────────────────────────────────────────────────┘
Type Safety
Use type checking before conversion:
double value = element;
}
}
bool IsString()
Check if the current JsonRef is a valid JSON string element.
Definition json_lexer.h:382
bool IsNumber()
Check if the current JsonRef is a valid JSON number element.
Definition json_lexer.h:371
Advanced Usage
Nested Array Navigation
JsonRef deepElement = pjds(
"level1")(
"level2")[index](
"level3");
Dynamic Key Access
int value = pjds(keyName.
c_str());
Array Iteration Patterns
for (int i = 0; array[i].IsValid(); i++) {
}
}
bool IsArray()
Check if the current JsonRef is a valid JSON array element.
Definition json_lexer.h:397
Summary
The JSON Lexer provides a powerful, intuitive interface for working with JSON data in NetBurner applications:
- ParsedJsonDataSet handles parsing and storage
- JsonRef simplifies navigation of complex structures
- Automatic type conversion reduces boilerplate code
- Validation functions ensure safe data access
- Performance optimizations through internal poolBuffer management
For complete working examples, refer to the NetBurner example applications in the \nburn\examples directory.