Been delivered some confusing JSON data with a problem I haven't seen before.
The JSON is formatted similar to this structure:
[
{
"title": "Event",
"start_date": "2022-08-20 15:00:00",
"end_date": "2022-08-20 16:00:00",
"branch": {
"85": "branchname"
},
"room": {
"156": "roomname"
},
"age_group": {
"5": "Youth",
"6": "Teen"
}
},
{
"title": "Event02",
"start_date": "2022-08-20 15:00:00",
"end_date": "2022-08-20 16:00:00",
"branch": {
"72": "branchname"
},
"room": {
"104": "roomname02"
},
"age_group": {
"5": "Youth",
"6": "Teen"
}
}
]
I'm trying to pull roomname out of the data, but it's nested in an object that has a random index number. If I manually put in the index number, I can retrieve the data, but the number changes every entry.
If I can figure out how to retrieve the number and store it in a variable, then use it again, or just somehow wildcard to just show any child of any key under the parent node "room" it would work perfect, but I don't know of a way to do this in javascript.
I'm limited to vanilla javascript, no external libraries or jquery.
here is the code that will output correctly if I manually enter the index numbers, but it only works for a single entry.
<script>
const url = 'example.json';
fetch(url)
.then((response) => {
return response.json();
})
.then((json) => {
json.map(function(event) {
console.log(`${event.start_date}`);
console.log(`${event.title}`);
console.log(`${event.room[156]}`);
return element;
});
}, 80);
</script>
EDIT: Forgot to point out, there is always only 1 entry in the "room" tag, but it's index is randomized, so if you just select the room tag it returns undefined or invalid. If I could wildcard the index so it just tries them all, or somehow retrieve the index number and store it in a variable, it would fix the issue.
I think this will work:
Here as you don't know the key so, instead of just guessing, you can use Object.values(JSONObjName) to get the list/array of values in that json.
Here I'm also using optional chaining (?.) to handle the case when the json has no key value pairs.
<script>
const url = 'example.json';
fetch(url)
.then((response) => {
return response.json();
})
.then((json) => {
json.map(function(event) {
const roomName = Object.values(event.room)?.[0];
console.log(`${event.start_date}`);
console.log(`${event.title}`);
console.log(`${roomName}`);
return {...event, room: roomName};
});
}, 80);
</script>
As long as you always want the first key you can fetch it like this
room = event.room[Object.keys(event.room)[0]]
if you want to get just roomname, you could do Object.values(room)[0]
or if you want the index and value you could go for Object.entries(room)[0]
arr?.map(({ room }) => {
for(let [key, value] of Object.entries(room)) {
console.log('Random Key : ',key)
console.log('Roomname : ', value)
console.log('Using random key : ',room[key])
}
})
By this way you can find the value of room against the random key.
Or you can try this if it is more relevant to you.
arr.map(({ room }) => {
for(let key of Object.keys(room)) {
console.log('Random Key : ',key)
console.log('Using random key : ',room[key])
}
})
Since you may want to do this for branch as well, here's an alternative solution which uses the object key as a computed property name (aka "dynamic key") to get the value.
And since, in this example it's done more than once, I've added that to a function that you can call in the destructuring assignment.
const data=[{title:"Event",start_date:"2022-08-20 15:00:00",end_date:"2022-08-20 16:00:00",branch:{85:"branchname"},room:{156:"roomname"},age_group:{5:"Youth",6:"Teen"}},{title:"Event02",start_date:"2022-08-20 15:00:00",end_date:"2022-08-20 16:00:00",branch:{72:"branchname02"},room:{104:"roomname02"},age_group:{5:"Youth",6:"Teen"}}];
// Get first key from an object
function getKey(obj) {
return Object.keys(obj)[0];
}
const out = data.map(obj => {
// Destructure the object and call `getKey` with the
// object to get its only key, and use that
// as a computed property to get its value, which
// we then relabel e.g. `roomName`
const {
branch: { [getKey(obj.branch)]: branchName },
room: { [getKey(obj.room)]: roomName },
...rest
} = obj;
// Now just return a new object with your new keys/values
return { ...rest, branchName, roomName };
});
console.log(out);
Additional documentation
Rest parameters
Spread syntax
Related
I made a request to an endpoint and I get this object, I'm filtering the name like this:
fetch('http://endpoint', requestOptions)
.then((response) => response.json())
.then((result) => {
const onlineUsers = result.resource.items[1].onlineUsers >= 1;
console.log(onlineUsers);
})
.catch((error) => console.log('error', error));
This workers, but I just need the result of what is in the key named Forms, but there is a possibility that it will change its position, so the items[1] it may not work anymore
This is an example of the object I receive:
{
"type": "application/vn+json",
"resource": {
"total": 4,
"itemType": "application",
"items": [
{
"name": "Test",
"onlineUsers": 1
},
{
"name": "Forms",
"onlineUsers": 1
},
{
"name": "Users",
"onlineUsers": 7
},
{
"name": "OnlineUsers",
"onlineUsers": 5
}
]
},
"method": "get",
"status": "success"
}
Is there any way to receive this object and filter by name? Like:
if (hasName === "Forms", get onlineUsers) {
// Do something
}
Thanks!
As suggested in your question, you can use filter on your array. Something like that:
console.log(
result.resource.items.filter((item) => item.name === "Forms")
);
This will print an array with all items having the name Forms. Using your example:
[
{
"name": "Forms",
"onlineUsers": 1
}
]
If there is only one item with the name Forms, or if you want only the first one, find may be a good alternative with a similar syntax:
console.log(
result.resource.items.find((item) => item.name === "Forms")
);
This will only print the first found object (or null if none is matching), without the array "pollution":
{
"name": "Forms",
"onlineUsers": 1
}
result.resource.items.filter((item) => item.name === "Forms")
Will filter your objects for you
Note, you will get back an array of all the filtered objects meeting the condition.
If you know there's only one object that matches that name you can use find which will return the first match so you don't necessarily need to iterate over the entire array.
Here's a little general function that might help. Pass in the data, the value of name, and the property you want the value of. If there is no match it returns 'No data'.
const data={type:"application/vn+json",resource:{total:4,itemType:"application",items:[{name:"Test",onlineUsers:1},{name:"Forms",onlineUsers:1},{name:"Users",onlineUsers:7},{name:"OnlineUsers",onlineUsers:5}]},method:"get",status:"success"};
function finder(data, name, prop) {
return data.resource.items.find(item => {
return item.name === name;
})?.[prop] || 'No data';
}
console.log(finder(data, 'Forms', 'onlineUsers'));
console.log(finder(data, 'morf', 'onlineUsers'));
console.log(finder(data, 'Users', 'onlineUsers'));
console.log(finder(data, 'Users', 'onlineUser'));
This is the Output: The data structure for the output is Map<String, List<Pair<String, String>>>
"testdata": [
{
"1.0": "True"
},
{
"1.1": "False"
}
]
Now I need to display this data on the UI as "testdata":["1.0","1.1","1.2"],
wherein here from the Pair, I want to fetch only the first elements from the Pair and put them in the structure Map<String, List<String>>
So how do I write that code in javascript in order to get that output?
this.previousData = versions.filter(v => v !== this.version).map(item => ({ text: item, value: item }))
How do I modify this code to get this output "testdata":["1.0","1.1","1.2"]?
You could try something like this:
// This assumes that there will be only one key in each array item, or the first key in each array item denotes the version
this.previousData =
versions
.filter(v => v !== this.version)
.map(item => Object.keys(item)[0])
// or as MikeT suggested, if you are only expecting something like this:
// [
// { "v1": "some value 1" },
// { "v2": "some value 2" },
// ... in general --> { "version": "some value" }
// ]
// you may try this as well
this.previousData =
versions
.filter(v => v !== this.version)
.map(item => Object.keys(item))
.flat()
your question isn't too clear about which element you are struggling with
so to get the data
async getData()
{
const resp = await fetch("<<your url from your java service>>" )
return await resp.json();
}
to format the data as you wish it to be formatted
formatData(json){
return json.testdata.map((i)=>Object.entries(i).map(([k,v])=>k)).flat()
}
and to display in vue
<template>testdata:{{testdata}}</template>
<script>
...
methods:{
async populatedata(){
const tmp = await getData()
this.testdata = formatData(tmp)
}
}
...
</script>
Object.entries will convert an object into a tuple array so {"1.0":"True","1.1":"False"} will become [["1.0","True"],["1.1":"False"]] which then lets you use tuple decomposition to get the keys
you could also use Object.keys() if you have no need for the values but that wasn't clear from the context so i gave you the more flexible option
I am still fairly new to JavaScript and I need help with an issue I am having.
I have a Java function that returns an array of json objects as such:
{
"payload": {
"phone": "1234567890",
"email": "test#test.com",
"contact": {
"personal": "test test",
"professional": "professionaltest test"
}
}
}
Now I have various scenarios in my API test where I am sending a POST request but purposefully missing some of the key/value pairs in the json. Such as:
Send the request by skipping contact.
Send the request by skipping phone.
etc.
I have researched and it seems that using .splice can be used to remove an object. However, from what I understand, the removal criteria is primarily driven by indexes (indices?). I'd rather not use remove by index in case something changes the output of the function in the future. Is there a way to remove by key name? Any alternative methods would be greatly appreciated too.
This is my favorite way to remove an value from an object.
For example, you want to remove phone from the payload object.
var { payload } = // your object
var { phone, ...newValue } = payload
console.log(newValue)
newValue is an copy of payload object without the phone key value.
function removeByKeys(obj, keys) {
return Object.keys(obj).reduce((newObj, key) => {
if (keys.includes(key)) {
return newObj;
}
return { ...newObj, [key]: obj[key] };
}, {});
}
const person = { name: 'bob', age: 36 };
removeByKeys(person, ['age']); // { name: 'bob' }
The has the same API as lodash omit. https://lodash.com/docs/4.17.15#omit. You can use that if you have more complicated use-cases, but the above is simple enough.
The following code demonstrates an issue that has surfaced while writing an app using the Vue framework for the front end. The issue is really a JS one though.
Here is the data object needed by the Vue component:
let data = { accountId: '', prospectId: '', address: '', city: '', state: '' }
and here is an object containing a row of data from the database:
const retrieved = {
"ProspectID": "4",
"AccountID": "1003",
"Address": "E2828 Highway 14",
"City": "Madison",
"State": "WI",
"Created": "2021-02-27 11:49:33.523",
"Updated": "2021-02-27 11:49:33.523"
}
It is necessary to copy some of the values from retrieved into data. The current way of doing the copy is the following:
data.accountId = retrieved.AccountID;
data.prospectId = retrieved.ProspectID;
data.address = retrieved.Address;
data.city = retrieved.City;
data.state = retrieved.State;
console.log('data', data);
The result of the above code is the desired outcome and it looks like this:
I'm looking for a more efficient way to do the copying because it's tedious when there are many key/value pairs involved.
I've tried this:
data = { ...data, ...retrieved };
console.log('data', data);
which results in this
which basically unions all the key/value pairs together. Not the desired outcome.
It is critical that the key names in data keep their exact names and no extra key/value pairs get added to data. How can this be achieved?
Since the capitalization is different, spread won't work. You'll have to iterate over an array mapping the properties on the different objects to each other:
const propsToCopy = {
// data // retrieved
accountId: 'AccountID',
prospectId: 'ProspectID',
// ...
};
for (const [dataProp, retrievedProp] of Object.entries(propsToCopy)) {
data[dataProp] = retrieved[retrievedProp];
}
That said, having slightly different property names for the same data like this seems very strange, since it makes the code a lot more convoluted than it needs to be and greatly increases the risk of typo-based problems, when a property is capitalized but doesn't need to be, or vice-versa. Consider if you can use just a single property name format instead, if at all possible; then the propsToCopy could be reduced to an array:
const propsToCopy = ['accountId', 'prospectId', /* ... */ ];
You can use a Proxy in order to intercept all settings of values. This allows only setting known values and ignoring anything else.
To make the setting of the property preserve the case, we can just lookup the original key case-insensitively and use the original one.
Finally, using Object.assign() will call all the setters on the target which means that the proxy can intercept these calls:
const eqCaseInsensitive = a => b =>
a.toLowerCase() === b.toLowerCase();
const handler = {
set(target, prop, value, receiver) {
const key = Object.keys(target)
.find(eqCaseInsensitive(prop));
if (key !== undefined) {
return Reflect.set(target, key, value, receiver);
}
return true;
}
}
let data = { accountId: '', prospectId: '', address: '', city: '', state: '' }
const retrieved = {
"ProspectID": "4",
"AccountID": "1003",
"Address": "E2828 Highway 14",
"City": "Madison",
"State": "WI",
"Created": "2021-02-27 11:49:33.523",
"Updated": "2021-02-27 11:49:33.523"
}
Object.assign(new Proxy(data, handler), retrieved);
console.log(data);
This can further be converted to a helper function that is analogous to Object.assign() and allow for as many sources as you wish. To save some processing time, there is no need to do a full search for each property assignment - a simple lookup map can be precomputed that holds lowercase property names as keys and normal case property names as values:
const assignOnlyKnownProps = (target, ...sources) => {
const known = new Map(
Object.keys(target)
.map(key => [key.toLowerCase(), key])
);
const handler = {
set(target, prop, value, receiver) {
const lookup = prop.toLowerCase();
if (known.has(lookup)) {
Reflect.set(target, known.get(lookup), value, receiver);
}
return true;
}
}
return Object.assign(new Proxy(target, handler), ...sources);
}
let data = { accountId: '', prospectId: '', address: '', city: '', state: '' }
const retrieved = {
"ProspectID": "4",
"AccountID": "1003",
"Address": "E2828 Highway 14",
"City": "Madison",
"State": "WI",
"Created": "2021-02-27 11:49:33.523",
"Updated": "2021-02-27 11:49:33.523"
}
assignOnlyKnownProps(data, retrieved);
console.log(data);
Creating a copy of retrieved and converting all the key names to lowercase, then populating the values for data works -
const r = Object.fromEntries(
Object.entries(retrieved).map(([k, v]) => [k.toLowerCase(), v])
);
// Object.keys(data).forEach(key => {
// data[key] = r[key.toLowerCase()];
// });
Object.keys(data).map(k => data[k] = r[k.toLowerCase()]);
console.log('r', r);
console.log('data', data);
So I am pretty new when it comes to Javascript and it is as simple as read a json list with a value of:
{
"URL": [{
"https://testing.com/en/p/-12332423/": "999"
}, {
"https://testing.com/en/p/-123456/": "123"
},
{
"https://testing.com/en/p/-456436346/": "422"
}
]
}
What I would like to do is to have both the URL and the amount of numbers etc
"https://testing.com/en/p/-12332423/" and "999"
and I would like to for loop so it runs each "site" one by one so the first loop should be
"https://testing.com/en/p/-12332423/" and "999"
second loop should be:
"https://testing.com/en/p/-123456/" and "123"
and so on depending on whats inside the json basically.
So my question is how am I able to loop it so I can use those values for each loop?
As Adam Orlov pointed out in the coment, Object.entries() can be very useful here.
const URLobj = {
"URL": [{
"https://testing.com/en/p/-12332423/": "999"
}, {
"https://testing.com/en/p/-123456/": "123"
},
{
"https://testing.com/en/p/-456436346/": "422"
}
]
};
URLobj.URL.forEach(ob => {
console.log('ob', ob);
const entries = Object.entries(ob)[0]; // 0 just means the first key-value pair, but because each object has only one we can just use the first one
const url = entries[0];
const number = entries[1];
console.log('url', url);
console.log('number', number);
})
You mean something like this using Object.entries
const data = {
"URL": [
{"https://testing.com/en/p/-12332423/": "999"},
{"https://testing.com/en/p/-123456/": "123"},
{"https://testing.com/en/p/-456436346/": "422"}
]
}
data.URL.forEach(obj => { // loop
const [url, num] = Object.entries(obj)[0]; // grab the key and value from each entry - note the [0]
console.log("Url",url,"Number", num); // do something with them
})
let's call your object o1 for simplicity. So you can really go to town with this link - https://zellwk.com/blog/looping-through-js-objects/
or you can just use this code :
for(var i = 0; i < o1.URL.length; i++) {
//each entry
var site = Object.keys(URL[i]) [0];
var value = Object.values(URL[i]) [0];
// ... do whatever
}
don't forget each member of the array is an object (key : value) in its own right
You can extract the keys and their values into another object array using map
Then use the for loop on the newly created array. You can use this method on any object to separate their keys and values into another object array.
const data = {
"URL": [{
"https://testing.com/en/p/-12332423/": "999"
}, {
"https://testing.com/en/p/-123456/": "123"
},
{
"https://testing.com/en/p/-456436346/": "422"
}
]
}
var extracted = data.URL.map(e => ({
url: Object.keys(e)[0],
number: Object.values(e)[0]
}))
extracted.forEach((e) => console.log(e))