dynamic_string.h v0.2.2
A modern, efficient, single-file string library for C
Loading...
Searching...
No Matches
Classes | Macros | Typedefs | Functions
dynamic_string.h File Reference

Modern, efficient, single-file string library for C. More...

#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
Include dependency graph for dynamic_string.h:

Go to the source code of this file.

Classes

struct  ds_codepoint_iter
 Unicode codepoint iterator for UTF-8 strings. More...
 
struct  ds_stringbuilder
 

Macros

#define DS_MALLOC   malloc
 
#define DS_REALLOC   realloc
 
#define DS_FREE   free
 
#define DS_ASSERT   assert
 
#define DS_ATOMIC_REFCOUNT   0
 Enable atomic reference counting (default: 0)
 
#define DS_DEF   extern
 
#define DS_ATOMIC_SIZE_T   size_t
 
#define DS_ATOMIC_FETCH_ADD(ptr, val)   (*(ptr) += (val), *(ptr) - (val))
 
#define DS_ATOMIC_FETCH_SUB(ptr, val)   (*(ptr) -= (val), *(ptr) + (val))
 
#define DS_ATOMIC_LOAD(ptr)   (*(ptr))
 
#define DS_ATOMIC_STORE(ptr, val)   (*(ptr) = (val))
 
#define ds_empty()   ds_new("")
 
#define ds_from_literal(lit)   ds_new(lit)
 

Typedefs

typedef char * ds_string
 String handle - points directly to null-terminated string data.
 

Functions

DS_DEF ds_string ds_new (const char *text)
 Create a new string from a C string.
 
DS_DEF ds_string ds_create_length (const char *text, size_t length)
 Create a string from a buffer with explicit length.
 
DS_DEF ds_string ds_retain (ds_string str)
 Increment reference count and return shared handle.
 
DS_DEF void ds_release (ds_string *str)
 Decrement reference count and free memory if last reference.
 
DS_DEF ds_string ds_append (ds_string str, const char *text)
 Append text to a string.
 
DS_DEF ds_string ds_append_char (ds_string str, uint32_t codepoint)
 Append a Unicode codepoint to a string.
 
DS_DEF ds_string ds_prepend (ds_string str, const char *text)
 Prepend text to the beginning of a string.
 
DS_DEF ds_string ds_insert (ds_string str, size_t index, const char *text)
 Insert text at a specific position in a string.
 
DS_DEF ds_string ds_substring (ds_string str, size_t start, size_t len)
 Extract a substring from a string.
 
DS_DEF ds_string ds_concat (ds_string a, ds_string b)
 Concatenate two strings.
 
DS_DEF ds_string ds_join (ds_string *strings, size_t count, const char *separator)
 Join multiple strings with a separator.
 
DS_DEF size_t ds_length (ds_string str)
 Get the length of a string in bytes.
 
DS_DEF int ds_compare (ds_string a, ds_string b)
 Compare two strings lexicographically.
 
DS_DEF int ds_compare_ignore_case (ds_string a, ds_string b)
 Compare two strings lexicographically (case-insensitive)
 
DS_DEF size_t ds_hash (ds_string str)
 Calculate hash value for string.
 
DS_DEF int ds_find (ds_string str, const char *needle)
 Find the first occurrence of a substring.
 
DS_DEF int ds_find_last (ds_string str, const char *needle)
 Find the last occurrence of a substring.
 
DS_DEF int ds_contains (ds_string str, const char *needle)
 Check if string contains a substring.
 
DS_DEF int ds_starts_with (ds_string str, const char *prefix)
 Check if string starts with a prefix.
 
DS_DEF int ds_ends_with (ds_string str, const char *suffix)
 Check if string ends with a suffix.
 
DS_DEF ds_string ds_trim (ds_string str)
 Remove whitespace from both ends of a string.
 
DS_DEF ds_string ds_trim_left (ds_string str)
 Remove whitespace from the beginning of a string.
 
DS_DEF ds_string ds_trim_right (ds_string str)
 Remove whitespace from the end of a string.
 
DS_DEF ds_string ds_replace (ds_string str, const char *old, const char *new)
 Replace the first occurrence of a substring.
 
DS_DEF ds_string ds_replace_all (ds_string str, const char *old, const char *new)
 Replace all occurrences of a substring.
 
