TypeError: Cannot read property 'reduce' of undefined in react - json

I have a form in which I am asking the user to input field values for a couple of fields, storing the field values in an state and displaying the state values in a customised format.
So, I have a couple of input fields and a submit button:
<button onClick={this.handleSubmit}>Submit</button>
{
this.state.credentials &&
//<Credentials value={this.state}/>
<Credentials value={JSON.stringify(this.state, undefined, 2)} />
}
The Credentials function convert the state of the component in JSON format:
const Credentials = ({value} ) => {
return <pre>{formatState(value)}</pre>;
}
The formatState function will basically manipulate the state values and display them in the way I want:
function formatState(state) {
console.log("hi")
console.log(state);
const output = state.groups.reduce((final, s)=> {
console.log(output)
const values = Object.keys(s).reduce((out, o)=> {
out[o] = s[o].map(k => Object.values(k))
return out;
}, {})
final = {...final, ...values}
return final;
}, {})
console.log(output)
}
The state looks like this:
{
"groups": [
{
"typeA": [
{
"name": "abc"
},
{
"number": "13,14"
}
],
"typeB": [
{
"country": "xyz"
},
{
"date1": "2019-05-14"
}
]
}
]
}
But I want the output like this:
groups: {
"typeA": [[abc],[13,14]],
"typeB": [[2019-05-14],[xyz]]
}
SO, reduce function is used to convert the state into the following output. But I getting the error :
"TypeError: Cannot read property 'reduce' of undefined"
Please can anybody tell me why this is happening.

Error is here <Credentials value={JSON.stringify(this.state, undefined, 2)} />. JSON.stringify produces string representaion of some object (this.state in your case). Argument state of formatState has type of string. It seems that you want to have state arguemnt to be object. So you should do
<Credentials value={this.state} />

Related

How create dynamic form controls and fetch data from it in Angular

