/** * Recursively filter input to output. * * @param filter the fitler to use. * @param in the input object. * @param out the filtered output object. */ static void _filter( DynamicObject& filter, DynamicObject& in, DynamicObject& out) { if(!in.isNull()) { DynamicObjectType inType = in->getType(); if(inType == Map) { // check if this object matches filter if(_filterOne(filter, in)) { out["@"]->append(in); } } if(inType == Map || inType == Array) { // filter each object DynamicObjectIterator i = in.getIterator(); while(i->hasNext()) { _filter(filter, i->next(), out); } } } }
/** * Expands a possible CURIE string into a full IRI. If the string is not * recognized as a CURIE that can be expanded into an IRI, then false is * returned. * * @param ctx the context to use. * @param str the string to expand (no <>). * @param iri the string to store the IRI in. * @param usedCtx a context to update if a value was used from "ctx". * * @return true if the string was expanded, false if not. */ static bool _expandCurie( DynamicObject& ctx, const char* str, string& iri, DynamicObject* usedCtx = NULL) { bool rval = false; if(!ctx.isNull()) { // try to find a colon const char* ptr = strchr(str, ':'); if(ptr != NULL) { // get the potential CURIE prefix size_t len = ptr - str + 1; char prefix[len]; snprintf(prefix, len, "%s", str); // see if the prefix is in the context if(ctx->hasMember(prefix)) { // prefix found, normalize string DynamicObject& uri = ctx[prefix]; len = strlen(uri->getString()) + strlen(ptr + 1) + 3; iri = StringTools::format("%s%s", uri->getString(), ptr + 1); if(usedCtx != NULL) { (*usedCtx)[prefix] = uri.clone(); } rval = true; } } } return rval; }
bool JsonWriter::write(DynamicObject& dyno, OutputStream* os) { bool rval = true; if(mStrict) { rval = !dyno.isNull(); DynamicObjectType type; if(rval) { type = dyno->getType(); } if(!rval || !(type == Map || type == Array)) { ExceptionRef e = new Exception( "No JSON top-level Map or Array found.", "monarch.data.json.JsonWriter.InvalidJson"); Exception::set(e); rval = false; } } if(rval) { ByteBuffer b(1024); BufferedOutputStream bos(&b, os); rval = write(dyno, &bos, mIndentLevel) && bos.flush(); } return rval; }
bool JsonLd::addContext( DynamicObject& context, DynamicObject& in, DynamicObject& out) { bool rval = true; // "a" is automatically shorthand for rdf type DynamicObject ctx = (context.isNull() ? DynamicObject() : context.clone()); ctx["a"] = RDF_TYPE; // TODO: should context simplification be an option? (ie: remove context // entries that are not used in the output) // setup output context DynamicObject& contextOut = out["#"]; contextOut->setType(Map); // apply context _applyContext(ctx, contextOut, NULL, in, out); // clean up if(contextOut->length() == 0) { out->removeMember("#"); } return rval; }
/** * Recursively applies context to the given input object. * * @param ctx the context to use. * @param usedCtx the used context values. * @param predicate the related predicate or NULL if none. * @param in the input object. * @param out the contextualized output object. */ static void _applyContext( DynamicObject& ctx, DynamicObject& usedCtx, const char* predicate, DynamicObject& in, DynamicObject& out) { if(in.isNull()) { out.setNull(); } else { // initialize output DynamicObjectType inType = in->getType(); out->setType(inType); if(inType == Map) { // add context to each property in the map char* tmp = NULL; DynamicObjectIterator i = in.getIterator(); while(i->hasNext()) { // compact predicate DynamicObject& next = i->next(); DynamicObject cp(NULL); const char* p; if(strcmp(i->getName(), "@") == 0) { p = "@"; } else { cp = _compactString(ctx, usedCtx, NULL, i->getName(), &tmp); p = cp; } // recurse _applyContext(ctx, usedCtx, p, next, out[p]); } free(tmp); } else if(inType == Array) { // apply context to each object in the array DynamicObjectIterator i = in.getIterator(); while(i->hasNext()) { DynamicObject& next = i->next(); _applyContext(ctx, usedCtx, predicate, next, out->append()); } } // only strings need context applied, numbers & booleans don't else if(inType == String) { // compact string char* tmp = NULL; out = _compactString(ctx, usedCtx, predicate, in->getString(), &tmp); free(tmp); } } }
bool Map::isValid( DynamicObject& obj, ValidatorContext* context) { bool rval = true; if(!obj.isNull() && obj->getType() == monarch::rt::Map) { Validators::iterator i; for(i = mValidators.begin(); i != mValidators.end(); ++i) { // only add a "." if this is not a root map if(context->getDepth() != 0) { context->pushPath("."); } context->pushPath(i->first); if(obj->hasMember(i->first)) { // do not short-circuit to ensure all keys tested if(!i->second.validator->isValid(obj[i->first], context)) { rval = false; } } else if(!i->second.validator->isOptional(context)) { rval = false; DynamicObject detail = context->addError("monarch.validation.MissingField", &obj); detail["validator"] = "monarch.validator.Map"; detail["message"] = "A required field has not been specified."; detail["key"] = i->first; } context->popPath(); if(context->getDepth() > 0) { context->popPath(); } } } else { rval = false; DynamicObject detail = context->addError("monarch.validation.TypeError", &obj); detail["validator"] = "monarch.validator.Map"; detail["message"] = "The given object type must a mapping (Map) type"; } return rval; }
/** * Recursively removes context from the given input object. * * @param ctx the context to use (changes during recursion as necessary). * @param in the input object. * @param out the normalized output object. * @param bnodeId the last blank node ID used. * * @return true on success, false on failure with exception set. */ static bool _removeContext( DynamicObject& ctx, DynamicObject& in, DynamicObject& out, int& bnodeId) { bool rval = true; if(in.isNull()) { out.setNull(); } else { // initialize output DynamicObjectType inType = in->getType(); out->setType(inType); // update context if(inType == Map && in->hasMember("#")) { ctx = in["#"]; } if(inType == Map) { rval = _normalize(ctx, in, NULL, &out, bnodeId); } else if(inType == Array) { // strip context from each object in the array DynamicObjectIterator i = in.getIterator(); while(i->hasNext()) { DynamicObject& next = i->next(); rval = _removeContext(ctx, next, out->append(), bnodeId); } } } return rval; }
bool Contains::isValid( DynamicObject& obj, ValidatorContext* context) { bool rval = false; // check if objects are equal rval = (obj == mObject); // if not equal, check if target contains the validation object if(!rval && !obj.isNull() && (obj->getType() == Array || obj->getType() == Map)) { DynamicObjectIterator i = obj.getIterator(); while(!rval && i->hasNext()) { DynamicObject& next = i->next(); rval = (next == mObject); } } if(!rval) { DynamicObject detail = context->addError("monarch.validation.NotFound", &obj); detail["validator"] = "monarch.validator.Contains"; detail["expectedValue"] = mObject; detail["message"] = mErrorMessage ? mErrorMessage : "The input object was not equal to or found in the validator."; } if(rval) { context->addSuccess(); } return rval; }
static DynamicObject _getExceptionGraph( DynamicObject& context, DynamicObject& autoContext, RdfaReader::Graph* g) { DynamicObject rval(NULL); // clone auto context DynamicObject ctx = autoContext.clone(); // use user-set context if(!context.isNull()) { ctx = context.clone(); } // save the old processor target and frame DynamicObject target = g->target; DynamicObject frame = g->frame; // use frame to embed error context in exception g->frame = DynamicObject(); //g->frame["@context"] = JsonLd::createDefaultContext(); g->frame["@type"] = "http://www.w3.org/ns/rdfa_processing_graph#Error"; g->frame["http://www.w3.org/ns/rdfa_processing_graph#context"]->setType(Map); // finish processor graph g->target = DynamicObject(); _finishGraph(ctx, g); rval = g->target; // reset old target and frame g->target = target; g->frame = frame; return rval; }
static bool _finishGraph(DynamicObject& ctx, RdfaReader::Graph* g) { bool rval = true; // sort triples std::sort(g->triples.begin(), g->triples.end(), &_sortTriples); // create a mapping of subject to JSON-LD DynamicObject DynamicObject subjects(Map); for(RdfaReader::TripleList::iterator ti = g->triples.begin(); ti != g->triples.end(); ++ti) { rdftriple* t = *ti; // get predicate const char* predicate = t->predicate; // JSON-LD encode object DynamicObject object(NULL); if(t->object_type == RDF_TYPE_IRI) { object = DynamicObject(); // JSON-LD encode type if(strcmp(t->predicate, RDF_TYPE) == 0) { object = t->object; predicate = "@type"; } else { object["@id"] = t->object; } } else if(t->object_type == RDF_TYPE_TYPED_LITERAL) { object = DynamicObject(Map); object["@value"] = t->object; object["@type"] = t->datatype; if(t->language != NULL && strlen(t->language) > 0) { object["@language"] = t->language; } } else { if(t->language != NULL && strlen(t->language) > 0) { object = DynamicObject(Map); object["@value"] = t->object; object["@language"] = t->language; } else { object = DynamicObject(String); object = t->object; } } // create/get the subject dyno DynamicObject& s = subjects[t->subject]; if(!s->hasMember("@id")) { // JSON-LD encode subject s["@id"] = t->subject; } // add the predicate and object to the subject dyno _setPredicate(s, predicate, object); } // clear triples _freeTriples(g->triples); /* Note: At this point "subjects" holds a reference to every subject in the graph and each of those subjects has all of its predicates. Embedding specific objects in the target according to a frame is next, followed by adding the specific context. */ if(rval) { DynamicObject out; if(!g->frame.isNull()) { rval = JsonLd::frame( subjects.values(), g->frame, g->frameOptions, out); if(rval && out.isNull()) { out = DynamicObject(g->frame->getType()); } } else { rval = JsonLd::compact( subjects.values(), ctx, DynamicObject(Map), out); } if(rval) { // set target to output, preserving original dyno reference *(g->target) = *out; } } return rval; }
/** * Recursively normalizes the given input object. * * Input: A subject with predicates and possibly embedded other subjects. * Output: Either a map of normalized subjects OR a tree of normalized subjects. * * Normalization Algorithm: * * Replace the existing context if the input has '#'. * * For each key-value: * 1. Split the key on a colon and look for prefix in the context. If found, * expand the key to an IRI, else it is already an IRI, add <>, save the * new predicate to add to the output. * 2. If value is a Map, then it is a subject, set the predicate to the * subject's '@' IRI value and recurse into it. Else goto #3. * 3. Look up the key in the context to find type coercion info. If not found, * goto #4, else goto #5. * 4. Check the value for an integer, double, or boolean. If matched, set * type according to xsd types. If not matched, look for <>, if found, * do CURIE vs. IRI check like #1 and create appropriate value. * 5. If type coercion entry is a string, encode the value using the specific * type. If it is an array, check the type in order of preference. If an * unrecognized type (non-xsd, non-IRI) is provided, throw an exception. * * @param ctx the context to use (changes during recursion as necessary). * @param in the input object. * @param subjects a map of normalized subjects. * @param out a tree normalized objects. * @param bnodeId the last blank node ID used. * * @return true on success, false on failure with exception set. */ static bool _normalize( DynamicObject ctx, DynamicObject& in, DynamicObject* subjects, DynamicObject* out, int& bnodeId) { bool rval = true; // FIXME: validate context (check for non-xsd types in type coercion arrays) if(!in.isNull()) { // update context if(in->hasMember("#")) { ctx = in["#"]; } // vars for normalization state DynamicObject subject(NULL); if(subjects != NULL) { subject = DynamicObject(); subject->setType(Map); // assign blank node ID as needed if(!in->hasMember("@")) { string bnodeKey = _createBlankNodeId(++bnodeId); subject["@"] = bnodeKey.c_str(); } } string nKey; // iterate over key-values DynamicObjectIterator i = in.getIterator(); while(rval && i->hasNext()) { DynamicObject& value = i->next(); const char* key = i->getName(); // skip context keys if(key[0] == '#') { continue; } // get normalized key nKey = _normalizeValue(ctx, key, RDF_TYPE_IRI, NULL, NULL); // put values in an array for single code path DynamicObject values; values->setType(Array); if(value->getType() == Array) { values.merge(value, true); // preserve array structure when not using subjects map if(out != NULL) { (*out)[nKey.c_str()]->setType(Array); } } else { values->append(value); } // normalize all values DynamicObjectIterator vi = values.getIterator(); while(rval && vi->hasNext()) { DynamicObject v = vi->next(); if(v->getType() == Map) { if(subjects != NULL) { // get a normalized subject for the value string vSubject; if(v->hasMember("@")) { // normalize existing subject vSubject = _normalizeValue( ctx, v["@"], RDF_TYPE_IRI, NULL, NULL); } else { // generate the next blank node ID in order to preserve // the blank node embed in the code below vSubject = _createBlankNodeId(bnodeId + 1); } // determine if value is a blank node bool isBNode = _isBlankNode(v); // update non-blank node subject (use value's subject IRI) if(!isBNode) { _setPredicate(subject, nKey.c_str(), vSubject.c_str()); } // recurse rval = _normalize(ctx, v, subjects, out, bnodeId); // preserve embedded blank node if(rval && isBNode) { // remove embed from top-level subjects DynamicObject embed = (*subjects)[vSubject.c_str()]; (*subjects)->removeMember(vSubject.c_str()); embed->removeMember("@"); _setEmbed(subject, nKey.c_str(), embed); } } else { // update out and recurse DynamicObject next = (*out)[nKey.c_str()]; if(value->getType() == Array) { next = next->append(); } else { next->setType(Map); } rval = _normalize(ctx, v, subjects, &next, bnodeId); } } else { _setPredicate( (subjects != NULL) ? subject : *out, nKey.c_str(), _normalizeValue(ctx, v, RDF_TYPE_UNKNOWN, key, NULL).c_str()); } } } // add subject to map if(subjects != NULL) { (*subjects)[subject["@"]->getString()] = subject; } } return rval; }
/** * Normalizes a value using the given context. * * @param ctx the context. * @param value the value to normalize. * @param type the expected RDF type, use RDF_TYPE_UNKNOWN if not known. * @param predicate an optional predicate for the value (used to look up * type coercion info). * @param usedCtx a context to update if a value was used from "ctx". * * @return the normalized string. */ static string _normalizeValue( DynamicObject& ctx, DynamicObject value, RdfType type, const char* predicate, DynamicObject* usedCtx) { string rval; // "@" or "a"/RDF_TYPE predicates have values that are IRIs or CURIEs if(predicate != NULL && (strcmp(predicate, "@") == 0 || strcmp(predicate, "a") == 0 || strcmp(predicate, RDF_TYPE_NORM) == 0)) { type = RDF_TYPE_IRI; } // IRI "@" is already normalized if(type == RDF_TYPE_IRI && strcmp(value, "@") == 0) { rval.assign(value); } // IRI "a" is special rdf type else if(type == RDF_TYPE_IRI && strcmp(value, "a") == 0) { rval.assign(RDF_TYPE_NORM); } else { string datatype; // look for type coercion info if(type == RDF_TYPE_UNKNOWN && predicate != NULL && !ctx.isNull() && ctx->hasMember("#types") && ctx["#types"]->hasMember(predicate)) { DynamicObject& tci = ctx["#types"][predicate]; if(usedCtx != NULL) { (*usedCtx)["#types"][predicate] = tci.clone(); } // handle specific type if(tci->getType() == String) { rval = value->getString(); datatype = _normalizeValue(ctx, tci, RDF_TYPE_IRI, NULL, usedCtx); type = (strcmp(datatype.c_str(), XSD_ANY_URI_NORM) == 0) ? RDF_TYPE_IRI : RDF_TYPE_TYPED_LITERAL; } // handle type preferences in order else { DynamicObjectIterator ti = tci.getIterator(); while(type == RDF_TYPE_UNKNOWN && ti->hasNext()) { // FIXME: if value works w/type, set value, datatype, type //type = RDF_TYPE_TYPED_LITERAL; } } } // determine type from DynamicObjectType else if(type == RDF_TYPE_UNKNOWN && value->getType() != String) { type = RDF_TYPE_TYPED_LITERAL; rval = value->getString(); // handle native xsd types if(value->isNumber()) { datatype = value->isInteger() ? XSD_INTEGER : XSD_DOUBLE; } else if(value->getType() == Boolean) { datatype = XSD_BOOLEAN; } else { // FIXME: this should never happen? datatype = XSD_ANY_TYPE; } } else { // JSON-LD decode RdfType t = type; _decode(value, type, rval, datatype); if(type == RDF_TYPE_TYPED_LITERAL) { _expandCurie(ctx, datatype.c_str(), datatype, usedCtx); } // preserve expected type if(t != RDF_TYPE_UNKNOWN) { type = t; } } // expand CURIE if(type == RDF_TYPE_IRI) { _expandCurie(ctx, rval.c_str(), rval, usedCtx); } // JSON-LD encode rval = _encode(type, rval.c_str(), datatype.c_str()); } return rval; }
bool JsonWriter::write(DynamicObject& dyno, OutputStream* os, int level) { bool rval = true; if(level < 0) { level = mIndentLevel; } if(dyno.isNull()) { rval = os->write("null", 4); } else { switch(dyno->getType()) { case String: { const char* temp = dyno->getString(); size_t length = strlen(temp); // Note: UTF-8 has a maximum of 6-bytes per character when // encoded in json format ("\u1234") string encoded; encoded.reserve(length); encoded.push_back('"'); char unicode[6]; for(size_t i = 0; i < length; ++i) { unsigned char c = temp[i]; if((c >= 0x5d /* && c <= 0x10FFFF */) || (c >= 0x23 && c <= 0x5B) || (c == 0x21) || (c == 0x20)) { // TODO: check this handles UTF-* properly encoded.push_back(c); } else { encoded.push_back('\\'); switch(c) { case '"': /* 0x22 */ case '\\': /* 0x5C */ // '/' is in the RFC but not required to be escaped //case '/': /* 0x2F */ encoded.push_back(c); break; case '\b': /* 0x08 */ encoded.push_back('b'); break; case '\f': /* 0x0C */ encoded.push_back('f'); break; case '\n': /* 0x0A */ encoded.push_back('n'); break; case '\r': /* 0x0D */ encoded.push_back('r'); break; case '\t': /* 0x09 */ encoded.push_back('t'); break; default: // produces "u01af" (4 digits of 0-filled hex) snprintf(unicode, 6, "u%04x", c); encoded.append(unicode, 5); break; } } } // end string serialization and write encoded string encoded.push_back('"'); rval = os->write(encoded.c_str(), encoded.length()); break; } case Boolean: case Int32: case UInt32: case Int64: case UInt64: case Double: { const char* temp = dyno->getString(); rval = os->write(temp, strlen(temp)); break; } case Map: { // start map serialization rval = (mCompact || dyno->length() == 0) ? os->write("{", 1) : os->write("{\n", 2); // serialize each map member DynamicObjectIterator i = dyno.getIterator(); while(rval && i->hasNext()) { DynamicObject& next = i->next(); // serialize indentation and start serializing member name if((rval = writeIndentation(os, level + 1) && os->write("\"", 1) && os->write(i->getName(), strlen(i->getName())))) { // end serializing member name, serialize member value rval = ((mCompact) ? os->write("\":", 2) : os->write("\": ", 3)) && write(next, os, level + 1); // serialize delimiter if appropriate if(rval && i->hasNext()) { rval = os->write(",", 1); } // add formatting if appropriate if(rval && !mCompact) { rval = os->write("\n", 1); } } } // end map serialization if(dyno->length() > 0) { rval = rval && writeIndentation(os, level); } rval = rval && os->write("}", 1); break; } case Array: { // start array serialization rval = (mCompact || dyno->length() == 0) ? os->write("[", 1) : os->write("[\n", 2); // serialize each array element DynamicObjectIterator i = dyno.getIterator(); while(rval && i->hasNext()) { // serialize indentation and array value rval = writeIndentation(os, level + 1) && write(i->next(), os, level + 1); // serialize delimiter if appropriate if(rval && i->hasNext()) { rval = os->write(",", 1); } // add formatting if appropriate if(rval && !mCompact) { rval = os->write("\n", 1); } } // end array serialization if(dyno->length() > 0) { rval = rval && writeIndentation(os, level); } rval = rval && os->write("]", 1); break; } } } return rval; }
bool Int::isValid( DynamicObject& obj, ValidatorContext* context) { bool rval; DynamicObjectType objType; rval = !obj.isNull(); if(rval) { objType = obj->getType(); if(objType == String) { objType = DynamicObject::determineType(obj->getString()); } // type check rval = objType == Int32 || objType == UInt32 || objType == Int64 || objType == UInt64; } if(!rval) { DynamicObject detail = context->addError("monarch.validation.ValueError", &obj); detail["validator"] = "monarch.validator.Int"; detail["message"] = mErrorMessage ? mErrorMessage : "The given value type is required to be an integer."; } // absolute value of dyno value uint64_t val = 0; // flag if val is negative bool valneg = false; // get value for min/max check if(rval) { // get value and sign switch(objType) { case Int32: case Int64: { int64_t raw = obj->getInt64(); valneg = (raw < 0); val = (uint64_t)(valneg ? -raw : raw); break; } case UInt32: case UInt64: { valneg = false; val = obj->getUInt64(); break; } default: // never get here break; } } // min check if(rval) { if(mMinNegative != valneg) { // signs are different // val meets the minimum unless val is negative rval = !valneg; } else { // signs are the same // val meets the minimum if: // 1. val is positive and larger than mMin // 2. val is negative and smaller than mMin rval = (!valneg ? val >= mMin : val <= mMin); } // set exception on failure if(!rval) { DynamicObject detail = context->addError("monarch.validation.ValueError", &obj); detail["validator"] = "monarch.validator.Int"; detail["message"] = mErrorMessage ? mErrorMessage : "The given integer value is less than the required minimum " "integer value."; detail["expectedMin"] = mMin; } } // max check if(rval) { if(mMaxNegative != valneg) { // signs are different // val meets the maximum unless val is positive rval = valneg; } else { // signs are the same // val meets the maximum if: // 1. val is positive and smaller than mMax // 2. val is negative and larger than mMax rval = (valneg ? val >= mMax : val <= mMax); } // set exception on failure if(!rval) { DynamicObject detail = context->addError("monarch.validation.ValueError", &obj); detail["validator"] = "monarch.validator.Int"; detail["message"] = mErrorMessage ? mErrorMessage : "The given integer value is greater than the allowable maximum " "integer value."; detail["expectedMax"] = mMax; } } if(rval) { context->addSuccess(); } return rval; }
bool DatabaseClient::mapInstance( const char* objType, DynamicObject& obj, DynamicObject& mapping, DynamicObject* userData) { bool rval = false; /* Algorithm to create a column mapping to the values in the given object: 1. Get the OR mapping for the object type. 2. Iterate over the members in the mapping, assigning each one's associated information to a matching member in the instance object. 3. Include any values from the instance object in the instance mapping validating and coercing types as needed. */ /* The instance mapping format: * * mapping: { * "tables": {} of table name to table entry * "entries": [ * "table": the database table name * "columns": [ * (cloned info from the OR mapping) + * "member": the object member name * "value": the value for the column (to apply via an operator, * coerced to columnType) * "userData": as given to this call * ], * "fkeys": [ * (cloned info from the OR mapping) + * "member": the object member name * "value": the value for "fcolumn" (coerced to columnType) * "userData": as given to this call * ] * ] * } */ // initialize mapping mapping["tables"]->setType(Map); mapping["entries"]->setType(Array); // get OR map for the given object type ObjRelMap orMap = getObjRelMap(objType); if(!orMap.isNull()) { rval = true; DynamicObjectIterator i = orMap["members"].getIterator(); while(rval && i->hasNext()) { // get OR member info DynamicObject& info = i->next(); // if object is NULL, then we want to get ALL members if(obj.isNull() || obj->hasMember(i->getName())) { // start building an instance mapping entry DynamicObject entry(NULL); // add/update entry based on table const char* table = info["table"]->getString(); if(mapping["tables"]->hasMember(table)) { // update existing entry entry = mapping["tables"][table]; } else { // add a new entry entry = mapping["entries"]->append(); entry["table"] = info["table"]; entry["columns"]->setType(Array); entry["fkeys"]->setType(Array); if(orMap->hasMember("autoIncrement") && orMap["autoIncrement"]->hasMember(table)) { const char* id = orMap["autoIncrement"][table]->getString(); entry["autoIncrement"]["id"] = id; entry["autoIncrement"]["type"]->setType( orMap["members"][id]["columnType"]->getType()); } mapping["tables"][table] = entry; } // clone info, add member name and user-data DynamicObject d = info.clone(); d["member"] = i->getName(); if(userData != NULL) { d["userData"] = *userData; } // set value if(obj.isNull()) { d["value"]; } else { // FIXME: validate data type d["value"] = obj[i->getName()].clone(); } // coerce data type to match column type d["value"]->setType(d["columnType"]->getType()); // add to entry based on group entry[info["group"]->getString()]->append(d); } } } return rval; }