DS_DEF ds_string ds_to_upper (ds_string str)
 Convert string to uppercase.
 
DS_DEF ds_string ds_to_lower (ds_string str)
 Convert string to lowercase.
 
DS_DEF ds_string ds_repeat (ds_string str, size_t times)
 Repeat a string multiple times.
 
DS_DEF ds_string ds_truncate (ds_string str, size_t max_length, const char *ellipsis)
 Truncate string to maximum length with optional ellipsis.
 
DS_DEF ds_string ds_reverse (ds_string str)
 Reverse a string (Unicode-aware)
 
DS_DEF ds_string ds_pad_left (ds_string str, size_t width, char pad)
 Pad string on the left to reach specified width.
 
DS_DEF ds_string ds_pad_right (ds_string str, size_t width, char pad)
 Pad string on the right to reach specified width.
 
DS_DEF ds_stringds_split (ds_string str, const char *delimiter, size_t *count)
 Split string into array by delimiter.
 
DS_DEF void ds_free_split_result (ds_string *array, size_t count)
 Free the result array from ds_split()
 
DS_DEF ds_string ds_format (const char *fmt,...)
 Create formatted string using printf-style format specifiers.
 
DS_DEF ds_string ds_format_v (const char *fmt, va_list args)
 Create formatted string using printf-style format specifiers (va_list version)
 
DS_DEF ds_string ds_escape_json (ds_string str)
 Escape string for JSON.
 
DS_DEF ds_string ds_unescape_json (ds_string str)
 Unescape JSON string.
 
DS_DEF size_t ds_refcount (ds_string str)
 Get the current reference count of a string.
 
DS_DEF int ds_is_shared (ds_string str)
 Check if a string has multiple references.
 
DS_DEF int ds_is_empty (ds_string str)
 Check if a string is empty.
 
DS_DEF ds_codepoint_iter ds_codepoints (ds_string str)
 Create an iterator for Unicode codepoints in a string.
 
DS_DEF uint32_t ds_iter_next (ds_codepoint_iter *iter)
 Get the next Unicode codepoint from iterator.
 
DS_DEF int ds_iter_has_next (const ds_codepoint_iter *iter)
 Check if iterator has more codepoints.
 
DS_DEF size_t ds_codepoint_length (ds_string str)
 Count the number of Unicode codepoints in a string.
 
DS_DEF uint32_t ds_codepoint_at (ds_string str, size_t index)
 Get Unicode codepoint at specific index.
 
DS_DEF ds_stringbuilder ds_builder_create (void)
 
DS_DEF ds_stringbuilder ds_builder_create_with_capacity (size_t capacity)
 
DS_DEF void ds_builder_destroy (ds_stringbuilder *sb)
 
DS_DEF int ds_builder_append (ds_stringbuilder *sb, const char *text)
 
DS_DEF int ds_builder_append_char (ds_stringbuilder *sb, uint32_t codepoint)
 
DS_DEF int ds_builder_append_string (ds_stringbuilder *sb, ds_string str)
 
DS_DEF int ds_builder_insert (ds_stringbuilder *sb, size_t index, const char *text)
 
DS_DEF void ds_builder_clear (ds_stringbuilder *sb)
 
DS_DEF ds_string ds_builder_to_string (ds_stringbuilder *sb)
 
DS_DEF size_t ds_builder_length (const ds_stringbuilder *sb)
 
DS_DEF size_t ds_builder_capacity (const ds_stringbuilder *sb)
 
DS_DEF const char * ds_builder_cstr (const ds_stringbuilder *sb)
 

Detailed Description

Modern, efficient, single-file string library for C.

Version
0.2.2
Date
Aug 22, 2025

A modern, efficient, single-file string library for C featuring:

Usage

#define DS_IMPLEMENTATION
#include "dynamic_string.h"
int main() {
ds_string greeting = ds_new("Hello");
ds_string full = ds_append(greeting, " World!");
printf("%s\n", full); // Direct usage - no ds_cstr() needed!
ds_release(&greeting);
ds_release(&full);
return 0;
}
Modern, efficient, single-file string library for C.
char * ds_string
String handle - points directly to null-terminated string data.
Definition dynamic_string.h:125
DS_DEF ds_string ds_append(ds_string str, const char *text)
Append text to a string.
DS_DEF ds_string ds_new(const char *text)
Create a new string from a C string.
DS_DEF void ds_release(ds_string *str)
Decrement reference count and free memory if last reference.