I'm very new to Angular and stuck on a problem.
When the component loads I have two input fields as terminal ID(textfield) and terminal type(dropdown). After selecting value from dropdown, onChange method makes an API call and gets data for me. The data is in this format:
data={
"productResults":[
{
"productId":"1",
"productName":"credit"
},
{
"productId":"2",
"productName":"debit"
}
],
"metaResultList":[
{
"customizationType":"time",
"customizationValue":"0420",
"longDescription":"Item1"
},
{
"customizationType":"code",
"customizationValue":"N",
"longDescription":"Auto Close",
"customizationCodeDetail":{
"customizationCodeDetails":[
{
"value":"N",
"shortDescription":"None"
},
{
"value":"H",
"shortDescription":"host Auto Close"
}
]
}
},
{
"customizationType":"bool",
"customizationValue":"Y",
"longDescription":"Block Account"
},
{
"customizationType":"bool",
"customizationValue":"N",
"longDescription":"Block Sales"
},
{
"customizationType":"number",
"customizationValue":"55421",
"longDescrption":"Max Value"
}
]
}
What i did is when i get data after selecting from dropdown, I have two sections here below my these two firlds which will shown with ngIf I get data:
1st: Product Section- Where i used ngFor to iterate over data.productResults like this and displayed the toggles
<div *ngFor="let product of data.productResultResults">
<label>{{product.productName}}</label>
<ion-toggle></ion-toggle>
</div>
2nd : Others Section: Here I iterate over metaResultList to display textfield if customizationType is time or number, dropdown if customizationType is code and toggle if customizationType is bool.
<div *ngFor="let other of data.metaResultList">
<div *ngIf="other.customizationType==='time'||other.customizationType==='number'">
<label>{{other.longDescription}}</label>
<input type="text" value="other.customizationValue"/>
</div>
<div *ngIf="other.customizationType==='bool'">
<label>{{other.longDescription}}</label>
<ion-toggle></ion-toggle>
</div>
<div *ngIf="other.customizationType==='code'">
<label>{{other.longDescription}}</label>
<dropdown [datalist]="autoCloseDropDown"></dropdown>
</div>
</div>
This dropDown is my custom dropdown and I'm passing value as H for host Auto Close and N for None and I'm getting options in dropdown(I did that logic after getting results).
After clicking on submit button below , I want my data to be in this format
{
"tid":"3",
"terminalType":"gateway",
"products":[
{
"productId":"1",
"productname":"credit"
}
],
"customizations":[
{
"customizationName":"Item1",
"customizationValue":"0420"
},
{
"customizationName":"Block Account",
"customizationValue":"Y"
},
{
"customizationName":"Block Sales",
"customizationValue":"N"
},
{
"customizationName":"Max Value",
"customizationValue":"54556"
}
]
}
Now my question is how to make formcontrols and as these fields are generating after i make decision in terminal type dropdown, but i already made the formgroup in ngOnInit.How should I make the form control names dynamic. How will I insert only those value in products array whose value is Y in form. IS there any way I can instantiate formgroup after making change in dropdown?Also there might be case that i won't get certain fields like type=bool sometime from api, so i won't show that in FrontEnd
Any help would be appreciated.
If you want to generate dynamic formControl by an object or an array, you can use this method below:
public builder(data: any): FormGroup | FormArray {
if (Array.isArray(data)) {
const formGroup = [];
for (const d of data) {
formGroup.push(this.builder(d));
}
return this.formBuilder.array(formGroup);
} else {
const formGroup = {};
Object.keys(data).forEach((key) => {
if (typeof data[key] === 'object' && data[key] && !Array.isArray(data[key])) {
Object.assign(formGroup, {
[key]: this.builder(data[key]),
});
} else if (Array.isArray(data[key])) {
Object.assign(formGroup, {
[key]: this.formBuilder.array([]),
});
data[key].forEach((newData: any) => {
formGroup[key].push(this.builder(newData));
});
} else {
Object.assign(formGroup, { [key]: [data[key]] });
}
});
return this.formBuilder.group(formGroup);
}
}
Now, if you want to generate an object or an array dinamic, you can call that's method with the parameter data as an object or an array. For an example:
ngOnInit(): void {
// will be generate a form array generatedForm in formGroup
const dynamicArray = [{ id: 1, name: 'John Doe'}, { id: 2, name: 'Jane Doe'}];
this.formGroup = this.formBuilder.group({
generatedForm: this.builder(dynamicArray)
});
// look at the form array
console.log(this.formGroup.get('generatedForm'));
// will be generated a formGroup by an object
const dynamicObject = { id: 1, name: 'John Doe'};
this.formGroup = <FormGroup>this.builder(dynamicObject)
// dynamic formGroup by an object
console.log(this.formGroup);
}
For an example: You can check at: https://stackblitz.com/edit/angular-ivy-7fm4d1
Ok, now you can try your own with that's builder.

How to search in json object in angular using a string?

