From: Petri Lehtinen Date: Thu, 4 Feb 2010 19:13:57 +0000 (+0200) Subject: Merge branch '1.2' X-Git-Tag: v1.3~15 X-Git-Url: http://www.project-moonshot.org/gitweb/?p=jansson.git;a=commitdiff_plain;h=8d75235ff22dc4aced697e198c3c024f1f4b88fe;hp=08dc8d9bafcb13bb1541894918f4a394e644e6e1 Merge branch '1.2' Conflicts: LICENSE --- diff --git a/LICENSE b/LICENSE index 552b349..b5c2887 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,5 @@ Copyright (c) 2009, 2010 Petri Lehtinen +Copyright (c) 2010 Sean Middleditch Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/configure.ac b/configure.ac index 95a207e..5290793 100644 --- a/configure.ac +++ b/configure.ac @@ -8,6 +8,7 @@ AC_CONFIG_HEADERS([config.h]) # Checks for programs. AC_PROG_CC +AC_PROG_CXX AC_PROG_LIBTOOL # Checks for libraries. diff --git a/doc/apiref.rst b/doc/apiref.rst index a2a0794..0a96999 100644 --- a/doc/apiref.rst +++ b/doc/apiref.rst @@ -493,6 +493,16 @@ The following functions implement an iteration protocol for objects: Returns an opaque iterator which can be used to iterate over all key-value pairs in *object*, or *NULL* if *object* is empty. +.. cfunction:: void *json_object_iter_at(json_t *object, const char *key) + + Like :cfunc:`json_object_iter()`, but returns an iterator to the + key-value pair in *object* whose key is equal to *key*, or NULL if + *key* is not found in *object*. Iterating forward to the end of + *object* only yields all key-value pairs of the object if *key* + happens to be the first key in the underlying hash table. + + .. versionadded:: 1.3 + .. cfunction:: void *json_object_iter_next(json_t *object, void *iter) Returns an iterator pointing to the next key-value pair in *object* @@ -509,6 +519,21 @@ The following functions implement an iteration protocol for objects: Extract the associated value from *iter*. +.. cfunction:: int json_object_iter_set(json_t *object, void *iter, json_t *value) + + Set the value of the key-value pair in *object*, that is pointed to + by *iter*, to *value*. + + .. versionadded:: 1.3 + +.. cfunction:: int json_object_iter_set_new(json_t *object, void *iter, json_t *value) + + Like :cfunc:`json_object_iter_set()`, but steals the reference to + *value*. This is useful when *value* is newly created and not used + after the call. + + .. versionadded:: 1.3 + The iteration protocol can be used for example as follows:: /* obj is a JSON object */ diff --git a/src/Makefile.am b/src/Makefile.am index e70077b..92fc90c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,4 +1,4 @@ -include_HEADERS = jansson.h +include_HEADERS = jansson.h jansson.hpp jansson.ipp lib_LTLIBRARIES = libjansson.la libjansson_la_SOURCES = \ diff --git a/src/hashtable.c b/src/hashtable.c index 1f8abf4..0d4a9f2 100644 --- a/src/hashtable.c +++ b/src/hashtable.c @@ -318,6 +318,22 @@ void *hashtable_iter(hashtable_t *hashtable) return hashtable_iter_next(hashtable, &hashtable->list); } +void *hashtable_iter_at(hashtable_t *hashtable, const void *key) +{ + pair_t *pair; + unsigned int hash; + bucket_t *bucket; + + hash = hashtable->hash_key(key); + bucket = &hashtable->buckets[hash % num_buckets(hashtable)]; + + pair = hashtable_find_pair(hashtable, bucket, key, hash); + if(!pair) + return NULL; + + return &pair->list; +} + void *hashtable_iter_next(hashtable_t *hashtable, void *iter) { list_t *list = (list_t *)iter; @@ -337,3 +353,13 @@ void *hashtable_iter_value(void *iter) pair_t *pair = list_to_pair((list_t *)iter); return pair->value; } + +void hashtable_iter_set(hashtable_t *hashtable, void *iter, void *value) +{ + pair_t *pair = list_to_pair((list_t *)iter); + + if(hashtable->free_value) + hashtable->free_value(pair->value); + + pair->value = value; +} diff --git a/src/hashtable.h b/src/hashtable.h index e055d9f..6d3c736 100644 --- a/src/hashtable.h +++ b/src/hashtable.h @@ -161,6 +161,17 @@ void hashtable_clear(hashtable_t *hashtable); void *hashtable_iter(hashtable_t *hashtable); /** + * hashtable_iter - Return an iterator at a specific key + * + * @hashtable: The hashtable object + * @key: The key that the iterator should point to + * + * Like hashtable_iter() but returns an iterator pointing to a + * specific key. + */ +void *hashtable_iter_at(hashtable_t *hashtable, const void *key); + +/** * hashtable_iter_next - Advance an iterator * * @hashtable: The hashtable object @@ -185,4 +196,12 @@ void *hashtable_iter_key(void *iter); */ void *hashtable_iter_value(void *iter); +/** + * hashtable_iter_set - Set the value pointed by an iterator + * + * @iter: The iterator + * @value: The value to set + */ +void hashtable_iter_set(hashtable_t *hashtable, void *iter, void *value); + #endif diff --git a/src/jansson.h b/src/jansson.h index 78094a7..55dce0f 100644 --- a/src/jansson.h +++ b/src/jansson.h @@ -83,9 +83,11 @@ int json_object_del(json_t *object, const char *key); int json_object_clear(json_t *object); int json_object_update(json_t *object, json_t *other); void *json_object_iter(json_t *object); +void *json_object_iter_at(json_t *object, const char *key); void *json_object_iter_next(json_t *object, void *iter); const char *json_object_iter_key(void *iter); json_t *json_object_iter_value(void *iter); +int json_object_iter_set_new(json_t *object, void *iter, json_t *value); static inline int json_object_set(json_t *object, const char *key, json_t *value) @@ -99,6 +101,12 @@ int json_object_set_nocheck(json_t *object, const char *key, json_t *value) return json_object_set_new_nocheck(object, key, json_incref(value)); } +static inline +int json_object_iter_set(json_t *object, void *iter, json_t *value) +{ + return json_object_iter_set_new(object, iter, json_incref(value)); +} + unsigned int json_array_size(const json_t *array); json_t *json_array_get(const json_t *array, unsigned int index); int json_array_set_new(json_t *array, unsigned int index, json_t *value); diff --git a/src/jansson.hpp b/src/jansson.hpp new file mode 100644 index 0000000..bf723bd --- /dev/null +++ b/src/jansson.hpp @@ -0,0 +1,312 @@ +// Copyright (c) 2010 Sean Middleditch +// Copyright (c) 2010 Petri Lehtinen +// +// Jansson is free software; you can redistribute it and/or modify +// it under the terms of the MIT license. See LICENSE for details. + +#ifndef JANSSON_HPP +#define JANSSON_HPP + +#include +#include +#include +#include + +// Included so that standard functions don't end up in namespace json +#include + +// For free() +#include + +namespace json { + // include Jansson C library into the json namespace +# include + + class Iterator; + class Value; + + // implementation details; do not use directly + namespace detail { + class ElementProxy; + class PropertyProxy; + + // base class for JSON value interface + template + class ValueBase : public _Base { + public: + // empty constructor + ValueBase() : _Base() {} + + // copy constructor + ValueBase(const _Base& base) : _Base(base) {} + + // create reference to value + ValueBase(json_t* json) : _Base(json) {} + + // assignment operator + inline ValueBase& operator=(const Value& value); + + // check value type + inline bool is_undefined() const; + inline bool is_object() const; + inline bool is_array() const; + inline bool is_string() const; + inline bool is_integer() const; + inline bool is_real() const; + inline bool is_number() const; + inline bool is_true() const; + inline bool is_false() const; + inline bool is_boolean() const; + inline bool is_null() const; + + // get size of array or object + inline unsigned int size() const; + + // get value at array index (const version) + inline const Value at(unsigned int index) const; + + inline const Value operator[](signed int index) const; + inline const Value operator[](unsigned int index) const; + inline const Value operator[](signed short index) const; + inline const Value operator[](unsigned short index) const; + inline const Value operator[](signed long index) const; + inline const Value operator[](unsigned long index) const; + + // get value at array index (non-const version) + inline ValueBase at(unsigned int index); + + inline ValueBase operator[](signed int index); + inline ValueBase operator[](unsigned int index); + inline ValueBase operator[](signed short index); + inline ValueBase operator[](unsigned short index); + inline ValueBase operator[](signed long index); + inline ValueBase operator[](unsigned long index); + + // get object property (const version) + inline const Value get(const char* key) const; + + inline const Value get(const std::string& key) const; + inline const Value operator[](const char* key) const; + inline const Value operator[](const std::string& key) const; + + // get object property (non-const version) + inline ValueBase get(const char* key); + + inline ValueBase get(const std::string& key); + inline ValueBase operator[](const char* key); + inline ValueBase operator[](const std::string& key); + + // clear all array/object values + inline void clear(); + + // get value cast to specified type + inline const char* as_cstring() const; + inline std::string as_string() const; + inline int as_integer() const; + inline double as_real() const; + inline double as_number() const; + inline bool as_boolean() const; + + // set an object property (converts value to object is not one already) + inline _Base& set_key(const char* key, const Value& value); + + inline _Base& set_key(const std::string& key, const Value& value); + + // set an array index (converts value to object is not one already) + inline _Base& set_at(unsigned int index, const Value& value); + + // delete an object key + inline _Base& del_key(const char* key); + + inline _Base& del_key(const std::string& key); + + // delete an item from an array by index + inline _Base& del_at(unsigned int index); + + // insert an item into an array at a given index + inline _Base& insert_at(unsigned int index, const Value& value); + + // write the value to a file + inline int dump_file(const char* path, int flags = 0) const; + inline int dump_file(const std::string& path, int flags = 0) const; + + // write the value to a string (caller must deallocate with free()!) + inline char* dumps(int flags = 0) const; + }; + + // represents any JSON value, private base + class Basic { + public: + // construct new Value with an undefined value + Basic() : _value(0) {} + + // copy constructor + Basic(const Basic& value) : _value(json_incref(value._value)) {} + + // make a reference to an existing json_t value + explicit Basic(json_t* value) : _value(json_incref(value)) {} + + // free Value resources + inline ~Basic(); + + // copy an existing Value + inline Basic& operator=(const Basic& e); + + // get the underlying json_t + inline json_t* as_json() const; + + // take ownership of a json_t (does not increase reference count) + inline static Basic take_ownership(json_t* json); + + protected: + // internal value pointer + json_t* _value; + }; + + // proxies an array element + class ElementProxy { + public: + // constructor + ElementProxy(json_t* array, unsigned int index) : _array(array), _index(index) {} + + // assign to the proxied element + inline ElementProxy& operator=(const Value& value); + + // get the proxied element + inline json_t* as_json() const; + + private: + // array object we wrap + json_t* _array; + + // index of property + unsigned int _index; + }; + + // proxies an object property + class PropertyProxy { + public: + // constructor + PropertyProxy(json_t* array, const char* key) : _object(array), _key(key) {} + + // assign to the proxied element + inline PropertyProxy& operator=(const Value& value); + + // get the proxied element + inline json_t* as_json() const; + + private: + // array object we wrap + json_t* _object; + + // key of property + const char* _key; + }; + + } // namespace json::detail + + // represents any JSON value + class Value : public detail::ValueBase { + public: + // construct Value from input + explicit inline Value(const char* value); + explicit inline Value(const std::string& value); + explicit inline Value(bool value); + explicit inline Value(signed int value); + explicit inline Value(unsigned int value); + explicit inline Value(signed short value); + explicit inline Value(unsigned short value); + explicit inline Value(signed long value); + explicit inline Value(unsigned long value); + explicit inline Value(float value); + explicit inline Value(double value); + + // empty constructor + Value() : detail::ValueBase() {} + + // copy constructor for base + Value(const detail::Basic& value) : detail::ValueBase(value) {} + + // copy constructor for base + Value(const detail::ValueBase& value) : detail::ValueBase(value) {} + + // copy constructor + Value(const Value& value) : detail::ValueBase(value) {} + + // create reference to value + explicit Value(json_t* json) : detail::ValueBase(json) {} + }; + + // iterators over a JSON object + class Iterator { + public: + // construct a new iterator for a given object + inline Iterator(const Value& value); + + // construct a new iterator for a given object + inline Iterator(const detail::ValueBase& value); + + // increment iterator + inline void next(); + + inline Iterator& operator++(); + + // test if iterator is still valid + inline bool valid() const; + + inline operator bool() const; + + // get key + inline const char* ckey() const; + + inline std::string key() const; + + // get value + inline const Value value() const; + + // dereference value + inline const Value operator*() const; + + private: + // disallow copying + Iterator(const Iterator&); + Iterator& operator=(const Iterator&); + + // object being iterated over + Value _object; + + // iterator value + void* _iter; + }; + + // create a new empty object + inline Value object(); + + // create a new empty array + inline Value array(); + + // create a new null value + inline Value null(); + + // load a file as a JSON value + inline Value load_file(const char* path, json_error_t* error = 0); + inline Value load_file(const std::string& path, json_error_t* error = 0); + + // load a string as a JSON value + inline Value loads(const char* string, json_error_t* error = 0); + inline Value loads(const std::string& string, json_error_t* error = 0); + +} // namespace json + +// stream JSON value out -- inefficient and not recommended for production use +inline std::ostream& operator<<(std::ostream& os, const json::Value& value); + +// read JSON value -- inefficient and not recommended for production use +inline std::istream& operator>>(std::istream& is, json::Value& value); + +// include implementation code +#define IN_JANSSON_HPP +#include "jansson.ipp" +#undef IN_JANSSON_HPP + +#endif // defined(JANSSON_HPP) diff --git a/src/jansson.ipp b/src/jansson.ipp new file mode 100644 index 0000000..5938f0f --- /dev/null +++ b/src/jansson.ipp @@ -0,0 +1,483 @@ +// Copyright (c) 2010 Sean Middleditch +// Copyright (c) 2010 Petri Lehtinen +// +// Jansson is free software; you can redistribute it and/or modify +// it under the terms of the MIT license. See LICENSE for details. + +#ifndef IN_JANSSON_HPP +#error "jansson.ipp may only be included from jansson.hpp" +#endif + +namespace json { + namespace detail { + // assignment operator + template + ValueBase<_Base>& ValueBase<_Base>::operator=(const Value& value) { + _Base::operator=(value); + return *this; + } + + // check value type + template + bool ValueBase<_Base>::is_undefined() const { + return _Base::as_json() == 0; + } + + template + bool ValueBase<_Base>::is_object() const { + return json_is_object(_Base::as_json()); + } + + template + bool ValueBase<_Base>::is_array() const { + return json_is_array(_Base::as_json()); + } + + template + bool ValueBase<_Base>::is_string() const { + return json_is_string(_Base::as_json()); + } + + template + bool ValueBase<_Base>::is_integer() const { + return json_is_integer(_Base::as_json()); + } + + template + bool ValueBase<_Base>::is_real() const { + return json_is_real(_Base::as_json()); + } + + template + bool ValueBase<_Base>::is_number() const { + return json_is_number(_Base::as_json()); + } + + template + bool ValueBase<_Base>::is_true() const { + return json_is_true(_Base::as_json()); + } + + template + bool ValueBase<_Base>::is_false() const { + return json_is_false(_Base::as_json()); + } + + template + bool ValueBase<_Base>::is_boolean() const { + return json_is_boolean(_Base::as_json()); + } + + template + bool ValueBase<_Base>::is_null() const { + return json_is_null(_Base::as_json()); + } + + // get size of array or object + template + unsigned int ValueBase<_Base>::size() const { + if (is_object()) + return json_object_size(_Base::as_json()); + else + return json_array_size(_Base::as_json()); + } + + // get value at array index (const version) + template + const Value ValueBase<_Base>::at(unsigned int index) const { + return Value(json_array_get(_Base::as_json(), index)); + } + + template + const Value ValueBase<_Base>::operator[](signed int index) const { return at(index); } + template + const Value ValueBase<_Base>::operator[](unsigned int index) const { return at(index); } + template + const Value ValueBase<_Base>::operator[](signed short index) const { return at(index); } + template + const Value ValueBase<_Base>::operator[](unsigned short index) const { return at(index); } + template + const Value ValueBase<_Base>::operator[](signed long index) const { return at(index); } + template + const Value ValueBase<_Base>::operator[](unsigned long index) const { return at(index); } + + // get value at array index (non-const version) + template + ValueBase ValueBase<_Base>::at(unsigned int index) { + return ElementProxy(_Base::as_json(), index); + } + + template + ValueBase ValueBase<_Base>::operator[](signed int index) { + return at(index); + } + + template + ValueBase ValueBase<_Base>::operator[](unsigned int index) { + return at(index); + } + + template + ValueBase ValueBase<_Base>::operator[](signed short index) { + return at(index); + } + + template + ValueBase ValueBase<_Base>::operator[](unsigned short index) { + return at(index); + } + + template + ValueBase ValueBase<_Base>::operator[](signed long index) { + return at(index); + } + + template + ValueBase ValueBase<_Base>::operator[](unsigned long index) { + return at(index); + } + + // get object property (const version) + template + const Value ValueBase<_Base>::get(const char* key) const { + return Value(json_object_get(_Base::as_json(), key)); + } + + template + const Value ValueBase<_Base>::get(const std::string& key) const { + return get(key.c_str()); + } + + template + const Value ValueBase<_Base>::operator[](const char* key) const { + return get(key); + } + + template + const Value ValueBase<_Base>::operator[](const std::string& key) const { + return get(key.c_str()); + } + + // get object property (non-const version) + template + ValueBase ValueBase<_Base>::get(const char* key) { + return PropertyProxy(_Base::as_json(), key); + } + + template + ValueBase ValueBase<_Base>::get(const std::string& key) { + return get(key.c_str()); + } + + template + ValueBase ValueBase<_Base>::operator[](const char* key) { + return get(key); + } + + template + ValueBase ValueBase<_Base>::operator[](const std::string& key) { + return get(key.c_str()); + } + + // clear all array/object values + template + void ValueBase<_Base>::clear() { + if (is_object()) + json_object_clear(_Base::as_json()); + else + json_array_clear(_Base::as_json()); + } + + // get value cast to specified type + template + const char* ValueBase<_Base>::as_cstring() const { + return json_string_value(_Base::as_json()); + } + + template + std::string ValueBase<_Base>::as_string() const { + const char* tmp = as_cstring(); + return tmp == 0 ? "" : tmp; + } + + template + int ValueBase<_Base>::as_integer() const { + return json_integer_value(_Base::as_json()); + } + + template + double ValueBase<_Base>::as_real() const { + return json_real_value(_Base::as_json()); + } + + template + double ValueBase<_Base>::as_number() const { + return json_number_value(_Base::as_json()); + } + + template + bool ValueBase<_Base>::as_boolean() const { + return is_true(); + } + + // set an object property (converts value to object is not one already) + template + _Base& ValueBase<_Base>::set_key(const char* key, const Value& value) { + json_object_set(_Base::as_json(), key, value._Base::as_json()); + return *this; + } + + template + _Base& ValueBase<_Base>::set_key(const std::string& key, const Value& value) { + return set_key(key.c_str(), value); + } + + // set an array index (converts value to object is not one already) + template + _Base& ValueBase<_Base>::set_at(unsigned int index, const Value& value) { + if (index == size()) + json_array_append(_Base::as_json(), value._Base::as_json()); + else + json_array_set(_Base::as_json(), index, value._Base::as_json()); + return *this; + } + + // delete an object key + template + _Base& ValueBase<_Base>::del_key(const char* key) { + json_object_del(_Base::as_json(), key); + return *this; + } + + template + _Base& ValueBase<_Base>::del_key(const std::string& key) { + return del_key(key.c_str()); + } + + // delete an item from an array by index + template + _Base& ValueBase<_Base>::del_at(unsigned int index) { + json_array_remove(_Base::as_json(), index); + return *this; + } + + // insert an item into an array at a given index + template + _Base& ValueBase<_Base>::insert_at(unsigned int index, const Value& value) { + json_array_insert(_Base::as_json(), index, value._Base::as_json()); + return *this; + } + + // write the value to a file + template + int ValueBase<_Base>::dump_file(const char* path, int flags) const { + return json_dump_file(_Base::as_json(), path, flags); + } + + template + int ValueBase<_Base>::dump_file(const std::string& path, int flags) const { + return dump_file(path.c_str(), flags); + } + + // write the value to a string (caller must deallocate with free()!) + template + char* ValueBase<_Base>::dumps(int flags) const { + return json_dumps(_Base::as_json(), flags); + } + + Basic::~Basic() { + json_decref(_value); + } + + // copy an existing Value + Basic& Basic::operator=(const Basic& e) { + if (&e != this) { + json_decref(_value); + _value = json_incref(e._value); + } + return *this; + } + + // get the underlying json_t + json_t* Basic::as_json() const { + return _value; + } + + // take ownership of a json_t (does not increase reference count) + Basic Basic::take_ownership(json_t* json) { + Basic v; + v._value = json; + return v; + } + + // assign value to proxied array element + ElementProxy& ElementProxy::operator=(const Value& value) { + json_array_set(_array, _index, value.as_json()); + return *this; + } + + // get the proxied element + json_t* ElementProxy::as_json() const { + return json_array_get(_array, _index); + } + + // assign value to proxied object property + PropertyProxy& PropertyProxy::operator=(const Value& value) { + json_object_set(_object, _key, value.as_json()); + return *this; + } + + json_t* PropertyProxy::as_json() const { + return json_object_get(_object, _key); + } + + } // namespace json::detail + + // construct Value::Value input + Value::Value(const char* value) { + _value = json_string(value); + } + + Value::Value(const std::string& value) { + _value = json_string(value.c_str()); + } + + Value::Value(bool value) { + _value = value ? json_true() : json_false(); + } + + Value::Value(signed int value) { + _value = json_integer(value); + } + + Value::Value(unsigned int value) { + _value = json_integer(value); + } + + Value::Value(signed short value) { + _value = json_integer(value); + } + + Value::Value(unsigned short value) { + _value = json_integer(value); + } + + Value::Value(signed long value) { + _value = json_integer(value); + } + + Value::Value(unsigned long value) { + _value = json_integer(value); + } + + Value::Value(float value) { + _value = json_real(value); + } + + Value::Value(double value) { + _value = json_real(value); + } + + // construct a new iterator for a given object + Iterator::Iterator(const Value& value) : _object(value), _iter(0) { + _iter = json_object_iter(_object.as_json()); + } + + // construct a new iterator for a given object + Iterator::Iterator(const detail::ValueBase& value) : + _object(value.as_json()), _iter(0) { + _iter = json_object_iter(_object.as_json()); + } + + // increment iterator + void Iterator::next() { + _iter = json_object_iter_next(_object.as_json(), _iter); + } + + Iterator& Iterator::operator++() { next(); return *this; } + + // test if iterator is still valid + bool Iterator::valid() const { + return _iter != 0; + } + + Iterator::operator bool() const { + return valid(); + } + + // get key + const char* Iterator::ckey() const { + return json_object_iter_key(_iter); + } + + std::string Iterator::key() const { + return ckey(); + } + + // get value + const Value Iterator::value() const { + return Value(json_object_iter_value(_iter)); + } + + // dereference value + const Value Iterator::operator*() const { + return value(); + } + + // create a new empty object + Value object() { + return Value::take_ownership(json_object()); + } + + // create a new empty array + Value array() { + return Value::take_ownership(json_array()); + } + + // create a new null value + Value null() { + return Value::take_ownership(json_null()); + } + + // load a file as a JSON value + Value load_file(const char* path, json_error_t* error) { + return Value::take_ownership(json_load_file(path, error)); + } + + Value load_file(const std::string& path, json_error_t* error) { + return load_file(path.c_str(), error); + } + + // load a string as a JSON value + Value loads(const char* string, json_error_t* error) { + return Value::take_ownership(json_loads(string, error)); + } + + Value loads(const std::string& string, json_error_t* error) { + return loads(string.c_str(), error); + } + +} // namespace json + +// stream JSON value out +std::ostream& operator<<(std::ostream& os, const json::Value& value) { + // get the temporary serialize string + char* tmp = value.dumps(); + if (tmp != 0) { + // stream temp string out and release it + os << tmp; + free(tmp); + } + return os; +} + +// read JSON value +std::istream& operator>>(std::istream& is, json::Value& value) { + // buffer the remaining bytes into a single string for Jansson + std::stringstream tmp; + while (is) + tmp << static_cast(is.get()); + // parse the buffered string + value = json::loads(tmp.str().c_str()); + return is; +} diff --git a/src/value.c b/src/value.c index 345ff8e..35166f4 100644 --- a/src/value.c +++ b/src/value.c @@ -190,6 +190,17 @@ void *json_object_iter(json_t *json) return hashtable_iter(&object->hashtable); } +void *json_object_iter_at(json_t *json, const char *key) +{ + json_object_t *object; + + if(!key || !json_is_object(json)) + return NULL; + + object = json_to_object(json); + return hashtable_iter_at(&object->hashtable, key); +} + void *json_object_iter_next(json_t *json, void *iter) { json_object_t *object; @@ -217,6 +228,19 @@ json_t *json_object_iter_value(void *iter) return (json_t *)hashtable_iter_value(iter); } +int json_object_iter_set_new(json_t *json, void *iter, json_t *value) +{ + json_object_t *object; + + if(!json_is_object(json) || !iter || !value) + return -1; + + object = json_to_object(json); + hashtable_iter_set(&object->hashtable, iter, value); + + return 0; +} + static int json_object_equal(json_t *object1, json_t *object2) { void *iter; diff --git a/test/.gitignore b/test/.gitignore index a960c50..065b4ea 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -7,3 +7,4 @@ suites/api/test_load suites/api/test_number suites/api/test_object suites/api/test_simple +suites/api/test_cpp diff --git a/test/suites/api/Makefile.am b/test/suites/api/Makefile.am index 64523c0..ddeb147 100644 --- a/test/suites/api/Makefile.am +++ b/test/suites/api/Makefile.am @@ -7,7 +7,8 @@ check_PROGRAMS = \ test_load \ test_simple \ test_number \ - test_object + test_object \ + test_cpp test_array_SOURCES = test_array.c util.h test_copy_SOURCES = test_copy.c util.h @@ -15,6 +16,7 @@ test_load_SOURCES = test_load.c util.h test_simple_SOURCES = test_simple.c util.h test_number_SOURCES = test_number.c util.h test_object_SOURCES = test_object.c util.h +test_cpp_SOURCES = test_cpp.cpp AM_CPPFLAGS = -I$(top_srcdir)/src AM_CFLAGS = -Wall -Werror diff --git a/test/suites/api/check-exports b/test/suites/api/check-exports index 178abeb..a6d877f 100755 --- a/test/suites/api/check-exports +++ b/test/suites/api/check-exports @@ -39,9 +39,11 @@ json_object_del json_object_clear json_object_update json_object_iter +json_object_iter_at json_object_iter_next json_object_iter_key json_object_iter_value +json_object_iter_set_new json_dumps json_dumpf json_dump_file diff --git a/test/suites/api/run b/test/suites/api/run index e01148d..ff53f35 100755 --- a/test/suites/api/run +++ b/test/suites/api/run @@ -6,16 +6,29 @@ # it under the terms of the MIT license. See LICENSE for details. is_test() { - [ "${test_name%.c}" != "$test_name" ] && return 0 - [ -x $test_path -a ! -f $test_path.c ] && return 0 - return 1 + case "$test_name" in + *.c|*.cpp|check-exports) + return 0 + ;; + *) + return 1 + ;; + esac } run_test() { - if [ -x $test_path ]; then + if [ "$test_name" = "check-exports" ]; then test_log=$test_log $test_path >$test_log/stdout 2>$test_log/stderr else - $test_runner $suite_builddir/${test_name%.c} \ + case "$test_name" in + *.c) + test_bin=${test_name%.c} + ;; + *.cpp) + test_bin=${test_name%.cpp} + ;; + esac + $test_runner $suite_builddir/$test_bin \ >$test_log/stdout \ 2>$test_log/stderr \ || return 1 diff --git a/test/suites/api/test_cpp.cpp b/test/suites/api/test_cpp.cpp new file mode 100644 index 0000000..1215e4e --- /dev/null +++ b/test/suites/api/test_cpp.cpp @@ -0,0 +1,162 @@ +#include +#include + +#include "jansson.hpp" + +#define ASSERT_OP(lhs, rhs, op, m) \ + do { \ + if(!((lhs) op (rhs))) { \ + std::cerr << std::boolalpha; \ + std::cerr << __FILE__ << '[' << __LINE__ << "]: ERROR: " \ + << (m) << std::endl; \ + std::cerr << "\ttest: " << #lhs << ' ' << #op << ' ' \ + << #rhs << std::endl; \ + std::cerr << "\tresult: " << (lhs) << ' ' << #op << ' ' \ + << (rhs) << std::endl; \ + return 1; \ + } \ + } while(0) +#define ASSERT_EQ(lhs, rhs, m) ASSERT_OP(lhs, rhs, ==, m) +#define ASSERT_NE(lhs, rhs, m) ASSERT_OP(lhs, rhs, !=, m) +#define ASSERT_TRUE(p, m) ASSERT_OP(p, true, ==, m) +#define ASSERT_FALSE(p, m) ASSERT_OP(p, true, !=, m) + +int main() { + std::string top_srcdir = getenv("top_srcdir"); + json::Value e1(json::load_file(top_srcdir + "/test/suites/api/test_cpp.json")); + json::Value e2(e1); + json::Value e3; + json::Value e4(json::loads("{\"foo\": true, \"bar\": \"test\"}")); + + ASSERT_TRUE(e1.is_object(), "e1 is not an object"); + ASSERT_TRUE(e2.is_object(), "e2 is not an object"); + ASSERT_TRUE(e3.is_undefined(), "e3 has a defined value"); + ASSERT_TRUE(e4.is_object(), "e4 is not an object"); + + ASSERT_EQ(e1.size(), 1, "e1 has too many properties"); + ASSERT_EQ(e2.size(), 1, "e2 has too many properties"); + ASSERT_EQ(e4.size(), 2, "e4 does not have 2 elements"); + + ASSERT_TRUE(e1.get("web-app").is_object(), "e1[0].web-app is not an object"); + ASSERT_EQ(e1.get("web-app").get("servlet").at(0).get("servlet-class").as_string(), "org.cofax.cds.CDSServlet", "property has incorrect value"); + ASSERT_EQ(e1["web-app"]["servlet"][0]["servlet-class"].as_string(), "org.cofax.cds.CDSServlet", "property has incorrect value"); + + ASSERT_EQ(e4["foo"].as_boolean(), true, "property has incorrect value"); + + json::Iterator i(e1.get("web-app")); + ASSERT_EQ(i.key(), "taglib", "first iterator result has incorrect key"); + i.next(); + ASSERT_EQ(i.key(), "servlet", "first iterator result has incorrect key"); + i.next(); + ASSERT_EQ(i.key(), "servlet-mapping", "first iterator result has incorrect key"); + i.next(); + ASSERT_FALSE(i.valid(), "iterator has more values than expected"); + + json::Value e5(json::Value(12.34)); + ASSERT_TRUE(e5.is_number(), "e5 is not a number after assignment"); + ASSERT_EQ(e5.as_real(), 12.34, "e5 has incorrect value after assignment"); + + json::Value e6(json::Value(true)); + ASSERT_TRUE(e6.is_boolean(), "e6 is not a boolean after assignment"); + ASSERT_EQ(e6.as_boolean(), true, "e6 has incorrect value after assignment"); + + json::Value e7(json::Value("foobar")); + ASSERT_TRUE(e7.is_string(), "e7 is not a string after assignment"); + ASSERT_EQ(e7.as_string(), "foobar", "e7 has incorrect value after assignment"); + + json::Value e8(json::object()); + ASSERT_TRUE(e8.is_object(), "e8 is not an object after assignment"); + + json::Value e9(json::null()); + ASSERT_TRUE(e9.is_null(), "e9 is not null after assignment"); + + json::Value e10(json::array()); + ASSERT_TRUE(e10.is_array(), "e10 is not an array after index assignment"); + + e10.set_at(0, json::Value("foobar")); + ASSERT_EQ(e10.size(), 1, "e10 has incorrect number of elements after assignment"); + ASSERT_EQ(e10[0].as_string(), "foobar", "e10[0] has incorrect value after assignment"); + + e10.set_at(1, json::Value("foobar")); + ASSERT_TRUE(e10.is_array(), "e10 is not an array after index assignment"); + ASSERT_EQ(e10.size(), 2, "e10 has incorrect number of elements after assignment"); + ASSERT_EQ(e10[1].as_string(), "foobar", "e10[0] has incorrect value after assignment"); + + e10.set_at(0, json::Value("barfoo")); + ASSERT_TRUE(e10.is_array(), "e10 is not an array after index assignment"); + ASSERT_EQ(e10.size(), 2, "e10 has incorrect number of elements after assignment"); + ASSERT_EQ(e10[0].as_string(), "barfoo", "e10[0] has incorrect value after assignment"); + + e10.set_at(100, json::null()); + ASSERT_TRUE(e10.is_array(), "e10 is not an array after index assignment"); + ASSERT_EQ(e10.size(), 2, "e10 has incorrect number of elements after assignment"); + + e10.insert_at(1, json::Value("new")); + ASSERT_EQ(e10.size(), 3, "e10 has incorrect size after insert"); + ASSERT_EQ(e10[1].as_string(), "new", "e10[1] has incorrect value after insert"); + ASSERT_EQ(e10[2].as_string(), "foobar", "e10[2] has incorrect value after insert"); + + e10.del_at(0); + ASSERT_EQ(e10.size(), 2, "e10 has incorrect size after delete"); + ASSERT_EQ(e10[1].as_string(), "foobar", "e10[1] has incorrect value after delete"); + + e10.clear(); + ASSERT_EQ(e10.size(), 0, "e10 has incorrect number of elements after clear"); + + json::Value e11(json::object()); + ASSERT_TRUE(e11.is_object(), "e11 is not an object after property assignment"); + + e11.set_key("foo", json::Value("test")); + ASSERT_EQ(e11.size(), 1, "e11 has incorrect number of properties after assignment"); + ASSERT_EQ(e11["foo"].as_string(), "test", "e11.foo has incorrect value after assignment"); + + e11.set_key("foo", json::Value("again")); + ASSERT_TRUE(e11.is_object(), "e11 is not an object after property assignment"); + ASSERT_EQ(e11.size(), 1, "e11 has incorrect number of properties after assignment"); + ASSERT_EQ(e11["foo"].as_string(), "again", "e11.foo has incorrect value after assignment"); + + e11.set_key("bar", json::Value("test")); + ASSERT_TRUE(e11.is_object(), "e11 is not an object after property assignment"); + ASSERT_EQ(e11.size(), 2, "e11 has incorrect number of properties after assignment"); + ASSERT_EQ(e11["bar"].as_string(), "test", "e11.foo has incorrect value after assignment"); + + e11.clear(); + ASSERT_EQ(e11.size(), 0, "e11 has incorrect number of properties after clear"); + + json::Value e12(json::object()); + e12.set_key("foo", json::Value("test")); + e12.set_key("bar", json::Value(3)); + char* out_cstr = e12.dumps(0); + std::string out(out_cstr); + free(out_cstr); + ASSERT_EQ(out, "{\"bar\": 3, \"foo\": \"test\"}", "object did not serialize as expected"); + + std::istringstream instr(out); + instr >> e12; + ASSERT_TRUE(e12.is_object(), "e12 is not an object after stream read"); + ASSERT_EQ(e12.size(), 2, "e12 has wrong size after stream read"); + ASSERT_EQ(e12.get("bar").as_integer(), 3, "e12.bar has incorrect value after stream read"); + ASSERT_EQ(e12.get("foo").as_string(), "test", "ee12.test has incorrect value after stream read"); + + std::ostringstream outstr; + outstr << e12; + ASSERT_EQ(instr.str(), "{\"bar\": 3, \"foo\": \"test\"}", "object did not serialize as expected"); + + const json::Value e13(e12); + ASSERT_EQ(e13["bar"].as_integer(), 3, "e13.bar has incorrect value after copy"); + + json::Value e14(json::object()); + ASSERT_TRUE(e14.is_object(), "e14 is not an object after construction"); + e14.set_key("foo", json::object()); + ASSERT_TRUE(e14["foo"].is_object(), "e14.foo is not an object after assignment"); + e14["foo"]["bar"] = json::Value(42); + ASSERT_EQ(e14["foo"]["bar"].as_integer(), 42, "e14.foo.bar has incorrect value after assignment"); + + json::Value e15(json::array()); + ASSERT_TRUE(e15.is_array(), "e15 is not an array after construction"); + e15.set_at(0, json::Value(42)); + ASSERT_EQ(e15[0].as_integer(), 42, "e15[0] has incorrect value after assignment"); + e15[0] = json::Value("foo"); + ASSERT_EQ(e15[0].as_string(), "foo", "e15[0] has incorrecy value after assignment"); + return 0; +} diff --git a/test/suites/api/test_cpp.json b/test/suites/api/test_cpp.json new file mode 100644 index 0000000..d31a395 --- /dev/null +++ b/test/suites/api/test_cpp.json @@ -0,0 +1,88 @@ +{"web-app": { + "servlet": [ + { + "servlet-name": "cofaxCDS", + "servlet-class": "org.cofax.cds.CDSServlet", + "init-param": { + "configGlossary:installationAt": "Philadelphia, PA", + "configGlossary:adminEmail": "ksm@pobox.com", + "configGlossary:poweredBy": "Cofax", + "configGlossary:poweredByIcon": "/images/cofax.gif", + "configGlossary:staticPath": "/content/static", + "templateProcessorClass": "org.cofax.WysiwygTemplate", + "templateLoaderClass": "org.cofax.FilesTemplateLoader", + "templatePath": "templates", + "templateOverridePath": "", + "defaultListTemplate": "listTemplate.htm", + "defaultFileTemplate": "articleTemplate.htm", + "useJSP": false, + "jspListTemplate": "listTemplate.jsp", + "jspFileTemplate": "articleTemplate.jsp", + "cachePackageTagsTrack": 200, + "cachePackageTagsStore": 200, + "cachePackageTagsRefresh": 60, + "cacheTemplatesTrack": 100, + "cacheTemplatesStore": 50, + "cacheTemplatesRefresh": 15, + "cachePagesTrack": 200, + "cachePagesStore": 100, + "cachePagesRefresh": 10, + "cachePagesDirtyRead": 10, + "searchEngineListTemplate": "forSearchEnginesList.htm", + "searchEngineFileTemplate": "forSearchEngines.htm", + "searchEngineRobotsDb": "WEB-INF/robots.db", + "useDataStore": true, + "dataStoreClass": "org.cofax.SqlDataStore", + "redirectionClass": "org.cofax.SqlRedirection", + "dataStoreName": "cofax", + "dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver", + "dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon", + "dataStoreUser": "sa", + "dataStorePassword": "dataStoreTestQuery", + "dataStoreTestQuery": "SET NOCOUNT ON;select test='test';", + "dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log", + "dataStoreInitConns": 10, + "dataStoreMaxConns": 100, + "dataStoreConnUsageLimit": 100, + "dataStoreLogLevel": "debug", + "maxUrlLength": 500}}, + { + "servlet-name": "cofaxEmail", + "servlet-class": "org.cofax.cds.EmailServlet", + "init-param": { + "mailHost": "mail1", + "mailHostOverride": "mail2"}}, + { + "servlet-name": "cofaxAdmin", + "servlet-class": "org.cofax.cds.AdminServlet"}, + + { + "servlet-name": "fileServlet", + "servlet-class": "org.cofax.cds.FileServlet"}, + { + "servlet-name": "cofaxTools", + "servlet-class": "org.cofax.cms.CofaxToolsServlet", + "init-param": { + "templatePath": "toolstemplates/", + "log": 1, + "logLocation": "/usr/local/tomcat/logs/CofaxTools.log", + "logMaxSize": "", + "dataLog": 1, + "dataLogLocation": "/usr/local/tomcat/logs/dataLog.log", + "dataLogMaxSize": "", + "removePageCache": "/content/admin/remove?cache=pages&id=", + "removeTemplateCache": "/content/admin/remove?cache=templates&id=", + "fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder", + "lookInContext": 1, + "adminGroupID": 4, + "betaServer": true}}], + "servlet-mapping": { + "cofaxCDS": "/", + "cofaxEmail": "/cofaxutil/aemail/*", + "cofaxAdmin": "/admin/*", + "fileServlet": "/static/*", + "cofaxTools": "/tools/*"}, + + "taglib": { + "taglib-uri": "cofax.tld", + "taglib-location": "/WEB-INF/tlds/cofax.tld"}}} diff --git a/test/suites/api/test_object.c b/test/suites/api/test_object.c index 67a93c8..4e730bb 100644 --- a/test/suites/api/test_object.c +++ b/test/suites/api/test_object.c @@ -258,6 +258,36 @@ static void test_iterators() if(json_object_iter_next(object, iter) != NULL) fail("able to iterate over the end"); + if(json_object_iter_at(object, "foo")) + fail("json_object_iter_at() succeeds for non-existent key"); + + iter = json_object_iter_at(object, "b"); + if(!iter) + fail("json_object_iter_at() fails for an existing key"); + + if(strcmp(json_object_iter_key(iter), "b")) + fail("iterating failed: wrong key"); + if(json_object_iter_value(iter) != bar) + fail("iterating failed: wrong value"); + + iter = json_object_iter_next(object, iter); + if(!iter) + fail("unable to increment iterator"); + if(strcmp(json_object_iter_key(iter), "c")) + fail("iterating failed: wrong key"); + if(json_object_iter_value(iter) != baz) + fail("iterating failed: wrong value"); + + if(json_object_iter_set(object, iter, bar)) + fail("unable to set value at iterator"); + + if(strcmp(json_object_iter_key(iter), "c")) + fail("json_object_iter_key() fails after json_object_iter_set()"); + if(json_object_iter_value(iter) != bar) + fail("json_object_iter_value() fails after json_object_iter_set()"); + if(json_object_get(object, "c") != bar) + fail("json_object_get() fails after json_object_iter_set()"); + json_decref(object); json_decref(foo); json_decref(bar);