/*
 *   This program is is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or (at
 *   your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

/**
 * $Id: 83bcba5842f2918fab14f53abca319bd05d25e30 $
 * @file json.c
 * @brief Common functions for working with json-c
 *
 * @author Matthew Newton
 * @author Arran Cudbard-Bell
 *
 * @copyright 2015 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
 * @copyright 2015,2021 Network RADIUS SARL (legal@networkradius.com)
 * @copyright 2015 The FreeRADIUS Server Project
 */

#include <freeradius-devel/rad_assert.h>
#include "json.h"

#ifndef HAVE_JSON
#  error "rlm_json should not be built unless json-c is available"
#endif


const FR_NAME_NUMBER fr_json_format_table[] = {
	{ "array",		JSON_MODE_ARRAY			},
	{ "array_of_names",	JSON_MODE_ARRAY_OF_NAMES	},
	{ "array_of_values",	JSON_MODE_ARRAY_OF_VALUES	},
	{ "object",		JSON_MODE_OBJECT		},
	{ "object_simple",	JSON_MODE_OBJECT_SIMPLE		},

	{ NULL,			-1				}
};

static inline CC_HINT(always_inline)
void json_object_put_assert(json_object *obj)
{
	int ret;

	ret = json_object_put(obj);
	if (ret == 1) return;

	rad_assert(0);
}


/** Given a VALUE_PAIR, create the correct JSON object based on the data type
 *
 * @param[in] ctx to allocate temporary buffers in
 * @param[in] vp VALUE_PAIR to convert.
 * @param[in] always_string create all values as strings
 * @param[in] enum_as_int output enum attribute values as integers not strings
 * @return Newly allocated JSON object, or NULL on error
 */