Json
{
"rootData": {
"test1": {
"testData0": "Previous data",
"testData1": "Earlier Data"
},
"test2": {
"testData0": "Partial data",
"testData1": "Services data"
},
"test3": {
"testData0": "Regular data",
"testData1": {
"testData0": "Your package"
}
}
}
}
Component.ts
import * as configData from './myData.json';
getData(data: string){
console.log(configData.rootData.test1.testData0); //returns "Previous Data.
return configData.rootData.{{data}}.testData0;
}
This getData method is being called in a loop passing a string with values of "test1" the first time "test2" the second time and "test3" the third time called.
I want to do something like this
return configData.rootData.{{data}}.testData0; //should return Previous data, then "partial data" if called again because test2 will be passed in data string.
I know this is not possible the way I am doing it because {{data}} is not defined in my json object.
The goal is to check for the object inside the object. The string data is returning values existing in the json object. I want to use that data to dynamically search in the json file and pull the values.
I know my attempt is not valid. I would like to know if there is an alternative to make this work as I intended.
To get the value with the key in Object, you can use Object[key] (here, key is variable name) and this will return the value of the selected key.
return configData.rootData[data]?.testData0; // Javascript case
So instead of using {{ }}, replace it with square brackets and you will get the result.
And on the above code, rootData[data]?.testData0 has same meaning as rootData[data] ? rootData[data].testData0 : undefined so this will be needed for validation check. (unexpected data value input)
On Typescript,
if (data in configData.rootData && "testData0" in configData.rootData[data]) {
return configData.rootData[data].testData0;
} else {
return undefined;
}
const input = {
"rootData": {
"test1": {
"testData0": "Previous data",
"testData1": "Earlier Data"
},
"test2": {
"testData0": "Partial data",
"testData1": "Services data"
},
"test3": {
"testData0": "Regular data",
"testData1": {
"testData0": "Your package"
}
}
}
};
let data = 'test1';
console.log(input.rootData[data]?.testData0);
data = 'test2';
console.log(input.rootData[data]?.testData0);
data = 'test3';
console.log(input.rootData[data]?.testData0);
data = 'test4';
console.log(input.rootData[data]?.testData0);
data = 'test5';
if (data in input.rootData) {
console.log('Existed', input.rootData[data].testData0);
} else {
console.log('Not Existed');
}
I use ng2-search-filter.
By directive
<tr *ngFor="let data of configData | filter:searchText">
<td>{{testData0}}</td>
<td>{{testData1}}</td>
...
</tr>
Or programmatically
let configDataFiltered = new Ng2SearchPipe().transform(this.configData, searchText);
Practical example: https://angular-search-filter.stackblitz.io

How to iterate over JSON returned by HttpClient

I have a simple Angular HttpClient, which is correctly returning JSON. I am attempting to cast the results to enforce type safety (not sure if this is correct).
But how do I actually access the returned JSON to copy it into an array?
The httpClient get() request is (and seems to be working fine):
public sendGetRequest(): Observable<Symbols[]> {
return this.httpClient.get<Symbols[]>(this.REST_API_SERVER);
}
The Symbols interface is
export interface Symbols {
code: string
desc: string
}
I have a component which calls the data service and is getting a response. However the code below returns an error when attempting to map the JSON into a string array
ERROR TypeError: syms.map is not a function
listOfOption: Array<{ value: string; label: string }> = []
this.dataService.sendGetRequest().subscribe((syms: Symbols[]) => {
console.log('return value ' + JSON.stringify(syms))
// console output shows the returned JSON and it looks correct
//this does not work, how do I copy the results to a string array??
this.listOfOption = syms.map(results => {
return {
value: results.code,
label: results.code,
}
})
})
The JSON data structure is:
{
"results": [
{
"code": "code1",
"desc": "Long description of code 1"
},
{
"code": "code2",
"desc": "Long description of code 2"
},
{
"code": "code3",
"desc": "Long description of code 3"
},
{
"code": "code4",
"desc": "Long description of code 4"
}
]
}
This is driving me crazy
Model a new interface called responseData to support response type.
export interface responseData{
results: Symbols[]
}
export interface Symbols {
code: string
desc: string
}
Update the same in service
public sendGetRequest(): Observable<responseData> {
return this.httpClient.get<responseData>(this.REST_API_SERVER);
}
You can now retrieve the results using array.map()
listOfOption: Array<{ value: string; label: string }> = []
this.dataService.sendGetRequest().subscribe((syms: responseData) => {
console.log('return value ' + syms)
this.listOfOption = syms.results.map(result => {
return {
value: result.code,
label: result.code,
}
})
})
The response data has an object root, but you're trying to parse it as an array root. I think the simplest solution would be something like this:
public sendGetRequest(): Observable<Symbols[]> {
return this.httpClient.get<{results: Symbols[]}>(this.REST_API_SERVER)
.pipe(pluck('results'));
}
Which specifies that the response data is an object with a field named results which holds an array of Symbols.
Alternatively you could also extract the response type to a separate definition:
interface ApiResponse {
results: Symbols[]
}
public sendGetRequest(): Observable<Symbols[]> {
return this.httpClient.get<ApiResponse>(this.REST_API_SERVER)
.pipe(pluck('results'));
}

Remove a specific JSONObject from JSONArray in groovy