License

Dual licensed under your choice of:

Macro Definition Documentation

◆ DS_ASSERT

#define DS_ASSERT   assert

◆ DS_ATOMIC_FETCH_ADD

#define DS_ATOMIC_FETCH_ADD (   ptr,
  val 
)    (*(ptr) += (val), *(ptr) - (val))

◆ DS_ATOMIC_FETCH_SUB

#define DS_ATOMIC_FETCH_SUB (   ptr,
  val 
)    (*(ptr) -= (val), *(ptr) + (val))

◆ DS_ATOMIC_LOAD

#define DS_ATOMIC_LOAD (   ptr)    (*(ptr))

◆ DS_ATOMIC_REFCOUNT

#define DS_ATOMIC_REFCOUNT   0

Enable atomic reference counting (default: 0)

Note
Requires C11 and stdatomic.h support
Only reference counting is atomic/thread-safe, not string operations
Warning
String modifications still require external synchronization

◆ DS_ATOMIC_SIZE_T

#define DS_ATOMIC_SIZE_T   size_t

◆ DS_ATOMIC_STORE

#define DS_ATOMIC_STORE (   ptr,
  val 
)    (*(ptr) = (val))

◆ DS_DEF

#define DS_DEF   extern

◆ ds_empty

#define ds_empty ( )    ds_new("")

◆ DS_FREE

#define DS_FREE   free

◆ ds_from_literal

#define ds_from_literal (   lit)    ds_new(lit)

◆ DS_MALLOC

#define DS_MALLOC   malloc

◆ DS_REALLOC

#define DS_REALLOC   realloc

Typedef Documentation

◆ ds_string

typedef char* ds_string

String handle - points directly to null-terminated string data.

This is a char* that points directly to UTF-8 string data. Metadata (refcount, length) is stored at negative offsets before the string data. This allows ds_string to be used directly with all C string functions.

Memory layout: [refcount|length|string_data|\0] ^ ds_string points here

Note
Use directly with printf, strcmp, fopen, etc. - no conversion needed!
Warning
NULL ds_string parameters cause assertion failures - all functions require valid strings

Function Documentation

◆ ds_append()

DS_DEF ds_string ds_append ( ds_string  str,
const char *  text 
)

Append text to a string.

Parameters
strSource string (must not be NULL)
textText to append (must not be NULL)
Returns
New string with appended text, or NULL on failure

◆ ds_append_char()

DS_DEF ds_string ds_append_char ( ds_string  str,
uint32_t  codepoint 
)

Append a Unicode codepoint to a string.

Parameters
strSource string (may be NULL)
codepointUnicode codepoint to append (invalid codepoints become U+FFFD)
Returns
New string with appended text, retained original if text is NULL/empty, or NULL if allocation fails

◆ ds_builder_append()

DS_DEF int ds_builder_append ( ds_stringbuilder sb,
const char *  text 
)

◆ ds_builder_append_char()

DS_DEF int ds_builder_append_char ( ds_stringbuilder sb,
uint32_t  codepoint 
)

◆ ds_builder_append_string()

DS_DEF int ds_builder_append_string ( ds_stringbuilder sb,
ds_string  str 
)

◆ ds_builder_capacity()

DS_DEF size_t ds_builder_capacity ( const ds_stringbuilder sb)

◆ ds_builder_clear()

DS_DEF void ds_builder_clear ( ds_stringbuilder sb)

◆ ds_builder_create()

DS_DEF ds_stringbuilder ds_builder_create ( void  )

◆ ds_builder_create_with_capacity()

DS_DEF ds_stringbuilder ds_builder_create_with_capacity ( size_t  capacity)

◆ ds_builder_cstr()

DS_DEF const char * ds_builder_cstr ( const ds_stringbuilder sb)

◆ ds_builder_destroy()

DS_DEF void ds_builder_destroy ( ds_stringbuilder sb)

◆ ds_builder_insert()

DS_DEF int ds_builder_insert ( ds_stringbuilder sb,
size_t  index,
const char *  text 
)

◆ ds_builder_length()

DS_DEF size_t ds_builder_length ( const ds_stringbuilder sb)

