Skip to content

Instantly share code, notes, and snippets.

@rosko
Last active April 28, 2025 13:56
Show Gist options
  • Save rosko/57ab2083ee4adf2f078d4d76851d9dc6 to your computer and use it in GitHub Desktop.
Save rosko/57ab2083ee4adf2f078d4d76851d9dc6 to your computer and use it in GitHub Desktop.
GunDB and JSON

To put JSON to Gun

gun.putJSON(json);

To get JSON from Gun

gun.open(() => {
    gun.getJSON().then(result => {
        const json = gunToJson(result || {});
        console.log(json);
    });
});
import _ from 'lodash';
export default function(Gun) {
/**
* Get a graph tree as a JSON
* @returns Promise
*/
Gun.chain.getJSON = function() {
return new Promise(resolve => {
const gun = this;
// get current state of the node
gun.once(node => {
const json = {};
const promises = [];
const promiseMap = {};
_.forEach(node, (value, key) => {
if (key === '#') { // ignore meta data
return;
}
if (key === '_' && value['#']) { // get only an id (Gun soul), ignore other meta data
json._ = {'#': value['#']};
return;
}
// it's object, it means there is a relation (graph edge) to another graph node
if (_.isPlainObject(value)) {
promiseMap[promises.length] = key;
// get a graph subtree recursively
promises.push(gun.get(key).getJSON());
}
// take primitive values (strings, numbers, booleans, etc) as is
else {
json[key] = value;
}
});
// wait for all the data and return it
Promise.all(promises).then(results => {
_.forEach(results, (result, index) => {
json[promiseMap[index]] = result;
});
resolve(json);
});
});
});
};
}
import _ from 'lodash';
export function gunToJson(value, removeSoul) {
if (_.isObject(value) && value !== null) {
let shouldBeArray = true, size = 0;
if (!value.__isArray) {
_.forEach(value, (item, index) => {
if (index === '_' || index === '__keys') {
return;
}
size++;
if (String(Number(index)) !== index) {
shouldBeArray = false;
}
});
}
const object = value.__isArray || (shouldBeArray && size) ? [] : {};
let keys;
try {
keys = _.map(JSON.parse(value.__keys), String);
} catch (e) {
}
_.forEach(value, (item, index) => {
if (item !== null &&
(!keys || _.indexOf(keys, String(index)) !== -1) &&
(!removeSoul || index !== '_')) {
object[index] = gunToJson(item, removeSoul);
}
});
return object;
} else {
return value;
}
}
import _ from 'lodash';
const TYPE_UNDEFINED = 'undefined';
export default function(Gun) {
/**
* Update a graph using JSON
*
* Updates a graph tree according to a given JSON.
* Calculating diff and doing as minimum updates as possible
*
* @param json
* @param [callback]
* @param [isArray]
* @returns {Gun.chain}
*/
Gun.chain.putJSON = function putJSON(json, callback, isArray) {
const gun = this;
// get current state of the node
gun.once(node => {
const nodeUpdate = {}; // values to update
// update the existing node
_.forEach(node, (value, key) => {
if (key === '_' || key === '__isArray') { // ignore meta data
return;
}
const newValue = json[key];
const _isArray = Array.isArray(newValue);
// update a leaf
if (_.isPlainObject(newValue) || _isArray) {
const soul = Gun.node.soul(newValue);
// if the new value is already known Gun node with id (Gun soul)
if (soul) {
const n = gun.back(-1).get(soul); // get a corresponding node directly
n.putJSON(newValue, callback, _isArray); // save/update it
// if it's a new leaf, update the relation (graph edge)
if (!_.isObject(value) || value['#'] !== soul) {
gun.get(key).put(n, callback);
}
}
// if the new value doesn't have id (Gun soul), just update it
else {
gun.get(key).putJSON(newValue, callback, _isArray);
}
}
// remove an old value
else if (typeof newValue === TYPE_UNDEFINED || newValue === null) {
// if it's not already removed value, mark it as removed
if (value !== null) {
gun.get(key).put(null, callback);
}
}
// update an old primitive (string, number, boolean) value
else if (value !== newValue) {
nodeUpdate[key] = newValue;
}
});
// apply new changes
_.forEach(json, (value, key) => {
if (key === '_' || key === '__isArray') { // ignore meta data
return;
}
// proceed only if:
// - there is a new node
// - OR the value doesn't exist or was removed
if (!node
|| !Object.prototype.hasOwnProperty.call(node, key)
|| node[key] === null
|| typeof node[key] === TYPE_UNDEFINED) { // add
const _isArray = Array.isArray(value);
if (_.isPlainObject(value) || _isArray) { // add a leaf
const soul = Gun.node.soul(value);
// if the new value is already known Gun node with id (Gun soul)
if (soul) {
const n = gun.back(-1).get(soul); // get a corresponding node directly
n.putJSON(value, callback, _isArray); // save/update it
gun.get(key).put(n, callback); // set the relation (graph edge)
}
// if the new value has no id (Gun soul), just update it
else {
gun.get(key).putJSON(value, callback, _isArray);
}
}
// add a primitive value (ignore null and undefined as removed values)
else if (typeof value !== TYPE_UNDEFINED && value !== null) {
nodeUpdate[key] = value;
}
}
});
// mark the node as an array only if
// - it is an array
// - it wasn't marked as an array before
if (isArray && (!node || !node.__isArray)) {
nodeUpdate['__isArray'] = true;
}
// remove id (Gun soul) in case of empty objects
delete nodeUpdate['#'];
// update the node only if it's a new node or there are some updates
if (!_.isEmpty(nodeUpdate) || !node) {
gun.put(nodeUpdate, callback);
}
});
// return a context unchanged (common behavior in Gun)
return gun;
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment