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.3] - 2026-05-19

### Added
- Atomic file saving: Implemented a safe save mechanism using temporary `-journal` files to prevent data loss or corruption during writes.
- Explicit `flush()` and `close()` checks for improved reliability during file operations.

### Changed
- Incremented version to 0.2.3.

## [0.2.2] - 2026-05-15

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

[![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.2-blue.svg)](src/TinyJSON.h)
[![Version](https://img.shields.io/badge/version-0.2.3-blue.svg)](src/TinyJSON.h)

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

* Zero-dependency: Easy to integrate into any C++ project.
* Atomic file saving: Guarantees data integrity by using a temporary file before overwriting.
* Minimal memory overhead: Ideal for resource-constrained environments.
* High-performance parsing: Benchmarked for speed against other leading parsers.
* Simple API: Intuitive and easy to use.
Expand Down Expand Up @@ -129,8 +130,8 @@ 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 = 0;
static const char TJ_VERSION_STRING[] = "0.2.0";
static const short TJ_VERSION_PATCH = 3;
static const char TJ_VERSION_STRING[] = "0.2.3";
```

## Options
Expand Down
39 changes: 37 additions & 2 deletions src/TinyJSON.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <fstream>
#include <iostream>
#include <cctype>
#include <cstdio>

static constexpr short TJ_MAX_NUMBER_OF_DIGGITS = 19;
static constexpr short TJ_DEFAULT_STRING_READ_SIZE = 10;
Expand Down Expand Up @@ -4188,11 +4189,21 @@ namespace TinyJSON
return false;
}

// try and optn the file...
std::ofstream outFile(file_path, std::ios::out | std::ios::binary);
// Allocate temporary file path
auto file_path_length = TJHelper::string_length(file_path);
auto journal_suffix = TJCHARPREFIX("-journal");
auto journal_length = TJHelper::string_length(journal_suffix);
TJCHAR* tmp_file_path = new TJCHAR[file_path_length + journal_length + 1];
TJHelper::copy_string(file_path, tmp_file_path, file_path_length);
TJHelper::copy_string(journal_suffix, tmp_file_path + file_path_length, journal_length);
tmp_file_path[file_path_length + journal_length] = TJ_NULL_TERMINATOR;

// try and open the temporary file...
std::ofstream outFile((const char*)tmp_file_path, std::ios::out | std::ios::binary);
if (!outFile)
{
write_result.assign_exception_message("Unable to open file for writing.");
delete[] tmp_file_path;
write_result.throw_if_exception();
return false;
}
Expand All @@ -4204,6 +4215,7 @@ namespace TinyJSON
if (!outFile)
{
write_result.assign_exception_message("Unable to write UTF-8 BOM.");
delete[] tmp_file_path;
write_result.throw_if_exception();
return false;
}
Expand All @@ -4215,6 +4227,16 @@ namespace TinyJSON
if (!outFile)
{
write_result.assign_exception_message("Unable to write to file.");
delete[] tmp_file_path;
write_result.throw_if_exception();
return false;
}

outFile.flush();
if (!outFile)
{
write_result.assign_exception_message("Unable to flush the file to disk.");
delete[] tmp_file_path;
write_result.throw_if_exception();
return false;
}
Expand All @@ -4224,9 +4246,22 @@ namespace TinyJSON
if (!outFile)
{
write_result.assign_exception_message("Unable to close the file.");
delete[] tmp_file_path;
write_result.throw_if_exception();
return false;
}

// Atomic replace
std::remove((const char*)file_path);
if (std::rename((const char*)tmp_file_path, (const char*)file_path) != 0)
{
write_result.assign_exception_message("Unable to atomically rename the temporary file to the target file path.");
delete[] tmp_file_path;
write_result.throw_if_exception();
return false;
}

delete[] tmp_file_path;
return true;
}

Expand Down
5 changes: 3 additions & 2 deletions src/TinyJSON.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@
// v0.2.0 - Breaking change: get_* methods no longer take throw parameters, use parse_options::strict instead.
// 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
static const short TJ_VERSION_MAJOR = 0;
static const short TJ_VERSION_MINOR = 2;
static const short TJ_VERSION_PATCH = 2;
static const char TJ_VERSION_STRING[] = "0.2.2";
static const short TJ_VERSION_PATCH = 3;
static const char TJ_VERSION_STRING[] = "0.2.3";

#ifndef TJ_USE_CHAR
# define TJ_USE_CHAR 1
Expand Down
4 changes: 2 additions & 2 deletions tests/testtinyjsonversion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ TEST(TestVersion, CheckVersionMinor) {
}

TEST(TestVersion, CheckVersionPatch) {
ASSERT_EQ(2, TJ_VERSION_PATCH);
ASSERT_EQ(3, TJ_VERSION_PATCH);
}

TEST(TestVersion, CheckVersionString) {
ASSERT_STREQ("0.2.2", TJ_VERSION_STRING);
ASSERT_STREQ("0.2.3", TJ_VERSION_STRING);
}
Loading