◆ ds_builder_to_string()

DS_DEF ds_string ds_builder_to_string ( ds_stringbuilder sb)

◆ ds_codepoint_at()

DS_DEF uint32_t ds_codepoint_at ( ds_string  str,
size_t  index 
)

Get Unicode codepoint at specific index.

Parameters
strString to access (may be NULL)
indexCodepoint index (0-based)
Returns
Codepoint at index, or 0 if index is out of bounds

◆ ds_codepoint_length()

DS_DEF size_t ds_codepoint_length ( ds_string  str)

Count the number of Unicode codepoints in a string.

Parameters
strString to count (may be NULL)
Returns
Number of codepoints, or 0 if str is NULL

◆ ds_codepoints()

DS_DEF ds_codepoint_iter ds_codepoints ( ds_string  str)

Create an iterator for Unicode codepoints in a string.

Parameters
strString to iterate over (may be NULL)
Returns
Iterator positioned at start of string

◆ ds_compare()

DS_DEF int ds_compare ( ds_string  a,
ds_string  b 
)

Compare two strings lexicographically.

Parameters
aFirst string (must not be NULL)
bSecond string (must not be NULL)
Returns
<0 if a < b, 0 if a == b, >0 if a > b

◆ ds_compare_ignore_case()

DS_DEF int ds_compare_ignore_case ( ds_string  a,
ds_string  b 
)

Compare two strings lexicographically (case-insensitive)

Parameters
aFirst string (may be NULL)
bSecond string (may be NULL)
Returns
<0 if a < b, 0 if a == b, >0 if a > b

◆ ds_concat()

DS_DEF ds_string ds_concat ( ds_string  a,
ds_string  b 
)

Concatenate two strings.

Parameters
aFirst string (may be NULL)
bSecond string (may be NULL)
Returns
New string containing a + b, or NULL if both inputs are null

◆ ds_contains()

DS_DEF int ds_contains ( ds_string  str,
const char *  needle 
)

Check if string contains a substring.

Parameters
strString to search in (may be NULL)
needleSubstring to search for (may be NULL)
Returns
1 if found, 0 otherwise

◆ ds_ends_with()

DS_DEF int ds_ends_with ( ds_string  str,
const char *  suffix 
)

Check if string ends with a suffix.

Parameters
strString to check (may be NULL)
suffixSuffix to look for (may be NULL)
Returns
1 if str ends with suffix, 0 otherwise

◆ ds_escape_json()

DS_DEF ds_string ds_escape_json ( ds_string  str)

Escape string for JSON.

Parameters
strString to escape (may be NULL)
Returns
New escaped string suitable for JSON, or NULL if str is NULL

◆ ds_find()

DS_DEF int ds_find ( ds_string  str,
const char *  needle 
)

Find the first occurrence of a substring.

Parameters
strString to search in (may be NULL)
needleSubstring to search for (may be NULL)
Returns
Index of first occurrence, or -1 if not found

◆ ds_find_last()

DS_DEF int ds_find_last ( ds_string  str,
const char *  needle 
)

Find the last occurrence of a substring.

Parameters
strString to search in (may be NULL)
needleSubstring to search for (may be NULL)
Returns
Index of last occurrence, or -1 if not found

◆ ds_format()

DS_DEF ds_string ds_format ( const char *  fmt,
  ... 
)

Create formatted string using printf-style format specifiers.

Parameters
fmtFormat string (may be NULL)
...Arguments for format specifiers
Returns
New formatted string, or NULL if fmt is NULL or formatting fails

◆ ds_format_v()

DS_DEF ds_string ds_format_v ( const char *  fmt,
va_list  args 
)

Create formatted string using printf-style format specifiers (va_list version)

Parameters
fmtFormat string (may be NULL)
argsVariable argument list
Returns
New formatted string, or NULL if fmt is NULL or formatting fails

◆ ds_free_split_result()

DS_DEF void ds_free_split_result ( ds_string array,
size_t  count 
)

Free the result array from ds_split()

Parameters
arrayArray returned by ds_split() (may be NULL)
countNumber of elements in array

◆ ds_hash()

DS_DEF size_t ds_hash ( ds_string  str)

Calculate hash value for string.

