Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.4] - 2026-05-20

### Added
- Dictionary-style member access via `operator[]` for `TJValue`.
- Templated `as<T>()` helper method for simplified type casting (e.g., `json["key"].as<int>()`).

### Changed
- Incremented version to 0.2.4.

## [0.2.3] - 2026-05-19

### Added
Expand Down
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![CI](https://github.com/FFMG/TinyJSON/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/FFMG/TinyJSON/actions/workflows/c-cpp.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Version](https://img.shields.io/badge/version-0.2.3-blue.svg)](src/TinyJSON.h)
[![Version](https://img.shields.io/badge/version-0.2.4-blue.svg)](src/TinyJSON.h)

A lightweight and lightning-fast C++ JSON & JSON5 parser designed for high performance and minimal footprint.

Expand All @@ -19,6 +19,7 @@ A lightweight and lightning-fast C++ JSON & JSON5 parser designed for high perfo
- [Data types](#data-types)
- [Simple examples](#simple-examples)
- [Version Control](#version-control)
- [Simple Value Access](#simple-value-access)
- [Options](#options)
- [Exceptions](#exceptions)
- [Check if JSON is valid](#check-if-json-is-valid)
Expand Down Expand Up @@ -130,8 +131,28 @@ The version is set in the `TinyJSON.h` file.
```cpp
static const short TJ_VERSION_MAJOR = 0;
static const short TJ_VERSION_MINOR = 2;
static const short TJ_VERSION_PATCH = 3;
static const char TJ_VERSION_STRING[] = "0.2.3";
static const short TJ_VERSION_PATCH = 4;
static const char TJ_VERSION_STRING[] = "0.2.4";
```

### Simple Value Access

TinyJSON provides a convenient `operator[]` for object member access and a templated `as<T>()` method for casting values.

```cpp
auto* tj = TJ::parse(R"({"a": {"b": 10}})");
if (tj) {
// Access nested members directly
int value = (*tj)["a"]["b"].as<int>(); // 10

// Handling missing keys (behavior depends on parse_options::throw_exception)
// If throw_exception is false (default), it returns a null value
if ((*tj)["missing"].is_null()) {
// ...
}

delete tj;
}
```

## Options
Expand Down
55 changes: 49 additions & 6 deletions src/TinyJSON.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
#include <fstream>
#include <limits>


#if defined(_WIN32)
#include <io.h>
#else
Expand Down Expand Up @@ -4417,9 +4416,18 @@ namespace TinyJSON
_string = nullptr;
}

const TJMember& TJMember::null_member()
{
static const TJMember member(nullptr, &TJValue::null_value());
return member;
}

void TJMember::free_value()
{
delete _value;
if (nullptr != _value && _value != &TJValue::null_value())
{
delete _value;
}
_value = nullptr;
}

Expand Down Expand Up @@ -4494,6 +4502,39 @@ namespace TinyJSON
_parse_options = options;
}

const TJValue& TJValue::null_value()
{
static const TJValueNull val;
return val;
}

const TJValue& TJValue::operator[](const TJCHAR* key) const
{
if (is_object())
{
const TJValueObject* obj = static_cast<const TJValueObject*>(this);
const TJValue* val = obj->try_get_value(key);
if (val != nullptr)
{
return *val;
}
}

if (_parse_options.throw_exception)
{
throw TJParseException("Key not found");
}

return null_value();
}

#if TJ_INCLUDE_STD_STRING == 1
const TJValue& TJValue::operator[](const std::string& key) const
{
return (*this)[key.c_str()];
}
#endif

void TJValueObject::set_parse_options(const parse_options& options)
{
TJValue::set_parse_options(options);
Expand Down Expand Up @@ -5722,9 +5763,10 @@ namespace TinyJSON
return _members == nullptr ? 0 : _members->size();
}

TJMember* TJValueObject::operator [](int idx) const
const TJMember& TJValueObject::operator [](int idx) const
{
return at(idx);
const TJMember* member = at(idx);
return member != nullptr ? *member : TJMember::null_member();
}

TJMember* TJValueObject::at(int idx) const
Expand Down Expand Up @@ -6191,9 +6233,10 @@ namespace TinyJSON
return _values == nullptr ? 0 : _values->size();
}

TJValue* TJValueArray::operator [](int idx) const
const TJValue& TJValueArray::operator [](int idx) const
{
return at(idx);
const TJValue* val = at(idx);
return val != nullptr ? *val : TJValue::null_value();
}

TJValue* TJValueArray::at(int idx) const
Expand Down
54 changes: 50 additions & 4 deletions src/TinyJSON.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@
// v0.2.1 - added remove_at to TJValueArray.
// v0.2.2 - added support for Json5 https://github.com/json5/
// v0.2.3 - added atomic file saving
// v0.2.4 - added operator[] and as<T>() accessors to TJValue
static const short TJ_VERSION_MAJOR = 0;
static const short TJ_VERSION_MINOR = 2;
static const short TJ_VERSION_PATCH = 3;
static const char TJ_VERSION_STRING[] = "0.2.3";
static const short TJ_VERSION_PATCH = 4;
static const char TJ_VERSION_STRING[] = "0.2.4";

#ifndef TJ_USE_CHAR
# define TJ_USE_CHAR 1
Expand Down Expand Up @@ -671,6 +672,36 @@ class TJDictionary;
return get_vector_internal<V>(std::is_integral<V>());
}

/// <summary>
/// Casting the value to a specific type.
/// it behaves exactly as the get() methods.
/// </summary>
/// <returns></returns>
template<typename T>
auto as() const -> decltype(this->get<T>())
{
return get<T>();
}

/// <summary>
/// Access a member of an object by its key.
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
const TJValue& operator[](const TJCHAR* key) const;

#if TJ_INCLUDE_STD_STRING == 1
/// <summary>
/// Access a member of an object by its key.
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
const TJValue& operator[](const std::string& key) const;
#endif

public:
static const TJValue& null_value();

private:
template<typename V>
std::vector<V> get_vector_internal(std::true_type) const
Expand Down Expand Up @@ -820,6 +851,19 @@ class TJDictionary;
const TJValue* value() const;
TJValue* value();

/// <summary>
/// Casting the value to a specific type.
/// it behaves exactly as the get() methods.
/// </summary>
/// <returns></returns>
template<typename T>
auto as() const -> decltype(this->value()->template as<T>())
{
return value()->template as<T>();
}

static const TJMember& null_member();

protected:
/// <summary>
/// Move the value ownership to the member.
Expand Down Expand Up @@ -1227,7 +1271,8 @@ class TJDictionary;
}
#endif

TJMember* operator [](int idx) const;
const TJMember& operator [](int idx) const;
using TJValue::operator[];
TJMember* at(int idx) const;
TJMember* element_at(int idx) const;

Expand Down Expand Up @@ -1484,7 +1529,8 @@ class TJDictionary;
/// <returns></returns>
unsigned int get_number_of_elements() const;

TJValue* operator [](int idx) const;
const TJValue& operator [](int idx) const;
using TJValue::operator[];
TJValue* at(int idx) const;
TJValue* element_at(int idx) const;

Expand Down
18 changes: 18 additions & 0 deletions tests/testjson5objects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,21 @@ TEST(TJJSON5Objects, IllegalUnquotedKeyStartRejected)
auto* tj = TJ::parse(json, options);
ASSERT_EQ(nullptr, tj);
}

TEST(TJJSON5Objects, OperatorBracketJSON5Features)
{
const TJCHAR* json = TJCHARPREFIX("{unquoted: 1, 'single': 2, while: true, $: 3, _: 4}");
parse_options options;
options.specification = parse_options::json5_1_0_0;
auto* tj = TJ::parse(json, options);
ASSERT_NE(nullptr, tj);

// Test operator[] with JSON5 specific key styles
ASSERT_EQ(1, (*tj)["unquoted"].as<int>());
ASSERT_EQ(2, (*tj)["single"].as<int>());
ASSERT_TRUE((*tj)["while"].as<bool>());
ASSERT_EQ(3, (*tj)["$"].as<int>());
ASSERT_EQ(4, (*tj)["_"].as<int>());

delete tj;
}
15 changes: 13 additions & 2 deletions tests/testtinyjsonarrays.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ TEST(TJValueArray, EmptyArrayInSideArrayHasNoItemsInIt) {
ASSERT_NE(nullptr, jarray);
ASSERT_EQ(1, jarray->get_number_of_items());

const auto jarraya = dynamic_cast<const TinyJSON::TJValueArray*>((*jarray)[0]);
const auto jarraya = dynamic_cast<const TinyJSON::TJValueArray*>(jarray->at(0));
ASSERT_NE(nullptr, jarraya);
ASSERT_EQ(0, jarraya->get_number_of_items());

Expand Down Expand Up @@ -558,4 +558,15 @@ TEST(TJValueArray, AddVectorOfNumberAndGetItAsAVector)
ASSERT_EQ(4, sum); // 1 + 3

delete json;
}
}

TEST(TJValueArray, OperatorBracketAccess)
{
auto json = TinyJSON::TJ::parse(R"([1, 2, 3])");
ASSERT_NE(nullptr, json);

// operator[] on array should return null (it is for object keys)
ASSERT_TRUE((*json)["any_key"].is_null());

delete json;
}
13 changes: 12 additions & 1 deletion tests/testtinyjsonbooleans.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,15 @@ TEST(TestBooleans, CloneFalse) {

delete bool1;
delete bool2;
}
}

TEST(TestBooleans, OperatorBracketAccess)
{
auto json = TinyJSON::TJ::parse("true");
ASSERT_NE(nullptr, json);

// operator[] on boolean should return null
ASSERT_TRUE((*json)["any_key"].is_null());

delete json;
}
13 changes: 12 additions & 1 deletion tests/testtinyjsonnulls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,15 @@ TEST(TestNulls, CloneNull) {

delete null1;
delete null2;
}
}

TEST(TestNulls, OperatorBracketAccess)
{
auto json = TinyJSON::TJ::parse("null");
ASSERT_NE(nullptr, json);

// operator[] on null should return null
ASSERT_TRUE((*json)["any_key"].is_null());

delete json;
}
13 changes: 12 additions & 1 deletion tests/testtinyjsonnumbers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -514,4 +514,15 @@ TEST(TestNumbers, CreateFloatByPassingNegativeNumber) {
ASSERT_EQ(-42, value->get_number());
ASSERT_EQ(-42.5, value->get_float());
delete json;
}
}

