I have array with some values :
let typeArray = ["name", "strret", "car", "type"];
And I have one object :
let formDefinitionObject = {schema:{}}
I want the formDefinitionObject object be like :
let formDefinitionObject = {
schema: {
name: {
type: 'string',
title: 'Name',
required: true
},
strret: {
type: 'string',
title: 'Strret'
},
car: {
type: 'string',
title: 'Car'
},
type: {
type: 'string',
title: 'Type'
}
}
}
I want dynamically for each item in array to be object in formDefinitionObject.schema object. For example if i add one more item in array typeArray.push('country') to automatic add this object in formDefinitionObject.schema object.
Couldn't understand how required: true would fit in. Remaining things can be done as follows
var getFormDefinition = function(arr) {
function getSchemaObj(arr) {
return arr.map(d => ({
[d]: {
type: typeof(d),
title: d
}
}))
.reduce((a, b) => ({ ...a, ...b }))
}
var schemaObj = getSchemaObj(arr)
arr.push = function(...d) {
Object.assign(schemaObj, getSchemaObj(d))
return Array.prototype.push.call(arr, ...d)
}
return ({
schema: schemaObj
})
}
var typeArray = ["name", "strret", "car", "type"];
var result = getFormDefinition(typeArray)
console.log(result)
typeArray.push('nitish')
console.log(result)
Even though you did not clarify more how a field has to be required or does any field has to be as string, here's a solution based on what you provided so far.
The next snippet does the job and it has some explanations :
let typeArray = ['name', 'strret', 'car', 'type'],
formDefinitionObject = {
schema: {}
};
/** cycle through "typeArray" and populate "formDefinitionObject.schema" **/
typeArray.forEach(el => {
let currObj = {
type: 'string', /** YOU DID NOT SPECIY HOW TO DECIDE THE TYPE **/
title: el[0].toUpperCase() + el.substring(1), /** the title with the first letter being capitalized as you provided in the question. You can just use "el" instead of "el[0].toUpperCase() + el.substring(1)" if you'd like to print as it is **/
};
el === 'name' && (currObj['required'] = true); /** YOU DID NOT SPECIY HOW TO DECIDE IF A FIELD HAS TO BE REQUIRED. I just saw only the "name" as required so I did a basic (yet a stupid) check if the current element is "name" add a required to it **/
formDefinitionObject.schema[el] = currObj; /** add the current record to the "schema" attribute **/
});
console.dir(formDefinitionObject); /** printing the result **/
I'll be here if you answer our questions in the comments section.
Til then, hope I pushed you further.
You could use Proxy on the typeArray with the set trap so each time you push new value to the proxy array you can also add new property to your schema. This way you can simulate observer pattern.
You can also create some pattern to add additional properties like required for example name:prop1:prop2 but this way value is fixed to true.
let typeArray = ["name:required", "strret", "car", "type"];
let formDefinitionObject = {
schema: {}
}
let proxyArray = new Proxy(typeArray, {
set(obj, prop, value) {
if (prop != 'length') addToSchema(formDefinitionObject.schema, value);
return Reflect.set(...arguments);
}
})
function capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function addToSchema(schema, prop) {
const [name, ...params] = prop.split(':');
schema[name] = {
type: 'string',
title: capitalize(name)
}
params.forEach(param => schema[name][param] = true);
return schema;
}
proxyArray.reduce(addToSchema, formDefinitionObject.schema);
proxyArray.push('email:required:isEmail');
proxyArray.push('phone');
console.log(formDefinitionObject)
Update: You could use something like this name:prop1|value:prop2 to add property value other then true but if you don't specify value default is still true
let typeArray = ["name:required", "strret", "car", "type"];
let formDefinitionObject = {
schema: {}
}
let proxyArray = new Proxy(typeArray, {
set(obj, prop, value) {
if (prop != 'length') addToSchema(formDefinitionObject.schema, value);
return Reflect.set(...arguments);
}
})
function capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function addToSchema(schema, prop) {
const [name, ...params] = prop.split(':');
schema[name] = {
type: 'string',
title: capitalize(name)
}
params.forEach(param => {
const [key, value] = param.split('|');
schema[name][key] = value ? value : true
});
return schema;
}
proxyArray.reduce(addToSchema, formDefinitionObject.schema);
proxyArray.push('email:required:isEmail');
proxyArray.push('phone:default|123/555-333:required');
proxyArray.push('address')
console.log(formDefinitionObject)
Related
I am converting the nested data object to the arrays, for the UI Library to show the relationship between the data.
Original
// assume that all object key is unique
{
"top":{
"test":{
"hello":"123"
},
"test2":{
"bye":"123"
"other":{
...
...
...
}
}
}
}
Preferred Result
[
{
id:"top",
parent: null,
},
{
id:"test",
parent: "top",
},
{
id:"hello",
parent: "test",
},
{
id:"test2",
parent: "top",
},
]
To do this, I write the code like this:
const test = []
const iterate = (obj, parent = null) => {
Object.keys(obj).forEach(key => {
const id = typeof obj[key] === 'object' ? key : obj[key]
const loopObj = {
id,
parent
}
test.push(loopObj)
if (typeof obj[key] === 'object') {
iterate(obj[key], id)
}
})
}
iterate(data)
console.log(test) // Done!!
It works.
However, I miss one important things, the library need the layers from the original data, to determine the type/ what function to do.
// The key name maybe duplicated in different layer
{
"top":{ // Layer 1
"test":{ // Layer 2
"hello":"123", // Layer 3
"test":"123" // Layer 3
// Maybe many many layers...
}
}
}
[
{
id:"top",
display:"0-top",
parent: null,
layer: 0
},
{
id: "1-top-test", // To prevent duplicated id, `${layer}-${parentDisplay}-${display}`
display:"test",
parent: "0-top",
parentDisplay: "top",
layer: 1
},
{
id: "3-test-test", // To prevent duplicated id,`${layer}-${parentDisplay}-${display}`
display:"test",
parent: "2-top-test",
parentDisplay: "test",
layer: 3
}
]
Editing the display or id format is very simple, just edit the function and add the field, but I don't know how to get the layer easily.
I tried to add the let count = 0 outside and do count++ when iterate function called.But I realized that it hit when the object detected, no by layers.
The original data may be very big,
So I think editing the original data structure or searching the parent id in the test[] every loop may be not a good solution.
Is there any solution to do this?
Just add the current depth as an argument that gets passed down on every recursive call (as well as the parent name).
const input = {
"top":{
"test":{
"hello":"123"
},
"test2":{
"bye":"123",
"other":{
}
}
}
};
const iterate = (obj, result = [], layer = 0, parentId = null, parentDisplay = '') => {
Object.entries(obj).forEach(([key, value]) => {
const id = `${layer}-${key}`;
result.push({
id,
display: key,
parentId,
parentDisplay,
layer,
});
if (typeof value === 'object') {
iterate(value, result, layer + 1, id, key);
}
});
return result;
}
console.log(iterate(input));
That said, your desired approach can still produce duplicate entries, if there exist two objects at the same level, with different grandparent objects, but whose parent objects use the same key, eg:
const input = {
"top1":{
"test":{
"hello":"123"
},
},
"top2": {
"test": {
"hello":"123"
}
}
};
const input = {
"top1":{
"test":{
"hello":"123"
},
},
"top2": {
"test": {
"hello":"123"
}
}
};
const iterate = (obj, result = [], layer = 0, parentId = null, parentDisplay = '') => {
Object.entries(obj).forEach(([key, value]) => {
const id = `${layer}-${key}`;
result.push({
id,
display: key,
parentId,
parentDisplay,
layer,
});
if (typeof value === 'object') {
iterate(value, result, layer + 1, id, key);
}
});
return result;
}
console.log(iterate(input));
If that's a problem, consider passing down the entire accessor string needed to access the property - eg top1.test.hello and top2.test.hello, which is guaranteed to be unique.
const input = {
"top1":{
"test":{
"hello":"123"
},
},
"top2": {
"test": {
"hello":"123"
}
}
};
const iterate = (obj, result = [], parentAccessor = '') => {
Object.entries(obj).forEach(([key, value]) => {
const accessor = `${parentAccessor}${parentAccessor ? '.' : ''}${key}`;
result.push({
id: key,
accessor,
});
if (typeof value === 'object') {
iterate(value, result, accessor);
}
});
return result;
}
console.log(iterate(input));
Updated object keys
let conditionedObject = {
"isNameRequired": true,
"isCityRequired": false,
"isPostRequired": true
};
let myTotalData = {
data: {
"givenName": 'myname',
"street":"mystreet",
"cityName": 'mycity',
"postcode": 'mypost'
}
};
let resultData = {};
Both condionedObject and myTotalData comes from different source.
I would like to know the best way to create a new object based on the condionedObject,
example my condionedObject says I need only name and post so my resultData should return {"givenName":"myname","postcode":"mypost"}
conditionedObject.isNameRequired is for myTotalData.data.givenName, conditionedObject.isPostRequired is for myTotalData.data.postcode,
conditionedObject.isCityRequired is for myTotalData.data.cityName, all this will say whether myTotalData key is required to be placed in the new object.
Thanks in advance for all the suggestions and helps
You can use Array.prototype.reduce():
In this case I recommend you to create a new object to store the relationship between properties:
let conditionedObject = {
"isNameRequired": true,
"isCityRequired": false,
"isPostRequired": true
};
let myTotalData = {
data: {
"givenName": 'myname',
"street":"mystreet",
"cityName": 'mycity',
"postcode": 'mypost'
}
};
const reference = {
"isNameRequired": "givenName",
"isCityRequired": "cityName",
"isPostRequired": "postcode"
}
let resultData = {};
resultData =Object.entries(conditionedObject).reduce((obj, [key, value]) => {
if(value && myTotalData.data[reference[key]]) {
const prop = reference[key];
obj[prop] = myTotalData.data[prop];
}
return obj;
}, {});
console.log(resultData);
Here is my solution:
You need to change the keys in the object conditionedObject and remove the is- prefix so that they are the same as the keys in myTotalData. This way the check is easier later.
Then it's pretty straight-forward.
let conditionedObject: {[key: string]: boolean} = {
"isName": true,
"isCity": false,
"isPost": true
};
type DataType = {[key: string]: string};
let myTotalData: DataType = {
"name": 'myname',
"city": 'mycity',
"post": 'mypost'
};
let resultData: DataType = {};
for(const key in conditionedObject) {
// Ensure that the key is really part of this object
if(!conditionedObject.hasOwnProperty(key)) {
continue;
}
if(!conditionedObject[key]) {
continue;
}
const dataKey = key.startsWith("is")
? key.charAt(2).toLowerCase() + key.substring(3)
: key;
if(myTotalData.hasOwnProperty(dataKey)) {
resultData[dataKey] = myTotalData[dataKey];
}
}
console.log(resultData);
Output:
{
name: "myname",
post: "mypost"
}
You can use something like that, if you can be sure that the conditionedObject keys are always equal your myTotalData keys, but capitalized and with "is" on the front:
let capitalize = str => str.charAt(0).toUpperCase() + str.slice(1)
let prependIs = str => "is" + str
let conditionedObject = {
"isName": true,
"isCity": false,
"isPost": true
};
let myTotalData = {
"name": 'myname',
"city": 'mycity',
"post": 'mypost'
};
let filterData = (data, conditionObj) => Object.fromEntries(Object.entries(data)
.filter(([key, _]) => conditionObj[prependIs(capitalize(key))]))
// More verbose alternative for browsers which doesn't support `Object.fromEntries()`
let filterDataAlt = (data, conditionObj) => {
let result = {}
let filteredEntries = Object.entries(data)
.filter(([key, _]) => conditionObj[prependIs(capitalize(key))])
for (let [key, value] of filteredEntries) {
result[key] = value
}
return result
}
console.log(filterData(myTotalData, conditionedObject))
console.log(filterDataAlt(myTotalData, conditionedObject))
I've got a nested object with child objects and arrays. Something like this:
const documents = {
invoice: {
documentID: '_e4564',
displayName: '2019-02-03',
url: 'https://www.urltoinvoice.com'
},
conditions: {
documentID: '_e9365',
displayName: 'Conditions company x',
url: 'https://www.urltoconditions.com'
},
reminders: [
{
documentID: '_e4364',
displayName: 'First reminder',
url: 'https://www.urltofirstreminder.com'
},
{
documentID: '_e0254',
displayName: 'Second reminder',
url: 'https://www.urltosecondreminder.com'
},
]
}
I'm trying to create a new array of objects to use in a select box.
The child objects need the same properties but with an updated displayName based on the document type. So, for example, reminder: First reminder .
Currently, this is my code:
const newArray = [];
this.addDocumentToArray(documents, newArray);
and the addDocumentToArray function:
addDocumentToArray = (documents, arr) => {
Object.entries(documents).forEach(([key, val]) => {
if (Array.isArray(val)) {
this.addDocumentToArray(val, arr);
} else {
arr.push({ documentID: val.documentID, displayName: `${key}: ${val.displayName}` });
}
});
}
The output at this point is an array that looks like this:
0: {documentID: "_e4564", displayName: "invoice: 2019-02-03"}
1: {documentID: "_e9365", displayName: "conditions: Conditions company x"}
2: {documentID: "_e4364", displayName: "0: First reminder"}
3: {documentID: "_e0254", displayName: "1: Second reminder"}
Almost ok but the key of the reminders is 0 and 1. How can I get reminder (or reminders) as key?
You can add third optional parameter to function labelKey. You are passing that parameter only if your value is array and it will use that value as key in else part
addDocumentToArray = (documents, arr, labelKey) => {
Object.entries(documents).forEach(([key, val]) => {
if (Array.isArray(val)) {
this.addDocumentToArray(val, arr, key);
} else {
arr.push({ documentID: val.documentID, displayName: `${labelKey || key}: ${val.displayName}` });
}
});
}
I really like chriss' answer, so I'll try to write an alternative:
let currentKey = null;
addDocumentToArray = (documents, arr) => {
Object.entries(documents).forEach(([key, val]) => {
if (Array.isArray(val)) {
let prevKey = currentKey;
currentKey = key;
this.addDocumentToArray(val, arr);
currentKey = prevKey;
} else {
arr.push({ documentID: val.documentID, displayName: `${currentKey || key}: ${val.displayName}` });
}
});
}
I'm trying to figure out how to add new items into array instead of overriding the current value with the new value. I'm using .push() which should add the item every time it maps through the array. Any Ideas?
const searchChips = [
{value: "string"}, {value: "test"}
];
const query = {
bool: {
filter: []
}
};
const searchQuery = {
query_string: {
query: ""
}
};
searchChips.map(chip => {
console.log(chip);
const key = "query";
searchQuery.query_string[key] = chip.value;
query.bool.filter.push(searchQuery);
});
console.log(query);
You are inserting the same query since you are dealing with the same exact reference to the searchQuery. Instead of this try having it as a function which returns an object:
const searchChips = [{
value: "string"
}, {
value: "test"
}];
const query = {
bool: {
filter: []
}
};
let sq = (query) => ({
query_string: {query}
});
searchChips.map(chip => query.bool.filter.push(sq(chip.value)));
console.log(query);
This will return to you the 2 filters each with different values for query_string since now the function will return an entirely new object instead of you dealing with the same reference.
The problem seems to be that you are pushing into query.bool.filter outside the .map() function. Try this.
const searchChips = [{ value: "string" }, { value: "test" }];
const query = {
bool: {
filter: []
}
};
searchChips.forEach(chip => {
const key = "query";
const searchQuery = {
query_string: {
query: ""
}
};
searchQuery.query_string[key] = chip.value;
query.bool.filter.push(searchQuery);
});
console.log(JSON.stringify(query));
Basically what I need is to get "items" from json and re-assign it, and leave other keys untouched:
The first approach duplicates data.
The second seems to be bad in terms of performance.
And the third is hard to read and understand.
I use lodash. But if it can't be done in a clever fashion, you can suggest me a different library.
function a(name, json) {
return {
type: RECEIVE_DATA,
name,
items: _.get(json, 'data.items', []),
receivedAt: Date.now(),
...json,
};
}
function b(name, json) {
return {
..._.omit(json, 'data'),
type: RECEIVE_DATA,
name,
items: _.get(json, 'data.items', []),
receivedAt: Date.now(),
}
}
function c(name, json) {
return {
..._.transform(json, (result, value, key) =>{
if (key === 'data') {
result['items'] = value['items'];
} else {
result[key] = value;
}
}, {}),
type: RECEIVE_DATA,
name,
receivedAt: Date.now(),
}
}
Use parameters destructuring to get items, and rest properties to collect the rest of the params:
const json = { another: [], data: { items: [1] } };
function a(name, { data, data: { items = [] }, ...jsonRest } = {}) {
return {
type: 'RECEIVE_DATA',
name,
items,
receivedAt: Date.now(),
...jsonRest
};
}
console.log(a('name', json));
You can also shorten the action creator a bit by using an arrow function:
const a = (name, { data, data: { items = [] }, ...jsonRest } = {}) => ({
type: RECEIVE_DATA,
name,
items,
receivedAt: Date.now(),
...jsonRest
});
If you don't mind having a couple more lines in your function, you could easily just grab the data from that key then delete it:
function a(name, json = {}) {
const { items = [] } = json.data;
delete json.data;
return {
type: RECEIVE_DATA,
name,
items,
receivedAt: Date.now(),
...json,
};
}