Parameters
strString to hash (may be NULL)
Returns
Hash value (0 if str is NULL)
Note
Uses FNV-1a hash algorithm

◆ ds_insert()

DS_DEF ds_string ds_insert ( ds_string  str,
size_t  index,
const char *  text 
)

Insert text at a specific position in a string.

Parameters
strSource string (may be NULL)
indexByte position where to insert text (0-based)
textText to insert (may be NULL)
Returns
New string with inserted text, or original string if index is invalid, or NULL on allocation failure

◆ ds_is_empty()

DS_DEF int ds_is_empty ( ds_string  str)

Check if a string is empty.

Parameters
strString to check (may be NULL)
Returns
1 if string is NULL or has zero length, 0 otherwise

◆ ds_is_shared()

DS_DEF int ds_is_shared ( ds_string  str)

Check if a string has multiple references.

Parameters
strString to check (may be NULL)
Returns
1 if shared (refcount > 1), 0 otherwise

◆ ds_iter_has_next()

DS_DEF int ds_iter_has_next ( const ds_codepoint_iter iter)

Check if iterator has more codepoints.

Parameters
iterIterator to check (may be NULL)
Returns
1 if more codepoints available, 0 otherwise

◆ ds_iter_next()

DS_DEF uint32_t ds_iter_next ( ds_codepoint_iter iter)

Get the next Unicode codepoint from iterator.

Parameters
iterIterator to advance (must not be NULL)
Returns
Next codepoint, or 0 if at end

◆ ds_join()

DS_DEF ds_string ds_join ( ds_string strings,
size_t  count,
const char *  separator 
)

Join multiple strings with a separator.

Parameters
stringsArray of ds_string to join (may contain NULL entries)
countNumber of strings in the array
separatorSeparator to insert between strings (may be NULL)
Returns
New string with all strings joined, or empty string if count is 0

◆ ds_length()

DS_DEF size_t ds_length ( ds_string  str)

Get the length of a string in bytes.

Parameters
strString to measure (must not be NULL)
Returns
Length in bytes

◆ ds_pad_left()

DS_DEF ds_string ds_pad_left ( ds_string  str,
size_t  width,
char  pad 
)

Pad string on the left to reach specified width.

Parameters
strString to pad (may be NULL)
widthTarget width in characters
padCharacter to use for padding
Returns
New string padded to width, or retained original if already wide enough

◆ ds_pad_right()

DS_DEF ds_string ds_pad_right ( ds_string  str,
size_t  width,
char  pad 
)

Pad string on the right to reach specified width.

Parameters
strString to pad (may be NULL)
widthTarget width in characters
padCharacter to use for padding
Returns
New string padded to width, or retained original if already wide enough

◆ ds_prepend()

DS_DEF ds_string ds_prepend ( ds_string  str,
const char *  text 
)

Prepend text to the beginning of a string.

Parameters
strSource string (may be NULL)
textText to prepend (may be NULL)
Returns
New string with prepended text, or NULL on failure

◆ ds_refcount()

DS_DEF size_t ds_refcount ( ds_string  str)

Get the current reference count of a string.

Parameters
strString to inspect (may be NULL)
Returns
Reference count, or 0 if str is NULL

◆ ds_repeat()

DS_DEF ds_string ds_repeat ( ds_string  str,
size_t  times 
)

Repeat a string multiple times.

Parameters
strString to repeat (may be NULL)
timesNumber of repetitions
Returns
New string with content repeated, or empty string if times is 0

◆ ds_replace()

DS_DEF ds_string ds_replace ( ds_string  str,
const char *  old,
const char *  new 
)

Replace the first occurrence of a substring.

Parameters
strSource string (may be NULL)
oldSubstring to replace (may be NULL)
newReplacement text (may be NULL)
Returns
New string with first occurrence replaced, or retained original if no match found

◆ ds_replace_all()

DS_DEF ds_string ds_replace_all ( ds_string  str,
const char *  old,
const char *  new 
)

Replace all occurrences of a substring.

Parameters
strSource string (may be NULL)
oldSubstring to replace (may be NULL)
newReplacement text (may be NULL)
Returns
New string with all occurrences replaced, or retained original if no matches found

◆ ds_reverse()

DS_DEF ds_string ds_reverse ( ds_string  str)

Reverse a string (Unicode-aware)