TEST(TestNumbers, OperatorBracketAccess)
{
auto json = TinyJSON::TJ::parse("123");
ASSERT_NE(nullptr, json);

// operator[] on number should return null
ASSERT_TRUE((*json)["any_key"].is_null());

delete json;
}
14 changes: 13 additions & 1 deletion tests/testtinyjsonobjects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ TEST(TestObjects, GetItemByIndex) {
ASSERT_NE(nullptr, jobject);
ASSERT_EQ(1, jobject->get_number_of_items());

const auto jobjecta = dynamic_cast<const TinyJSON::TJValueObject*>((*jobject)[0]->value());
const auto jobjecta = dynamic_cast<const TinyJSON::TJValueObject*>(jobject->at(0)->value());
ASSERT_NE(nullptr, jobjecta);
ASSERT_EQ(1, jobjecta->get_number_of_items());

Expand Down Expand Up @@ -1257,3 +1257,15 @@ TEST(TestObjects, NumberOfItemsUpdatesOnMutation) {

delete json;
}

TEST(TestObjects, OperatorBracketAccess)
{
auto json = TinyJSON::TJ::parse(R"({"key": "value", "nested": {"a": 1}})");
ASSERT_NE(nullptr, json);

// Test operator[] for objects
ASSERT_STREQ("value", (*json)["key"].as<const char*>());
ASSERT_EQ(1, (*json)["nested"]["a"].as<int>());

delete json;
}
9 changes: 9 additions & 0 deletions tests/testtinyjsonset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,12 @@ TEST(TestSet, GenericSetVectorFloat) {
ASSERT_NEAR(1.1, result[0], 0.001);
ASSERT_NEAR(2.2, result[1], 0.001);
}

TEST(TestSet, OperatorBracketAccess)
{
TinyJSON::TJValueObject obj;
obj.set("a", 42);

ASSERT_EQ(42, obj["a"].as<int>());
ASSERT_TRUE(obj["b"].is_null());
}
Loading
Loading