Skip to content

Instantly share code, notes, and snippets.

@chergert
Last active December 18, 2015 13:59
Show Gist options
  • Select an option

  • Save chergert/5793969 to your computer and use it in GitHub Desktop.

Select an option

Save chergert/5793969 to your computer and use it in GitHub Desktop.
GObject <-> BSON encoder Transparently convert GObjects to and from BSON using libbson.
/*
* Copyright 2013 Christian Hergert <christian@hergert.me>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "bson-encoder.h"
static gboolean
_g_object_to_bson (GObject *object,
bson_t *b);
static gboolean
_bson_iter_gvalue (const bson_iter_t *iter,
GValue *value)
{
g_assert(iter);
g_assert(value);
switch (bson_iter_type(iter)) {
case BSON_TYPE_DOUBLE:
g_value_init(value, G_TYPE_DOUBLE);
g_value_set_double(value, bson_iter_double(iter));
break;
case BSON_TYPE_INT32:
g_value_init(value, G_TYPE_INT);
g_value_set_int(value, bson_iter_int32(iter));
break;
case BSON_TYPE_INT64:
g_value_init(value, G_TYPE_INT64);
g_value_set_int64(value, bson_iter_int64(iter));
break;
case BSON_TYPE_BOOL:
g_value_init(value, G_TYPE_BOOLEAN);
g_value_set_boolean(value, bson_iter_bool(iter));
break;
case BSON_TYPE_UTF8:
g_value_init(value, G_TYPE_STRING);
g_value_set_string(value, bson_iter_utf8(iter, NULL));
break;
default:
return FALSE;
}
return TRUE;
}
static gboolean
decode_property (GParamSpec *pspec,
const bson_iter_t *iter,
GValue *value)
{
GValue v;
g_assert(pspec);
g_assert(iter);
g_assert(value);
g_value_init(value, pspec->value_type);
switch (pspec->value_type) {
case G_TYPE_ENUM:
case G_TYPE_FLAGS:
case G_TYPE_FLOAT:
case G_TYPE_DOUBLE:
case G_TYPE_INT:
case G_TYPE_UINT:
case G_TYPE_INT64:
case G_TYPE_UINT64:
case G_TYPE_STRING:
case G_TYPE_BOOLEAN:
memset(&v, 0, sizeof v);
if (_bson_iter_gvalue(iter, &v)) {
if (G_VALUE_TYPE(&v) == G_VALUE_TYPE(value)) {
g_value_copy(&v, value);
g_value_unset(&v);
return TRUE;
}
return g_value_transform(&v, value);
}
break;
default:
if (pspec->value_type == G_TYPE_PTR_ARRAY) {
const bson_uint8_t *data = NULL;
bson_uint32_t len = 0;
bson_iter_t child;
GPtrArray *ptr;
GObject *obj;
bson_t b;
ptr = g_ptr_array_new();
if (bson_iter_recurse(iter, &child)) {
while (bson_iter_next(&child)) {
if (BSON_ITER_HOLDS_DOCUMENT(&child)) {
bson_iter_document(&child, &len, &data);
if (bson_init_static(&b, data, len)) {
if ((obj = g_object_from_bson(&b))) {
g_ptr_array_add(ptr, obj);
}
bson_destroy(&b);
}
}
}
}
g_value_set_boxed(value, ptr);
g_ptr_array_unref(ptr);
return TRUE;
} else if (g_type_is_a(pspec->value_type, G_TYPE_OBJECT)) {
const bson_uint8_t *data = NULL;
bson_uint32_t len = 0;
GObject *obj;
bson_t b;
if (BSON_ITER_HOLDS_DOCUMENT(iter)) {
bson_iter_document(iter, &len, &data);
if (bson_init_static(&b, data, len)) {
if ((obj = g_object_from_bson(&b))) {
g_value_set_object(value, obj);
g_object_unref(obj);
bson_destroy(&b);
return TRUE;
}
bson_destroy(&b);
return TRUE;
}
} else if (BSON_ITER_HOLDS_NULL(iter)) {
g_value_set_object(value, NULL);
return TRUE;
}
} else if (pspec->value_type == G_TYPE_DATE_TIME) {
GDateTime *dt;
GTimeVal gtv = { 0 };
if (BSON_ITER_HOLDS_DATE_TIME(iter)) {
gtv.tv_sec = bson_iter_time_t(iter);
gtv.tv_usec = 0;
if ((dt = g_date_time_new_from_timeval_utc(&gtv))) {
g_value_set_boxed(value, dt);
g_date_time_unref(dt);
return TRUE;
}
}
} else if (pspec->value_type == G_TYPE_BYTES) {
const bson_uint8_t *binary = NULL;
bson_subtype_t subtype = 0;
bson_uint32_t len = 0;
GBytes *bytes;
if (BSON_ITER_HOLDS_BINARY(iter)) {
bson_iter_binary(iter, &subtype, &len, &binary);
if ((bytes = g_bytes_new(binary, len))) {
g_value_set_boxed(value, bytes);
g_bytes_unref(bytes);
return TRUE;
}
}
}
break;
}
return FALSE;
}
GObject *
g_object_from_bson (const bson_t *bson)
{
GObjectClass *klass = NULL;
const gchar *type_name;
GParameter *params = NULL;
GParamSpec *pspec;
bson_iter_t iter;
GObject *ret = NULL;
GType gtype;
guint count = 0;
guint i = 0;
g_return_val_if_fail(bson, NULL);
if (!bson_iter_init_find_case(&iter, bson, "_gtype") ||
!BSON_ITER_HOLDS_UTF8(&iter) ||
!(type_name = bson_iter_utf8(&iter, NULL)) ||
(!(gtype = g_type_from_name(type_name))) ||
(!(klass = g_type_class_ref(gtype))) ||
!G_IS_OBJECT_CLASS(klass)) {
goto failure;
}
count = bson_count_keys(bson);
params = g_malloc0_n(count, sizeof *params);
if (!bson_iter_init(&iter, bson)) {
goto failure;
}
while (bson_iter_next(&iter)) {
if (!(pspec = g_object_class_find_property(klass, bson_iter_key(&iter)))) {
continue;
}
memset(&params[i], 0, sizeof params[i]);
if (!decode_property(pspec, &iter, &params[i].value)) {
g_print("Failed to decode property %s\n", pspec->name);
continue;
}
params[i++].name = pspec->name;
}
ret = g_object_newv(gtype, i, params);
failure:
if (klass) {
g_type_class_unref(klass);
}
if (params) {
for (i = 0; i < count; i++) {
if (G_IS_VALUE(&params[i].value)) {
g_value_unset(&params[i].value);
}
}
g_free(params);
}
return ret;
}
static void
encode_property (GParamSpec *pspec,
bson_t *b,
const GValue *value)
{
g_assert(pspec);
g_assert(b);
g_assert(value);
switch (G_VALUE_TYPE(value)) {
case G_TYPE_INT:
bson_append_int32(b, pspec->name, -1, g_value_get_uint(value));
break;
case G_TYPE_UINT:
bson_append_int32(b, pspec->name, -1, g_value_get_int(value));
break;
case G_TYPE_INT64:
bson_append_int64(b, pspec->name, -1, g_value_get_int64(value));
break;
case G_TYPE_UINT64:
bson_append_int64(b, pspec->name, -1, g_value_get_uint64(value));
break;
case G_TYPE_BOOLEAN:
bson_append_bool(b, pspec->name, -1, g_value_get_boolean(value));
break;
case G_TYPE_DOUBLE:
bson_append_double(b, pspec->name, -1, g_value_get_double(value));
break;
case G_TYPE_FLOAT:
bson_append_double(b, pspec->name, -1, g_value_get_float(value));
break;
case G_TYPE_STRING:
bson_append_utf8(b, pspec->name, -1, g_value_get_string(value), -1);
break;
default:
if (G_VALUE_HOLDS(value, G_TYPE_PTR_ARRAY)) {
GPtrArray *ptr;
GObject *obj;
gchar key[16];
bson_t ar;
bson_t c;
guint i;
if ((ptr = g_value_get_boxed(value))) {
bson_append_array_begin(b, pspec->name, -1, &ar);
for (i = 0; i < ptr->len; i++) {
if ((obj = g_ptr_array_index(ptr, i))) {
g_snprintf(key, sizeof key - 1, "%u", i);
key[sizeof key - 1] = '\0';
bson_append_document_begin(&ar, key, -1, &c);
_g_object_to_bson(obj, &c);
bson_append_document_end(&ar, &c);
}
}
bson_append_array_end(b, &ar);
}
} else if (G_VALUE_HOLDS(value, G_TYPE_DATE_TIME)) {
GDateTime *dt;
GTimeVal tv;
if ((dt = g_value_get_boxed(value))) {
g_date_time_to_timeval(dt, &tv);
bson_append_time_t(b, pspec->name, -1, tv.tv_sec);
} else {
bson_append_null(b, pspec->name, -1);
}
} else if (G_VALUE_HOLDS(value, G_TYPE_BYTES)) {
const bson_uint8_t *data;
GBytes *bytes;
gsize len = 0;
if ((bytes = g_value_get_boxed(value))) {
if ((data = g_bytes_get_data(bytes, &len))) {
bson_append_binary(b, pspec->name, -1,
BSON_SUBTYPE_BINARY, data, len);
}
}
} else if (g_type_is_a(G_VALUE_TYPE(value), G_TYPE_OBJECT)) {
GObject *obj;
bson_t c;
if ((obj = g_value_get_object(value))) {
bson_append_document_begin(b, pspec->name, -1, &c);
_g_object_to_bson(obj, &c);
bson_append_document_end(b, &c);
}
}
break;
}
}
static gboolean
_g_object_to_bson (GObject *object,
bson_t *b)
{
const gchar *type_name;
GParamSpec **pspecs;
GValue v = { 0 };
guint i;
guint n;
g_return_val_if_fail(G_IS_OBJECT(object), NULL);
if (!(type_name = g_type_name(G_TYPE_FROM_INSTANCE(object)))) {
return FALSE;
}
bson_append_utf8(b, "_gtype", 6, type_name, -1);
pspecs = g_object_class_list_properties(G_OBJECT_GET_CLASS(object), &n);
for (i = 0; i < n; i++) {
g_value_init(&v, pspecs[i]->value_type);
g_object_get_property(object, pspecs[i]->name, &v);
encode_property(pspecs[i], b, &v);
g_value_unset(&v);
}
return TRUE;
}
bson_t *
g_object_to_bson (GObject *object)
{
bson_t *b;
b = bson_new();
if (!_g_object_to_bson(object, b)) {
bson_destroy(b);
return NULL;
}
return b;
}
/*
* Copyright 2013 Christian Hergert <christian@hergert.me>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef BSON_ENCODER_H
#define BSON_ENCODER_H
#include <bson.h>
#include <glib-object.h>
G_BEGIN_DECLS
bson_t *g_object_to_bson (GObject *object);
GObject *g_object_from_bson (const bson_t *bson);
G_END_DECLS
#endif /* BSON_ENCODER_H */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment