How to prevent duplicates being added to JSON object - json

Using Electron and electron-store to add files' simplified executable names and their full paths from showOpenDialog to config.json. Selecting the same file causes repeating entries in config.json. For some reason (or rather missing code), app thinks they're different paths.
function addTool() {
dialog.showOpenDialog({
title: 'Select tool executable.',
filters: [{
name: 'Tool start file',
extensions: ['exe', 'jar']
}],
properties: ['openFile']
},
(exeFromDialog) => {
var var_exeToolPath = exeFromDialog.join(); //removes square brackets
var var_toolName = path.basename(var_exeToolPath).split(/[/._-]/g)[0];
//path.basename removes path until file, split+regex takes only first part until first character (one of ._/)
const tools = appConfig.get('tools');
const newTool = [...(tools || []), {
"toolName": var_toolName,
"toolPath": var_exeToolPath
}];
appConfig.set('tools', newTool);
})
}
This is how config.json looks when you open the same file few times:
{
"winPosition": {
"x": 1497,
"y": 410,
"width": 203,
"height": 603
},
"exePOEPath": [
"C:\\Program Files (x86)\\Grinding Gear Games\\Path of Exile\\PathOfExile_x64.exe"
],
"tools": [
{
"toolName": "tool1",
"toolPath": "D:\\tool1.exe"
},
{
"toolName": "tool1",
"toolPath": "D:\\tool1.exe"
},
{
"toolName": "tool1",
"toolPath": "D:\\tool1.exe"
}
]
}

Ultimately it comes to the question How to remove duplicates from your array
This part of your code will always add the new value, it doesn't check for duplicates
const newTool = [...(tools || []), {
toolName: var_toolName,
toolPath: var_exeToolPath
}]
So it should be improved to something like the following:
newTool = newTool.filter((item, pos, self) =>
self.find(other => other.toolName === item.toolName) === item
)
I would prefer using [...new Set([newTool])] but you store Objects which are compared by reference thus duplicates cannot be eliminated by Set

Related

discord.js 12 any way to send data from json file to channel?

I have json file, that contains info about some players, that looks like this
{
"208505383361314816": {
"warns": 8,
"reason": "test"
},
"776387838350196756": {
"warns": 99,
"reason": ""
}
}
Then I sort the information by the number of warns. It works perfectly, but i have no idea, how to send a message.
client.on('message', message => {
if (message.content.startsWith('>topw')) {
const sorted = [];
const keys = Object.keys(warns)
for (let user in warns) {
const warny = warn[user].warns;
const entry = {
[keys[sorted.length]]: warns[user]
}
if (sorted.length === 0) {
sorted.push(entry);
continue;
}
let i = 0;
while (sorted[i] !== undefined && sorted[i][Object.keys(sorted[i])].warns > warny) {
i++;
}
sorted.splice(i, 0, entry)
}
console.log(sorted)
}
})
It should look like a "leaderboard", but with amount of warns
e.g:
name: Bob
warns: 20
reason: test
Welcome to StackOverflow! :)
About your JSON file
First of all, quick advice regarding the way you store data: you really, really should store the user info in an array, it would look like that:
[
{
"id": "00000000",
"warns": 10,
"reason": "test"
}
]
Getting your data ready
That being said, let's answer your question.
I guess that you'd love to use MessageEmbeds, take a look at the docs before reading the code, it might help. I will also rewrite a little bit of your code.
Reformating it
To make our life easier, we'll reformat the data so we can sort it.
// It's just your JSON file here
const oldWarns = {
"208505383361314816": {
"warns": 8,
"reason": "test"
},
"776387838350196756": {
"warns": 99,
"reason": ""
}
}
let newWarns = Object.keys(oldWarns).map((e) => ({
id: e,
warns: oldWarns[e].warns,
reason: oldWarns[e].reason
}))
console.log(newWarns)
Sorting it
Now that we've got this beautiful array, let's sort it. It's much simpler than you think!
// Precedently generated data
let newWarns = [{
"id": "208505383361314816",
"warns": 8,
"reason": "test"
},
{
"id": "776387838350196756",
"warns": 99,
"reason": ""
}
]
newWarns.sort((a, b) => (a.warns > b.warns ? 1 : -1))
console.log(newWarns)
Sending the data
What you actually asked for.
Rendering it with an embed
Alright, everything is set so we can send the data. Here's how to use embeds in Discord.js.
const warnBoard = new Discord.MessageEmbed()
.setTitle("Warnboard")
.setColor("#FF0000")
.addFields(newWarns.flatMap((e, i) => ( // flatMap will delete all the [] of the array after mapping it
i >= 25 ? [] : { // the max number of fields in embeds is 25
name: e.id,
value: e.warns,
inline: true // changes the layout
}
)))
I just used a few of MessageEmbed's features. They can be really nice looking, so feel free to mess around with its props!
Making it as simple as it can get
Embeds are too much? You can also just send a string.
const stringifiedData = JSON.stringify(newWarns, null, 4)
const warnBoard = "```json\n"+ stringifiedData +"\n```"
Send it
Whether the solution you chose is the string or the embed, this is the way you're gonna send the data.
message.channel.send(warnBoard)

Restructuring a large amount of values in a JSON file

I have a JSON file with a large amount of the following values:
"values": [
"Foo": 1,
"Bar": 2,
"Baz": 3,
...
],
How do I efficiently convert this into:
"values": [
{
"name": "Foo",
"value": 1
},
{
"name": "Bar",
"value": 2
},
{
"name": "Baz",
"value": 3
},
...
],
Any help would be appreciated!
Okay, so there are two problems with your input. The first is the fact that the given JSON is invalid, so can't directly be parsed. The square brackets after "values" should be curly brackets, to allow for a hash instead of an array:
let raw_old_data =
// Read the old file
fs.readFileSync('./input_data.json').toString()
// Remove all newlines which could interfere with the regex
.replace(/[\r\n]/g, '')
// Replace the square brackets after `"values"` with curly braces
.replace(/"values": \[(.+?)\]/g, '"values": { $1 }');
To convert this (now valid) string to a JSON object, you use JSON.parse:
let old_data = JSON.parse(raw_old_data);
The second problem is that the format in which the values are stored doesn't match your needs. You want to convert from { key: "value" } to [ name: "key", value: "value" ]. The following function can do that, assuming your version of Node supports ES6 (If not, look at Murillo's answer):
function fix_format(obj) {
// This is where we keep the new items in the correct format
let res = [];
// Loop over all values
Object.keys(obj.values).forEach(name => {
let value = obj.values[name];
// Change the format and add to resulting array
res.push({
// If the variable is the same as the key of the hash, it doesn't have to be specified
name,
value,
});
});
return res;
}
All that's then left to do is loop all data from the old object through that function with the Array.map function:
let new_data = old_data.map(fix_format);
And optionally write it back to a file to use with a different program:
fs.writeFileSync('./formatted_data.json', JSON.stringify(data, null, 2));
Note: The 2 in the JSON.stringify function indicates that the resulting JSON should be padded with 2 spaces, to keep it readable.
With ES6:
Object.keys(values).map(name => ({
name,
value: values[name]
}))
Without ES6:
var keys = Object.keys(values);
var newValues = [];
for(var i = 0; i < keys.length; i++){
newValues.push({
name: keys[i],
value: values[keys[i]]
})
}
If your intention is to use the received data i.e obtain data from DB (e.g MSSql, MySql...) using the connection.query(your_custom_sql_query, (err, rows, fields)
for more info:Node.js MySQL Select From Table
I'll recommend you to use:
const myJson = JSON.stringify(rows[0]);

Winston log format

i am using Winston ^3.0.0-rc6 as below :
var options = {
file: {
level: 'info',
filename: `${appRoot}/logs/app.log`,
handleExceptions: true,
json: true,
prettyPrint: true,
maxsize: 5242880, // 5MB
maxFiles: 5,
colorize: true,
}
};
const jsonFormatter = (logEntry) => {
if (logEntry.type) {
const base = {
timestamp: new Date()
};
const json = Object.assign(base, logEntry);
logEntry[MESSAGE] = JSON.stringify(json);
} else {
logEntry = "";
}
return logEntry;
}
const logger = winston.createLogger({
format: winston.format(jsonFormatter)(),
transports: [
new winston.transports.File(options.file)
],
exceptionHandlers: [
new winston.transports.File(options.uncaughtExceptions)
]
});
my log output :
{"timestamp":"2018-06-10T07:41:03.387Z","type":"Authentication","status":"failed","level":"error","message":"Incorrect password"}
but i want them to be like :
{
"timestamp": "2018-06-10T07:41:03.387Z",
"type": "Authentication",
"status": "failed",
"level": "error",
"message": "Incorrect password"
}
i tried to play around with json : true , and prettyPrint but it did not do the trick .
Can any one help please
Thanks.
I noticed in your code that on the line
logEntry[MESSAGE] = JSON.stringify(json);
you're using JSON.stringify() which takes two more optional arguments
JSON.stringify(value[, replacer[, space]])
If you set space to the amount of spaces you'd like you'll get the output you're looking for. So change the initial line to be:
logEntry[MESSAGE] = JSON.stringify(json, null, 2); // or 4 ;)
(The replacer argument is null because we don't want to change the default behavior.)
This is deprecated: You can check the link here.
I tried to play around with json: true, and prettyPrint but it did not do the trick.
Simple code like this work for you:
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
//
// - Write to all logs with level `info` and below to `combined.log`
// - Write all logs error (and below) to `error.log`.
//
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
If this does not work, let me know so that I can improvise.

Redux and Calendar repeating events

What should be the proper way of storing / handling repeating events in the redux store ?
Problem: Let's say that we have a backend API that generates repeating events trough a complicated business logic.Some of the events might have the same ID. Lets say that generated output looks this way :
[
{
"id": 1,
"title": "Weekly meeting",
"all_day": true,
"starts_at": "2017-09-12",
"ends_at": "2017-09-12"
},
{
"id": 3,
"title": "Daily meeting1",
"all_day": false,
"starts_at": "2017-09-12",
"ends_at": "2017-09-12",
},
{
"id": 3,
"title": "Daily meeting1",
"all_day": false,
"starts_at": "2017-09-13",
"ends_at": "2017-09-13",
},
{
"id": 3,
"title": "Daily meeting1",
"all_day": false,
"starts_at": "2017-09-14",
"ends_at": "2017-09-14",
}
]
Possible solution would be: generate unique ID by having additional property uid composed like this: id + # + starts_at. This way we could identify each occurrence uniquely. (I'm using this right now)
Example:
[
{
"id": 1,
"uid": "1#2017-09-12",
"title": "Weekly meeting",
"all_day": true,
"starts_at": "2017-09-12",
"ends_at": "2017-09-12"
}
]
I'm wondering is there some other way, maybe more elegant than having composed unique id ?
There is a possible pitfall with your current solution. What will happen if id and start_id of two events will be the same? Is it possible scenario in your domain?
Because of that I usually use this nice lib in such cases. It produces really short unique ids, which have some nice properties, like guaranties not to intersect, to be unpredictable and so on.
Also ask yourself if you actually need unique ids in your case. Looks like your back-end have no chance to distinguish events anyways, so why bother? Redux store will happily keep your events event without uid.
Maybe not much of an improvement (if at all) but just using JSON.stringify to check for duplicates could make unique id's obsolete.
const existingEvents = [
{
"id": 3,
"title": "Daily meeting1",
"all_day": false,
"starts_at": "2017-09-14",
"ends_at": "2017-09-14",
}
];
const duplicate = {
"id": 3,
"title": "Daily meeting1",
"all_day": false,
"starts_at": "2017-09-14",
"ends_at": "2017-09-14",
};
const eventIsDuplicate = (existingEvents, newEvent) => {
const duplicate =
existingEvents.find(event => JSON.stringify(event) == JSON.stringify(newEvent));
return typeof duplicate != 'undefined';
};
console.log(eventIsDuplicate(existingEvents, duplicate)); // true
I guess this would only be preferable to your existing solution if, for some reason, you'd want to keep all the uniqueness logic on the client side.
As far as I understand the examples you've given, it seems like the server is sending a particular event whenever the details of the event change.
If that is so, and you want to track the changes to events, your might shape might be an array of objects with all the fields of the event that hold the current data, and a history property which is an array of all previous (or n most recent) event objects and the timestamps at which they were received. This is how your reducers would look, storing only the five most recent event changes for each event. I'm expecting the action to have a payload property which has your standard event property and a timestamp property, which can be easily accomplished in the action creator.
const event = (state = { history: [] }, action) => {
switch (action.type) {
case 'EVENT_FETCHED':
return ({
...action.payload.event,
history: [...state.history, action.payload].slice(-5),
});
default:
return state;
}
};
const events = (state = { byID: {}, IDs: [] }, action) => {
const id = action.payload.event.ID;
switch (action.type) {
case 'EVENT_FETCHED':
return id in state.byID
? {
...state,
byID: { ...state.byID, [id]: event(state.byID[id], action) },
}
: {
byID: { ...state.byID, [id]: event(undefined, action) },
IDs: [id],
};
default:
return state;
}
};
Doing this, you don't need any unique ID. Please let me know if I have misunderstood your problem.
Edit: This is a slight extension of the pattern in the Redux documentation, to store previous events.
At the end this is what I've implemented (for demonstration purpose only - unrelated code is omitted):
eventRoot.js:
import { combineReducers } from 'redux'
import ranges from './events'
import ids from './ids'
import params from './params'
import total from './total'
export default resource =>
combineReducers({
ids: ids(resource),
ranges: ranges(resource),
params: params(resource)
})
events.js:
import { GET_EVENTS_SUCCESS } from '#/state/types/data'
export default resource => (previousState = {}, { type, payload, requestPayload, meta }) => {
if (!meta || meta.resource !== resource) {
return previousState
}
switch (type) {
case GET_EVENTS_SUCCESS:
const newState = Object.assign({}, previousState)
payload.data[resource].forEach(record => {
// ISO 8601 time interval string -
// http://en.wikipedia.org/wiki/ISO_8601#Time_intervals
const range = record.start + '/' + record.end
if (newState[record.id]) {
if (!newState[record.id].includes(range)) {
// Don't mutate previous state, object assign is only a shallow copy
// Create new array with added id
newState[record.id] = [...newState[record.id], range]
}
} else {
newState[record.id] = [range]
}
})
return newState
default:
return previousState
}
}
There is also a data reducer but it's linked in parent reducer due to generic implementation that is re-used for common list responses. Events data are updated and start/end property is removed as it's composed by range (ISO 8601 time interval string). This can be later used by moment.range or split by '/' to get start/end data. I've opted for array of range strings to simplify checking of existing ranges, as they might grow large. I think that primitive string comparison (indexOf or es6 includes) would be faster than looping over complex structure in such cases.
data.js (stripped down version):
import { END } from '#/state/types/fetch'
import { GET_EVENTS } from '#/state/types/data'
const cacheDuration = 10 * 60 * 1000 // ten minutes
const addRecords = (newRecords = [], oldRecords, isEvent) => {
// prepare new records and timestamp them
const newRecordsById = newRecords.reduce((prev, record) => {
if (isEvent) {
const { start, end, ...rest } = record
prev[record.id] = rest
} else {
prev[record.id] = record
}
return prev
}, {})
const now = new Date()
const newRecordsFetchedAt = newRecords.reduce((prev, record) => {
prev[record.id] = now
return prev
}, {})
// remove outdated old records
const latestValidDate = new Date()
latestValidDate.setTime(latestValidDate.getTime() - cacheDuration)
const oldValidRecordIds = oldRecords.fetchedAt
? Object.keys(oldRecords.fetchedAt).filter(id => oldRecords.fetchedAt[id] > latestValidDate)
: []
const oldValidRecords = oldValidRecordIds.reduce((prev, id) => {
prev[id] = oldRecords[id]
return prev
}, {})
const oldValidRecordsFetchedAt = oldValidRecordIds.reduce((prev, id) => {
prev[id] = oldRecords.fetchedAt[id]
return prev
}, {})
// combine old records and new records
const records = {
...oldValidRecords,
...newRecordsById
}
Object.defineProperty(records, 'fetchedAt', {
value: {
...oldValidRecordsFetchedAt,
...newRecordsFetchedAt
}
}) // non enumerable by default
return records
}
const initialState = {}
Object.defineProperty(initialState, 'fetchedAt', { value: {} }) // non enumerable by default
export default resource => (previousState = initialState, { payload, meta }) => {
if (!meta || meta.resource !== resource) {
return previousState
}
if (!meta.fetchResponse || meta.fetchStatus !== END) {
return previousState
}
switch (meta.fetchResponse) {
case GET_EVENTS:
return addRecords(payload.data[resource], previousState, true)
default:
return previousState
}
}
This can be then used by an calendar component with event selector:
const convertDateTimeToDate = (datetime, timeZoneName) => {
const m = moment.tz(datetime, timeZoneName)
return new Date(m.year(), m.month(), m.date(), m.hour(), m.minute(), 0)
}
const compileEvents = (state, filter) => {
const eventsRanges = state.events.list.ranges
const events = []
state.events.list.ids.forEach(id => {
if (eventsRanges[id]) {
eventsRanges[id].forEach(range => {
const [start, end] = range.split('/').map(d => convertDateTimeToDate(d))
// You can add an conditional push, filtered by start/end limits
events.push(
Object.assign({}, state.events.data[id], {
start: start,
end: end
})
)
})
}
})
return events
}
And here is how the data structure looks in the redux dev tools:
Each time the events are fetched, their data is updated (if there is a change) and references are added. Here is an screenshot of redux diff after fetching new events range:
Hope this helps somebody, I'll just add that this still isn't battle tested but more a proof of a concept that's working.
[EDIT] Btw. I'll probably move some of this logic to the backend as then there will be no need to split / join / delete properties.

How to remove extra column value from jqgrid json data

Free Jqgrid has actions column. colmodel:
{"hidden":false,"label":"","name":"_actions","width":72
,"align":"left","template":"actions","fixed":false,"resizable":true,
"formatoptions":{"editbutton":true,"delbutton":true,"delOptions":{"url":"Delete" }}},
{"label":"Nimetus","name":"Nimi","index":"Nimi","editoptions":{"maxlength":80,"size":80 }
It is populated from remote json data like
{"total":1,
"page":1,
"rows":[{"id":"2ARVELDUSARV", "cell":[null,"2ARVELDUSARV"]},
{"id":"ACME","cell":[null,"ACME"]},
{"id":"KAKSKOERA","cell":[null,"KAKSKOERA"]}
]
}
In cell array first column is not used.
If this column is removed, jqgrid does not render data correctly since this column presence is required as placeholder for actions column.
How to fix this so that jqgrid will accept data without first column:
{"total":1,
"page":1,
"rows":[{"id":"2ARVELDUSARV", "cell":[null,"2ARVELDUSARV"]},
{"id":"ACME","cell":["ACME"]},
{"id":"KAKSKOERA","cell":["KAKSKOERA"]}
]
}
Update
I looked for data format change as recommended in answer.
jqgrid data is created from sql select statement in ASP.NET MVC4 using code below. Web API serializes this to format for json for jqgrid automatically.
How to create result which can serialized to propertyname: value format recommended in answer ?
object GetDataForJqGrid() {
IDbConnection conn;
using (var dataReader = DataAccessBase.ExecuteReader(sql.ToString(), out conn,
CommandBehavior.CloseConnection | CommandBehavior.SingleResult,
sql.GetParameters.ToArray()))
{
var rowList = new List<GridRow>();
var pkeys = DatabasePrimaryKey();
while (dataReader.Read())
{
var pkv = new List<object>();
int offset = 1; // required for actions column
var row = new GridRow
{
id = IdHelper.EncodeId(pkv),
cell = new object[dataReader.FieldCount + offset + imageCount]
};
for (int j = 0; j < dataReader.FieldCount; j++)
row.cell[offset + j] = dataReader.GetValue(j);
rowList.Add(row);
}
return new
{
total = rowList.Count() < rows ? page : page + 1, page,
rows = rowList
};
}
public class GridRow
{
public string id;
public object[] cell;
}
The most easy way would be to chanege the format of data returned from the server to use repeatitems: false style of the data. I mean the usage of
{
"total": 1,
"page": 1,
"rows": [
{ "id": "2ARVELDUSARV", "Nimi": "2ARVELDUSARV" },
{ "id": "ACME", "Nimi": "ACME" },
{ "id": "KAKSKOERA", "Nimi": "KAKSKOERA"}
]
}
or, after adding key: true to the definition of the column Nimi
{
"total": 1,
"page": 1,
"rows": [
{ "Nimi": "2ARVELDUSARV" },
{ "Nimi": "ACME" },
{ "Nimi": "KAKSKOERA"}
]
}
instead of
{
"total": 1,
"page": 1,
"rows": [{
"id": "2ARVELDUSARV",
"cell": ["2ARVELDUSARV"]
}, {
"id": "ACME",
"cell": ["ACME"]
}, {
"id": "KAKSKOERA",
"cell": ["KAKSKOERA"]
}]
}
Alternatively one can use jsonReader: { repeatitems: false } event with your current format of data and add jsonmap: "cell.0" property to, which means getting the first element (index 0) from the array cell:
$("#list").jqGrid({
datatype: "json",
url: "andrus.json",
colModel: [
{ label: "", name: "_actions", template: "actions" },
{ label: "Nimetus", name: "Nimi", jsonmap: "cell.0" }
],
iconSet: "fontAwesome",
jsonReader: { repeatitems: false }
});
see the demo.
I personally would recommend you don't use your original format (cell with array of values) and use just the named property with additional id property (if id value is not included in the item already). If you would do use the solution with jsonmap you should be carefully with changing the order of the columns (using remapColumns) and later reloading of data. You could required to update jsonmap values after the changing the column order. Thus I repeat that I recommend you to change format of data returned from the server.
UPDATED: The Updated part of your question formulate absolutely new question which have no relation with jqGrid. It's pure C# problem. Nevertheless I try to answer, because I use C# too.
What you can do with minimal changes of your code is the following: You should add using System.Dynamic; and using System.Linq; first of all. Then you should replace the code inside of using (...) {...} to about the following
var rowList = new List<dynamic>();
while (dataReader.Read()) {
var row = new ExpandoObject() as IDictionary<string, object>;
for (int j = 0; j < dataReader.FieldCount; j++) {
if (!dataReader.IsDBNull(j)) {
row.Add(dataReader.GetName(j), dataReader.GetValue(j));
}
}
rowList.Add(row);
}
Serializing of rowList will produce the names properties. If you know the primary key of the data, then you can add id property with the corresponding value in the same way (using row.Add("id", IdHelper.EncodeId(pkv))). I don't included the part because the code which you posted is not full and pkv is currently always new List<object>(), which is wrong. If the data have composed key (multiple value set is unique) then you can make string concatenation of the keys using '_' (underscore) as the separator.