I've been trying to change inline data (which worked) to external data in a JSON file.
Originally, I had this, which worked:
const treeData = [
{
name: "Parent"
attributes: {
id: 12345678
},
children: [
{
name: "Child"
attributes: {
id: 12345679
},
}
]
}
]
return(
<Tree data = {treeData}/>
)
So now I have an external JSON file that looks like this:
{
"name": "Parent",
"attributes": {
"id": 12345678,
},
"children": [
{
"name": "Child",
"attributes": {
"id": 12345679,
}
}
]
}
]
And in my program:
const[treeData, setTreeData] = useState(undefined)
const jsonSource = '../../data/tree-data.json'
/* Used to center the tree on render */
useEffect(()=> {
// Some irrelevant code here...
// Parse JSON data (Relevant)
return function parseJSONFile(){
treeUtil.parseJSON(jsonSource).
then((data) => setTreeData({data}))
.catch((error)=>console.log(error))
}
}, []);
return(
<Tree data = {treeData} />
)
Aand it does not work. Error message:
TypeError: Cannot set property 'id' of undefined
Which makes me confused because it's my first time dealing with JSON. Any insight on this can help!
You can do it in a very simple way (No need for extra works you are doing):
import treeData from "./tree-data.json"; // Import your json file (Path to your json file)
function App() {
return <Tree data={treeData} />;
}
Your code have many problems including you write your parseJSONFile in return of useEffect, Also you just defined parseJSONFile and you are not calling it, and even you call, it will be executed on component unmount (because you call it on return of useEffect), Also initial state of your treeData is undefined and it's the cause of TypeError: Cannot set property 'id' of undefined.
My codesandbox link (https://codesandbox.io/s/react-hooks-counter-demo-dh0q4?file=/src/index.js).
Related
My angular service returns the following type of response:
{
"name": "abc",
"id": 1,
"list": [
{
"name": "listName1",
"id": 1
},
{
"name": "listName2",
"id": 2
}
]
}
This is what the call in the service looks like:
fetchX(): Observable<X> {
return this.http.get<X>(some_url)
}
Now, in my component I can access the 'name' and 'id' attribute of the returned object, but for the 'list', which is a list itself, I only get 'undefined'. I'm displaying the list elements in my application (by their name), and they don't actually show up there either (the HTML part for this is definitely correct, this isn't the issue).
myX: CustomX
myList: CustomList[]
ngOnInit() {
this.service.fetchX().subscribe((response) => {
this.myX = response,
this.myList = response.list,
console.log(response.name), //returns 'abc'
console.log(response.id), //returns '1'
console.log(response.list) //returns 'undefined'})}
The entities look like this:
export class CustomX {
name: string
id: number
list: CustomList[]
}
class CustomList {
name: string
id: number
}
What am I missing? I think I may be handling the response in the service incorrectly.
I have this JSON data which has questions with options and questions nested in them. So the user picks an answer and the next question is chosen based on that answer and so on.... I have an array that is essentially a path (of ids) to the specific question object I'm looking for. How can I get to that object using my array of paths in my react component?
path = ["525289", "128886", "123456", "7547", "35735"]
{"question": {
"id":"525289",
"options": [
{
"id":"128886",
"optionName":{"alt":"123"},
"question": {
"id":"123456",
"title": "soidjfs",
"options": [
{
"id":"7547",
"optionName":{"alt":"new"},
"question": {
"id":"35735",
"title": "soidjfs",
"options": [
]
}
},
{
"id":"1234",
"optionName":{"alt":"new"},
"question": {
"id":"25825",
"title": "soidjfs",
"options": [
]
}
}
]
}
},
{
"id":"1234569999",
"optionName":{"alt":"123"},
"question": {
"id":"3457",
"title": "soidjfs",
"options": [
{
"id":"999998",
"optionName":{"alt":"new"},
"question": {
"id":"2134678",
"title": "soidjfs",
"options": [
]
}
},
{
"id":"55555",
"optionName":{"alt":"new"},
"question": {
"id":"123456159",
"title": "soidjfs",
"options": [
]
}
}
]
}
}
]
}
}
This could be done with a path similar (although not too similar) to what you provide, using the reduce function although there are some inconsistencies in your examples. For example, question seems to have a 1:1 relationship with option, so it doesn't seem like it needs filtering on traversal?
Alternatively as Mitya stated, you can use a JSON querying library. These are much more general purpose and so will be more verbose than a bespoke solution with contextual knowledge of your data structures.
I've used a much more simple, recursive parent-children model here:
let data = [
{
id: "123",
children: [
{
id: "456A",
title: "xyz",
children: [
{
id: "789AA",
message: "Foo"
},
{
id: "789AB",
message: "Bar"
}
]
},
{
id: "456B",
title: "abc",
children: [
{
id: "789BA",
message: "Hello"
},
{
id: "789BB",
message: "World!"
}
]
}
]
}
];
let path1 = ["123", "456A", "789AA"];
let path2 = ["123", "456C", "789CA"]; // Invalid as 456C isn't a valid item at this point in the tree
let path3 = ["123", "456B", "789BB"];
function select(path, data) {
return path.reduce((acc, v) => acc && acc.children.find(c => c.id === v), {children: data});
}
console.log(select(path1, data));
console.log(select(path2, data));
console.log(select(path3, data));
Output:
{ id: '789AA', message: 'Foo' }
undefined
{ id: '789BB', message: 'World!' }
If you're open to using a (small) library, you could use something like very own J-Path, which allows you to query and traverse an object (first argument) using XPath (second), which is normally used to query XML.
let item = jpath(object, 'question[id="525289"]/options/item[id="128886"]/question[id="123456"]/options/item[id="7547"]/question[id="35735"]');
Or, more simply, we could go straight to the target using the final ID:
let item = jpath(object, '//question[id="35735"]');
If you don't want a library, you'll need to step through the IDs manually and use a while loop until you find the data item you need.
I think your approach to this question is wrong. I think a better way would be to define each question separately as their own object and then have arrays of nested objects.
var questionId1 = {}
var questionId2 = {}
var questionId3 = {}
var questionId4 = {}
var questionId5 = {}
questionId1.options = [questionId2, questionId3]
questionId2.options = [questionId4]
questionId4.options = [questionId5]
Traversing things that way can make the idea of the path obsolete. You'd just need the name of the current question to display the options. When the next question is selected, the parent is no longer relevant, only its children matter.
Still, if you're stuck using this method for one reason or another, you can use the . notation to access nested elements inside of objects. In this case, it sounds like you're looking for each element's id and wanting the element's options object. Assuming you want the option that traverses the user's path and returns the options at the end of the path, you're going to need a lot of nested ifs to make sure that certain properties are there and that they're not empty and loops to go deeper into arrays.
Here's a fiddle that shows that method.
I am trying to create a local lang file that will be formatted as json. I have the following navigation in json format below. And I need to create a new JSON file using GULP to create a lang file (see below)
"lists": [
{
"title": "Application Intel",
"items": [
{
"title": "Analytics Dashboard",
"href": "intel_analytics_dashboard.html"
},
{
"title": "Marketing Dashboard",
"href": "intel_marketing_dashboard.html"
},
{
"title": "CEO Dashboard",
"href": "intel_ceo_dashboard.html"
},
{
"title": "Introduction",
"href": "intel_introduction.html"
},
{
"title": "Build Notes",
"href": "intel_build_notes.html",
"text": "Build Notes",
"span": {
"class": "",
"text": "v{{version}}"
}
}
]
}
I need to create a file that looks like the following json:
"nav": {
"application_intel": "Application Intel",
"intel_analytics_dashboard": "Analytics Dashboard",
"intel_marketing_dashboard": "Marketing Dashboard",
"intel_ceo_dashboard": "CEO Dashboard",
"intel_introduction": "Introduction",
"intel_build_notes": "Build Notes",
}
Whats the best way to go about this?
Here is solution.
Let's say you have nav.json file inside src and you want to change its shape and place it into dest directory. You can achieve this from within gulpfile.js
const { src, dest } = require("gulp");
const through = require("through2");
// gulp task
function json() {
return src("src/nav.json")
.pipe(
through.obj((file, enc, cb) => {
// get content of json file
const rawJSON = file.contents.toString();
// parse raw json into javscript object
const parsed = JSON.parse(rawJSON);
// transform json into desired shape
const transformed = transformJson(parsed);
// make string from javascript obj
const stringified = JSON.stringify(transformed, null, 2);
// make bufer from string and attach it as current file content
file.contents = Buffer.from(stringified);
// pass transformed file into next gulp pipe
cb(null, file);
})
)
.pipe(dest("dest"));
}
// transformation
function transformJson(input) {
const result = { nav: {} };
// read json field by field
Object.keys(input).forEach(topLevelKey => {
// current object
const topLevelItem = input[topLevelKey];
// in your design topLevelItems are arrays
topLevelItem.forEach(menuItem => {
if (menuItem.title) {
// make url either from item href or title
const itemUrl = makeUrl(menuItem.href || menuItem.title);
result.nav[itemUrl] = menuItem.title;
}
// prcoess children
if (menuItem.items) {
menuItem.items
.filter(child => !!child.title) // process only child items with title
.forEach(child => {
const childUrl = makeUrl(child.href || child.title);
result.nav[childUrl] = child.title;
});
}
});
});
return result;
}
// helper func
function makeUrl(href) {
return href
.toLowerCase()
.replace(/\.html$/, "")
.replace(/\s/g, "_");
}
// export for use in command line
exports.json = json;
json transformation function is bit forEachy and if you have deep nested navigation structure, maybe you should change it into something recursive
Im building a React app and I have a quite complex JSON file where I need to find and output certain values of an object in an array.
Im trying to output all my people from my JSON, they look something like this:
people: [
{
"id": 1,
"email": "Sincere#april.biz",
"address": [
{
"street": "Kulas Light",
"type": "house",
"attribute": {
"sketch": "sketch.jpg",
"photo": "photo.jpg"
}
},
{
"street": "Lorem Ipsum",
"type": "apartment",
"attribute": {
"sketch": "sketch.jpg",
"photo": "photo.jpg"
}
}
]
}
]
I have no problem to output the email, doing it like so:
var App = React.createClass({
getInitialState: function () {
return {
results: {}
}
},
componentDidMount() {
fetch(REQUEST_URL) // fetch from API, returns JSON
.then(res => res.json())
.then(data => {this.setState(
{ results: data.people}
);
})
},
renderResult : function(key){
return <Result key={key} index={key} details={this.state.results[key]}/>
},
render : function() {
return (
<ul>
{Object.keys(this.state.results).map(this.renderResult)}
</ul>
)
}
});
var Result = React.createClass({
render : function() {
return (
<li>
{this.props.details.email}
<img src="{this.props.details.address.type=house.attribute.photo}"/>
</li>
)
}
});
ReactDOM.render(App, document.querySelector('#app'));
However, now I need to output "photo" but only for "type": "house". I tried this but no luck, well aware that this is way off. Im quite new to handling JSON data and React and Google hasn't helped me even after a few hours of trying to solve this.
The .address property isn't an object but an array of objects so
.type is not available directly on .address:
this.state.results.people.address.type
// .type property doesn't exist on array
Solution:
You can use Array.prototype.filter on .address to obtain an array of objects that have a property type whose value is "house":
var houseAddresses = this.state.results.people.address.filter(function(value){
return value.type === "house";
});
Here, houseAddress will be an array of objects whose type value is 'house".
You can then loop through the array to create the relevant JSX using for, Array#forEach or Array#map. The following example uses Array#map:
const houseImgTags = houseAddresses.map(function(house, index){
return (
<img
src={house.attribute.photo}
key={'house'+index}
/>
);
});
(A key was added here in case there are more than one instance of a house object)
You can simply write.
<img src={this.states.results.address.type==="house"?house.attribute.photo : otherwise_photo}/>
Basically this would compare address.type is house or not,then return the result corresponded.
I am looking for best solution how to work with JSON in my angular2 app.
My JSON is:
{
"rightUpperLogoId": {
"id": 100000,
"value": ""
},
"navbarBackgroundColorIdCss": {
"id": 100001,
"value": ""
},
"backgroundColorIdCss": {
"id": 100002,
"value": ""
},
"translationIdFrom": {
"value": "90000"
},
"translationIdTo": {
"value": "90055"
}
}
This JSON is something like configuration file for UI of app. In my application, I want to get id from rightUpperLogoId, it is 100000. With this id I need to do GET task on my backend REST api and the returned value I would like to set to value. Thank you
You could leverage Rx operators like the flatMap one with Observable.forkJoin / Observable.of.
Here is a sample:
this.http.get('config.json')
.map(res => res.json())
.flatMap(config => {
return Observable.forkJoin(
Observable.of(config),
// For example for the request. You can build the
// request like you want
this.http.get(
`http://.../${config.rightUpperLogoId.id}`)
);
})
.map(res => {
let config = res[0];
let rightUpperLogoIdValue = res[1].json();
config.rightUpperLogoId.value = rightUpperLogoIdValue;
return config;
})
.subcribe(config => {
// handle the config object
});
This article could give you more hints (section "Aggregating data"):
http://restlet.com/blog/2016/04/12/interacting-efficiently-with-a-restful-service-with-angular2-and-rxjs-part-2/