json_object *json_object_from_attr_value(TALLOC_CTX *ctx, VALUE_PAIR const *vp, bool always_string, bool enum_as_int)
{
	char buf[2048];
	ssize_t len;

	/*
	 *  We're converting to PRESENTATION format
	 *  so any attributes with enumeration values
	 *  should be converted to string types, unless
	 *  enum_as_int is set.
	 */

#define RETURN_ENUM_OR_STRING(_type) \
	if (always_string) { \
		len = snprintf(buf, sizeof(buf), "%d", vp->vp_ ## _type); \
		return json_object_new_string_len(buf, len); \
	} \
	return json_object_new_int(vp->vp_ ## _type)

	/*
	 *  Handle enumeration values first
	 */
	if (vp->da->flags.has_value) {
		if (enum_as_int) {
			switch (vp->da->type) {
			default:
				break;

			case PW_TYPE_BYTE:
				RETURN_ENUM_OR_STRING(byte);

			case PW_TYPE_SHORT:
				RETURN_ENUM_OR_STRING(short);

			case PW_TYPE_INTEGER:
				RETURN_ENUM_OR_STRING(integer);
			}
		} else {
			always_string = true;
		}
	}


	/*
	 *  If always_string is set then we print everything to a string and
	 *  return a JSON string object.
	 */
	if (always_string) {
		char		*p;
		char		*quoted_string;
		json_object	*obj = NULL;

	do_string:
		p = vp_aprints_value(ctx, vp, '\0');
		if (!p) return NULL;

		quoted_string = fr_json_from_string(ctx, p, false);
		if (!quoted_string) {
			talloc_free(p);
			return NULL;
		}

		obj = json_object_new_string(quoted_string);
		talloc_free(p);

		return obj;
	}

	/*
	 *  Otherwise use the correct JSON object function depending on the
	 *  attribute value type.
	 */
	switch (vp->da->type) {
	default:
		goto do_string;

	case PW_TYPE_BOOLEAN:
		return json_object_new_boolean(vp->vp_byte);

	case PW_TYPE_BYTE:
		return json_object_new_int(vp->vp_byte);

	case PW_TYPE_SHORT:
		return json_object_new_int(vp->vp_short);

#ifdef HAVE_JSON_OBJECT_GET_INT64
	case PW_TYPE_INTEGER:
		return json_object_new_int64((int64_t)vp->vp_integer64);	/* uint32_t (max) > int32_t (max) */

	case PW_TYPE_INTEGER64:
		if (vp->vp_integer64 > INT64_MAX) goto do_string;
		return json_object_new_int64(vp->vp_integer64);
#else
	case PW_TYPE_INTEGER:
		if (vp->vp_integer > INT32_MAX) goto do_string;
		return json_object_new_int(vp->vp_integer);
#endif

	case PW_TYPE_SIGNED:
		return json_object_new_int(vp->vp_signed);
	}
}

/** Escapes string for use as a JSON string
 *
 * @param ctx Talloc context to allocate this string
 * @param s Input string
 * @param include_quotes Include the surrounding quotes of JSON strings
 * @return New allocated character string, or NULL if something failed.
 */
char *fr_json_from_string(TALLOC_CTX *ctx, char const *s, bool include_quotes)
{
	char const *p;
	char *out = NULL;
	struct json_object *json;
	int len;

	json = json_object_new_string(s);
	if (!json) return NULL;

	if ((p = json_object_to_json_string(json))) {
		if (include_quotes) {
			out = talloc_typed_strdup(ctx, p);
		} else {
			len = strlen(p);
			out = talloc_bstrndup(ctx, p + 1, len - 2);	/* to_json_string adds quotes (") */
		}
	}

	/*
	 * Free the JSON structure, it's not needed any more
	 */
	json_object_put_assert(json);

	return out;
}


/** Convert VALUE_PAIR into a JSON object
 *
 * If inst.enum_as_int is set, and the given VP is an enum
 * value, the integer value is returned as a json_object rather
 * than the text representation.
 *
 * If inst.always_string is set then a numeric value pair
 * will be returned as a JSON string object.
 *
 * @param[in] ctx	Talloc context.
 * @param[out] out	returned json object.
 * @param[in] vp	to get the value of.
 * @param[in] inst	format definition, or NULL.
 * @return
 *	- 1 if 'out' is the integer enum value, 0 otherwise
 *	- -1 on error.
 */
static int json_afrom_value_pair(TALLOC_CTX *ctx, json_object **out,
				 VALUE_PAIR *vp, rlm_json_t const *inst)
{
	struct json_object	*obj;
	int			is_enum = 0;

	fr_assert(vp);
	fr_assert(inst);

	MEM(obj = json_object_from_attr_value(ctx, vp, inst->always_string, inst->enum_as_int));

	*out = obj;
	return is_enum;
}


/** Add prefix to attribute name
 *
 * If the format "attr.prefix" string is set then prepend this
 * to the given attribute name, otherwise return name unchanged.
 *
 * @param[out] buf where to write the new name, if set
 * @param[in] buf_len length of buf
 * @param[in] name original attribute name
 * @param[in] inst json format structure
 * @return pointer to name, or buf if the prefix was added
 */
static inline char const *attr_name_with_prefix(char *buf, size_t buf_len, const char *name, rlm_json_t const *inst)
{
	int len;

	if (!inst->attr_prefix) return name;

	len = snprintf(buf, buf_len, "%s:%s", inst->attr_prefix, name);

	if (len == (int)strlen(buf)) {
		return buf;
	}

	return name;
}


/** Verify that the options in rlm_json_t are valid
 *
 * Warnings are optional, will fatal error if the format is corrupt.
 *
 * @param[in] inst	the format structure to check
 * @param[in] verbose	print out warnings if set
 * @return		true if format is good, otherwise false
 */
bool fr_json_format_verify(rlm_json_t const *inst, bool verbose)
{
	bool ret = true;

	fr_assert(inst);

	switch (inst->output_mode) {
	case JSON_MODE_OBJECT:
	case JSON_MODE_OBJECT_SIMPLE:
	case JSON_MODE_ARRAY:
		/* all options are valid */
		return true;
	case JSON_MODE_ARRAY_OF_VALUES:
		if (inst->attr_prefix) {
			if (verbose) WARN("attribute name prefix not valid in output_mode 'array_of_values' and will be ignored");
			ret = false;
		}
		if (inst->value_as_array) {
			if (verbose) WARN("'value_as_array' not valid in output_mode 'array_of_values' and will be ignored");
			ret = false;
		}
		return ret;
	case JSON_MODE_ARRAY_OF_NAMES:
		if (inst->value_as_array) {
			if (verbose) WARN("'value_as_array' not valid in output_mode 'array_of_names' and will be ignored");
			ret = false;
		}
		if (inst->enum_as_int) {
			if (verbose) WARN("'enum_as_int' not valid in output_mode 'array_of_names' and will be ignored");
			ret = false;
		}
		if (inst->always_string) {
			if (verbose) WARN("'always_string' not valid in output_mode 'array_of_names' and will be ignored");
			ret = false;
		}
		return ret;
	default:
		ERROR("JSON format output mode is invalid");
	}

	/* If we get here, something has gone wrong */
	fr_assert(0);

	return false;
}


/** Returns a JSON object representation of a list of value pairs
 *
 * The result is a struct json_object, which should be free'd with
 * json_object_put() by the caller. Intended to only be called by
 * fr_json_afrom_pair_list().
 *
 * This function generates the "object" format, JSON_MODE_OBJECT.
 * @see fr_json_format_s
 *
 * @param[in] ctx	Talloc context.
 * @param[in] vps	a list of value pairs.
 * @param[in] inst	Formatting control, must be set.
 * @return JSON object with the generated representation.
 */
static json_object *json_object_afrom_pair_list(TALLOC_CTX *ctx, VALUE_PAIR *vps,
						rlm_json_t const *inst)
{
	VALUE_PAIR		*vp;
	struct json_object	*obj;
	char			buf[DICT_ATTR_MAX_NAME_LEN + 32];

	/* Check format and type */
	fr_assert(inst);
	fr_assert(inst->output_mode == JSON_MODE_OBJECT);

	MEM(obj = json_object_new_object());

	for (vp = vps;
	     vp;
	     vp = vp->next) {
		char const		*attr_name;
		struct json_object	*vp_object, *values, *value, *type_name;

		/*
		 *	Get attribute name and value.
		 */
		attr_name = attr_name_with_prefix(buf, sizeof(buf), vp->da->name, inst);

		if (json_afrom_value_pair(ctx, &value, vp, inst) < 0) {
			fr_strerror_printf("Failed to convert attribute value to JSON object");
		error:
			json_object_put_assert(obj);

			return NULL;
		}

		/*
		 *	Look in the table to see if we already have
		 *	a key for the attribute we're working on.
		 */
		if (!json_object_object_get_ex(obj, attr_name, &vp_object)) {
			/*
			 *	Wasn't there, so create a new object for this attribute.
			 */
			MEM(vp_object = json_object_new_object());
			json_object_object_add(obj, attr_name, vp_object);

			/*
			 *	Add "type" to newly created keys.
			 */
			MEM(type_name = json_object_new_string(fr_int2str(dict_attr_types, vp->da->type, "<INVALID>")));
			json_object_object_add_ex(vp_object, "type", type_name, JSON_C_OBJECT_KEY_IS_CONSTANT);

			/*
			 *	Create a "value" array to hold any attribute values for this attribute...
			 */
			if (inst->value_as_array) {
				MEM(values = json_object_new_array());
				json_object_object_add_ex(vp_object, "value", values, JSON_C_OBJECT_KEY_IS_CONSTANT);
			} else {
				/*
				 *	...unless this is the first time we've seen the attribute and
				 *	value_as_array is false, in which case just add the value directly
				 *	and move on to the next attribute.
				 */
				json_object_object_add_ex(vp_object, "value", value, JSON_C_OBJECT_KEY_IS_CONSTANT);
				continue;
			}
		} else {
			/*
			 *	Find the 'values' array to add the current value to.
			 */
			if (!json_object_object_get_ex(vp_object, "value", &values)) {
				fr_strerror_printf("Inconsistent JSON tree");
				goto error;
			}

			/*
			 *	If value_as_array is no set then "values" may not be an array, so it will
			 *	need converting to an array to add this extra attribute.
			 */
			if (!inst->value_as_array) {
				json_type		type;
				struct json_object	*convert_value = values;

				/* Check "values" type */
				type = json_object_get_type(values);

				/* It wasn't an array, so turn it into one with the old value as the first entry */
				if (type != json_type_array) {
					MEM(values = json_object_new_array());
					json_object_array_add(values, json_object_get(convert_value));
					json_object_object_del(vp_object, "value");
					json_object_object_add_ex(vp_object, "value", values,
								  JSON_C_OBJECT_KEY_IS_CONSTANT);
				}
			}
		}

		/*
		 *	Append to the JSON array.
		 */
		json_object_array_add(values, value);
	}

	return obj;
}


/** Returns a JSON object representation of a list of value pairs
 *
 * The result is a struct json_object, which should be free'd with
 * json_object_put() by the caller. Intended to only be called by
 * fr_json_afrom_pair_list().
 *
 * This function generates the "simple object" format, JSON_MODE_OBJECT_SIMPLE.
 * @see fr_json_format_s
 *
 * @param[in] ctx	Talloc context.
 * @param[in] vps	a list of value pairs.
 * @param[in] inst	Formatting control, must be set.
 * @return JSON object with the generated representation.
 */
static json_object *json_smplobj_afrom_pair_list(TALLOC_CTX *ctx, VALUE_PAIR *vps,
						 rlm_json_t const *inst)
{
	VALUE_PAIR		*vp;
	struct json_object	*obj;
	char			buf[DICT_ATTR_MAX_NAME_LEN + 32];
	json_type		type;

	/* Check format and type */
	fr_assert(inst);
	fr_assert(inst->output_mode == JSON_MODE_OBJECT_SIMPLE);

	MEM(obj = json_object_new_object());

	for (vp = vps;
	     vp;
	     vp = vp->next) {
		char const		*attr_name;
		struct json_object	*vp_object, *value;
		struct json_object	*values = NULL;
		bool			add_single = false;

		/*
		 *	Get attribute name and value.
		 */
		attr_name = attr_name_with_prefix(buf, sizeof(buf), vp->da->name, inst);

		if (json_afrom_value_pair(ctx, &value, vp, inst) < 0) {
			fr_strerror_printf("Failed to convert attribute value to JSON object");

			json_object_put_assert(obj);
			return NULL;
		}

		/*
		 *	See if we already have a key in the table we're working on,
		 *	if not then create a new one.
		 */
		if (!json_object_object_get_ex(obj, attr_name, &vp_object)) {
			if (inst->value_as_array) {
				/*
				 *	We have been asked to ensure /all/ values are lists,
				 *	even if there's only one attribute.
				 */
				MEM(values = json_object_new_array());
				json_object_object_add(obj, attr_name, values);
			} else {
				/*
				 *	Deal with it later on.
				 */
				add_single = true;
			}
		/*
		 *	If we do have the key already, get its value array.
		 */
		} else {
			type = json_object_get_type(vp_object);

			if (type == json_type_array) {
				values = vp_object;
			} else {
				/*
				 *	We've seen one of these before, but didn't add
				 *	it as an array the first time. Sort that out.
				 */
				MEM(values = json_object_new_array());
				json_object_array_add(values, json_object_get(vp_object));

				/*
				 *	Existing key will have refcount decremented
				 *	and will be freed if thise drops to zero.
				 */
				json_object_object_add(obj, attr_name, values);
			}
		}

		if (add_single) {
			/*
			 *	Only ever used the first time adding a new
			 *	attribute when "value_as_array" is not set.
			 */
			json_object_object_add(obj, attr_name, value);
		} else {
			/*
			 *	Otherwise we're always appending to a JSON array.
			 */
			json_object_array_add(values, value);
		}
	}

	return obj;
}


/** Returns a JSON array representation of a list of value pairs
 *
 * The result is a struct json_object, which should be free'd with
 * json_object_put() by the caller. Intended to only be called by
 * fr_json_afrom_pair_list().
 *
 * This function generates the "array" format, JSON_MODE_ARRAY.
 * @see fr_json_format_s
 *
 * @param[in] ctx	Talloc context.
 * @param[in] vps	a list of value pairs.
 * @param[in] inst	Formatting control, must be set.
 * @return JSON object with the generated representation.
 */
static struct json_object *json_array_afrom_pair_list(TALLOC_CTX *ctx, VALUE_PAIR *vps,
						      rlm_json_t const *inst)
{
	VALUE_PAIR		*vp;
	struct json_object	*obj;
	struct json_object	*seen_attributes = NULL;
	char			buf[DICT_ATTR_MAX_NAME_LEN + 32];

	/* Check format and type */
	fr_assert(inst);
	fr_assert(inst->output_mode == JSON_MODE_ARRAY);

	MEM(obj = json_object_new_array());

	/*
	 *	If attribute values should be in a list format, then keep track
	 *	of the attributes we've previously seen in a JSON object.
	 */
	if (inst->value_as_array) {
		seen_attributes = json_object_new_object();
	}

	for (vp = vps;
	     vp;
	     vp = vp->next) {
		char const		*attr_name;
		struct json_object	*name, *value, *type_name;
		struct json_object	*values = NULL;
		struct json_object	*attrobj = NULL;
		bool			already_seen = false;

		/*
		 *	Get attribute name and value.
		 */
		attr_name = attr_name_with_prefix(buf, sizeof(buf), vp->da->name, inst);

		if (json_afrom_value_pair(ctx, &value, vp, inst) < 0) {
			fr_strerror_printf("Failed to convert attribute value to JSON object");
			json_object_put_assert(obj);
			return NULL;
		}

		if (inst->value_as_array) {
			/*
			 *	Try and find this attribute in the "seen_attributes" object. If it is
			 *	there then get the "values" array to add this attribute value to.
			 */
			already_seen = json_object_object_get_ex(seen_attributes, attr_name, &values);
		}

		/*
		 *	If we're adding all attributes to the toplevel array, or we're adding values
		 *	to an array of an existing attribute but haven't seen it before, then we need
		 *	to create a new JSON object for this attribute.
		 */
		if (!inst->value_as_array || !already_seen) {
			/*
			 * Create object and add it to top-level array
			 */
			MEM(attrobj = json_object_new_object());
			json_object_array_add(obj, attrobj);

			/*
			 * Add the attribute name in the "name" key and the type in the "type" key
			 */
			MEM(name = json_object_new_string(attr_name));
			json_object_object_add_ex(attrobj, "name", name, JSON_C_OBJECT_KEY_IS_CONSTANT);

			MEM(type_name = json_object_new_string(fr_int2str(dict_attr_types, vp->da->type, "<INVALID>")));
			json_object_object_add_ex(attrobj, "type", type_name, JSON_C_OBJECT_KEY_IS_CONSTANT);
		}

		if (inst->value_as_array) {
			/*
			 *	We're adding values to an array for the first copy of this attribute
			 *	that we saw. First time around we need to create an array.
			 */
			if (!already_seen) {
				MEM(values = json_object_new_array());
				/*
				 * Add "value":[] key to the attribute object
				 */
				json_object_object_add_ex(attrobj, "value", values, JSON_C_OBJECT_KEY_IS_CONSTANT);

				/*
				 * Also add to "seen_attributes" to check later
				 */
				json_object_object_add(seen_attributes, attr_name, json_object_get(values));
			}

			/*
			 *	Always add the value to the respective "values" array.
			 */
			json_object_array_add(values, value);
		} else {
			/*
			 * This is simpler; just add a "value": key to the attribute object.
			 */
			json_object_object_add_ex(attrobj, "value", value, JSON_C_OBJECT_KEY_IS_CONSTANT);
		}

	}

	/*
	 *	No longer need the "seen_attributes" object, it was just used for tracking.
	 */
	if (inst->value_as_array) {
		json_object_put_assert(seen_attributes);
	}

	return obj;
}


/** Returns a JSON array of a list of value pairs
 *
 * The result is a struct json_object, which should be free'd with
 * json_object_put() by the caller. Intended to only be called by
 * fr_json_afrom_pair_list().
 *
 * This function generates the "array_of_values" format,
 * JSON_MODE_ARRAY_OF_VALUES, listing just the attribute values.
 * @see fr_json_format_s
 *
 * @param[in] ctx	Talloc context.
 * @param[in] vps	a list of value pairs.
 * @param[in] inst	Formatting control, must be set.
 * @return JSON object with the generated representation.
 */
static struct json_object *json_value_array_afrom_pair_list(TALLOC_CTX *ctx, VALUE_PAIR *vps,
							    rlm_json_t const *inst)
{
	VALUE_PAIR		*vp;
	struct json_object	*obj;

	/* Check format and type */
	fr_assert(inst);
	fr_assert(inst->output_mode == JSON_MODE_ARRAY_OF_VALUES);

	MEM(obj = json_object_new_array());

	/*
	 *	This array format is very simple - just add all the
	 *	attribute values to the array in order.
	 */
	for (vp = vps;
	     vp;
	     vp = vp->next) {
		struct json_object	*value;

		if (json_afrom_value_pair(ctx, &value, vp, inst) < 0) {
			fr_strerror_printf("Failed to convert attribute value to JSON object");
			json_object_put_assert(obj);
			return NULL;
		}

		json_object_array_add(obj, value);
	}

	return obj;
}


/** Returns a JSON array of a list of value pairs
 *
 * The result is a struct json_object, which should be free'd with
 * json_object_put() by the caller. Intended to only be called by
 * fr_json_afrom_pair_list().
 *
 * This function generates the "array_of_names" format,
 * JSON_MODE_ARRAY_OF_NAMES, listing just the attribute names.
 * @see fr_json_format_s
 *
 * @param[in] ctx	Talloc context.
 * @param[in] vps	a list of value pairs.
 * @param[in] inst	Formatting control, must be set.
 * @return JSON object with the generated representation.
 */
static struct json_object *json_attr_array_afrom_pair_list(UNUSED TALLOC_CTX *ctx, VALUE_PAIR *vps,
							   rlm_json_t const *inst)
{
	VALUE_PAIR		*vp;
	struct json_object	*obj;
	char			buf[DICT_ATTR_MAX_NAME_LEN + 32];

	/* Check format and type */
	fr_assert(inst);
	fr_assert(inst->output_mode == JSON_MODE_ARRAY_OF_NAMES);

	MEM(obj = json_object_new_array());

	/*
	 *	Add all the attribute names to the array in order.
	 */
	for (vp = vps;
	     vp;
	     vp = vp->next) {
		char const		*attr_name;
		struct json_object	*value;

		attr_name = attr_name_with_prefix(buf, sizeof(buf), vp->da->name, inst);
		value = json_object_new_string(attr_name);

		json_object_array_add(obj, value);
	}

	return obj;
}


/** Returns a JSON string of a list of value pairs
 *
 * The result is a talloc-ed string, freeing the string is
 * the responsibility of the caller.
 *
 * The 'inst' format struct contains settings to configure the output
 * JSON document format.
 * @see fr_json_format_s
 *
 * @param[in] ctx	Talloc context.
 * @param[in] vps	a list of value pairs.
 * @param[in] inst	Formatting control, can be NULL to use default format.
 * @return JSON string representation of the value pairs
 */
char *fr_json_afrom_pair_list(TALLOC_CTX *ctx, VALUE_PAIR *vps,
			      rlm_json_t const *inst)
{
	struct json_object	*obj = NULL;
	const char		*p;
	char			*out;

	rad_assert(inst);

	switch (inst->output_mode) {
	case JSON_MODE_OBJECT:
		MEM(obj = json_object_afrom_pair_list(ctx, vps, inst));
		break;
	case JSON_MODE_OBJECT_SIMPLE:
		MEM(obj = json_smplobj_afrom_pair_list(ctx, vps, inst));
		break;
	case JSON_MODE_ARRAY:
		MEM(obj = json_array_afrom_pair_list(ctx, vps, inst));
		break;
	case JSON_MODE_ARRAY_OF_VALUES:
		MEM(obj = json_value_array_afrom_pair_list(ctx, vps, inst));
		break;
	case JSON_MODE_ARRAY_OF_NAMES:
		MEM(obj = json_attr_array_afrom_pair_list(ctx, vps, inst));
		break;
	default:
		/* This should never happen */
		rad_assert(0);
	}

	/*
	 *	p is a buff inside obj, and will be freed
	 *	when it is freed.
	 */
	MEM(p = json_object_to_json_string_ext(obj, JSON_C_TO_STRING_PLAIN));
	MEM(out = talloc_typed_strdup(ctx, p));

	/*
	 * Free the JSON structure, it's not needed any more
	 */
	json_object_put_assert(obj);

	return out;
}