Say I have a JSON request payload like
{
"workflow": {
"approvalStore": {
"sessionInfo": {
"user": "baduser"
},
"guardType": "Transaction"
}
}
}
I get the value of user via
def user = req.get("workflow").get("approvalStore").get("sessionInfo").get("user")
Now, I get a RestResponse approvalList which I store as list and return to caller as return approvalList.json as JSON. All well so far.
Suppose the response (approvalList.json) looks like below JSONArray -
[
{
"objId": "abc2",
"maker": "baduser"
},
{
"objId": "abc1",
"maker": "baduser"
},
{
"objId": "abc4",
"maker": "gooduser"
}
]
Question : How may I filter the approvalList.json so that it doesn't contain entries (objects) that have "maker": "baduser" ? The value passed to maker should essentially be the user variable I got earlier.
Ideal required output -
It's not entirely clear if you always want a single object returned or a list of objects but using collect is going to be the key here:
// given this list
List approvalList = [
[objId: "abc2", maker: "baduser"],
[objId: "abc1", maker: "baduser"],
[objId: "abc4", maker: "gooduser"]
]
// you mentioned you wanted to match a specific user
String user = "baduser"
List filteredList = approvalList.findAll{ it.maker != user}​​​​​​
// wasn't sure if you wanted a single object or a list...
if (filteredList.size() == 1) {
return filteredList[0] as JSON
} else {
return filteredList as JSON
}​
Pretty simple. First parse the JSON into an object, then walk through and test.
JSONObject json = JSON.parse(text)
json.each(){ it ->
it.each(){ k,v ->
if(v=='baduser'){
// throw exception or something
}
}
}

Unable to parse JSON data due to errors or undefined data

I apologize if this seems similar to other questions asked but I have not been able to find any posts that have resolved this issue for me. Basically, I am getting a JSON object and I am trying to parse it but I can't parse it correctly. Mainly the WordDetails section that I am getting from a Word API. I am able to get everything outside the results section under WordDetails. Basically, when I get to results, I am not able to parse it correctly. Below is an example of the format.
{
"LastIndex": 133,
"SRDWords": [
{
"Domain": {
"URL": "abactinal.com",
"Available": true
},
"WordDetails": "{\"word\":\"abactinal\",\"results\":[{\"definition\":\"(of radiate animals) located on the surface or end opposite to that on which the mouth is situated\",\"partOfSpeech\":null,\"antonyms\":[\"actinal\"]}],\"syllables\":{\"count\":4,\"list\":[\"ab\",\"ac\",\"ti\",\"nal\"]}}"
},
{
"Domain": {
"URL": "aaronical.com",
"Available": true
},
"WordDetails": "{\"word\":\"aaronical\",\"syllables\":{\"count\":4,\"list\":[\"aa\",\"ron\",\"i\",\"cal\"]},\"pronunciation\":{\"all\":\"ɜ'rɑnɪkəl\"}}"
},
...
Here is my code below. Basically, I am getting to the results section of WordDetails but if I try to parse the results section it fails and if I try object.entries on it, it will not return a response according to the alert messages I used. I know there must be a better way but not sure what. Most articles say just JSON.parse then map it but that does not work. Any help would be appreciated!
data.Words.map(word => {
//get data
for (let [key, value] of Object.entries(word)) {
if (key === "Domain") {
url = value.URL;
availability = value.Available;
} else if (key.trim() === "WordDetails") {
alert("value " + value);
wDetails = JSON.parse(value);
for (let [key2, value2] of Object.entries(wDetails)) {
if (key2 === "word") {
//store word
} else if (key2.toString().trim() === "results") {
let test = JSON.parse(value2);
test = Object.entries(value2);
test.map(t => {
alert(t.definition);
});
}
}
}
}
});
You did JSON.parse above, no need to parse value2 again.
And value for results is an array, so no need for Object.entries.
...
} else if (key2.toString().trim() === 'results') {
let test = JSON.parse(value2); // this should be remove
test = Object.entries(value2); // this should be remove, value2 should be an array
// map value2 directly
value2.map(t => {
alert(t.definition);
});
}
...