Parameters
strString to reverse (may be NULL)
Returns
New string with characters in reverse order, preserving Unicode codepoints

◆ ds_split()

DS_DEF ds_string * ds_split ( ds_string  str,
const char *  delimiter,
size_t *  count 
)

Split string into array by delimiter.

Parameters
strString to split (may be NULL)
delimiterDelimiter to split on (may be NULL)
countOutput parameter for number of parts (may be NULL)
Returns
Allocated array of ds_string parts, or NULL on failure
Since
0.0.2
Warning
Caller MUST call ds_free_split_result() to free the returned array
Note
Empty delimiter splits string into individual characters
Consecutive delimiters create empty string parts
ds_string text = ds_new("apple,banana,cherry");
size_t count;
ds_string* parts = ds_split(text, ",", &count);
for (size_t i = 0; i < count; i++) {
printf("Part %zu: %s\n", i, parts[i]);
}
ds_free_split_result(parts, count); // REQUIRED!
ds_release(&text);
DS_DEF void ds_free_split_result(ds_string *array, size_t count)
Free the result array from ds_split()
DS_DEF ds_string * ds_split(ds_string str, const char *delimiter, size_t *count)
Split string into array by delimiter.
See also
ds_free_split_result() for proper cleanup
ds_join() for the reverse operation @performance O(n) where n is string length

◆ ds_starts_with()

DS_DEF int ds_starts_with ( ds_string  str,
const char *  prefix 
)

Check if string starts with a prefix.

Parameters
strString to check (may be NULL)
prefixPrefix to look for (may be NULL)
Returns
1 if str starts with prefix, 0 otherwise

◆ ds_substring()

DS_DEF ds_string ds_substring ( ds_string  str,
size_t  start,
size_t  len 
)

Extract a substring from a string.

Parameters
strSource string (may be NULL)
startStarting byte position (0-based)
lenNumber of bytes to include in substring
Returns
New string containing the substring, or empty string if invalid range

◆ ds_to_lower()

DS_DEF ds_string ds_to_lower ( ds_string  str)

Convert string to lowercase.

Parameters
strString to convert (may be NULL)
Returns
New string in lowercase, or retained original if empty/NULL

◆ ds_to_upper()

DS_DEF ds_string ds_to_upper ( ds_string  str)

Convert string to uppercase.

Parameters
strString to convert (may be NULL)
Returns
New string in uppercase, or retained original if empty/NULL

◆ ds_trim()

DS_DEF ds_string ds_trim ( ds_string  str)

Remove whitespace from both ends of a string.

Parameters
strString to trim (may be NULL)
Returns
New string with whitespace removed, or retained original if no trimming needed
Since
0.0.2
ds_string padded = ds_new(" hello world ");
ds_string clean = ds_trim(padded);
printf("'%s'\n", clean); // 'hello world'
ds_release(&padded);
ds_release(&clean);
DS_DEF ds_string ds_trim(ds_string str)
Remove whitespace from both ends of a string.
See also
ds_trim_left() for trimming only leading whitespace
ds_trim_right() for trimming only trailing whitespace @performance O(n) where n is string length

◆ ds_trim_left()

DS_DEF ds_string ds_trim_left ( ds_string  str)

Remove whitespace from the beginning of a string.

Parameters
strString to trim (may be NULL)
Returns
New string with leading whitespace removed, or retained original if no trimming needed

◆ ds_trim_right()

DS_DEF ds_string ds_trim_right ( ds_string  str)

Remove whitespace from the end of a string.

Parameters
strString to trim (may be NULL)
Returns
New string with trailing whitespace removed, or retained original if no trimming needed

◆ ds_truncate()

DS_DEF ds_string ds_truncate ( ds_string  str,
size_t  max_length,
const char *  ellipsis 
)

Truncate string to maximum length with optional ellipsis.

Parameters
strString to truncate (may be NULL)
max_lengthMaximum length in bytes (not including ellipsis)
ellipsisEllipsis string to append if truncated (may be NULL)
Returns
New string truncated to max_length, or retained original if already short enough

◆ ds_unescape_json()

DS_DEF ds_string ds_unescape_json ( ds_string  str)

Unescape JSON string.

Parameters
strJSON string to unescape (may be NULL)
Returns
New unescaped string, or NULL if str is NULL or invalid JSON