Home > Net >  FIltering Nested JSON in Javascript
FIltering Nested JSON in Javascript

Time:03-08

I have some nested Json in a file called data.json. I am using fetch to read the file in and then would like to do some filtering based on if a user has a specific option selected in a dropdown on the website.

var jsonData = [{"type": "FeatureCollection",
       "features": [
          {"type": 'Feature', "properties": {"id": 1}, "geometry": {"type": "Point", "coordinates": [-80.71, 28.34]}},
          {"type": 'Feature', "properties": {"id": 2}, "geometry": {"type": "Point", "coordinates": [-79.89, 28.45]}},
          {"type": 'Feature', "properties": {"id": 2}, "geometry": {"type": "Point", "coordinates": [-60.79, 28.32]}}
       ]}
]

I would like to perform some filtering on this array of features based on the "properties": {"id": #} field. E.g. if a user selects a value that matches that id, keep that in my result display data, else remove it.

I am trying to do something via a Promise like below. I'm new to JavaScript but the usage of .filter in the below code is my attempt to get this to work.

Ideal solution I want to achieve:

I am displaying a map of data on the U.S. Map with points relative to some locations that belong to the id field I mentioned. I would like the user to be able to click one of the IDs via a drop-down facility in Javascript, then via their selection, filter the JSON data to only features that belong to that Id. E.g. a traditional filter you'd use on data.

function filterIds(data) {
    let idFilter= document.getElementById("id-filter");
    let selection = idFilter.addEventListener('change', function(event) {
        return this.value;
    });

    data.features.map((element) => {
        // spread out our array of data and filter on specific property (namely, the id key)
        return {...element, properties: element.filter((property) => property.id=== selection)};
    });

async function getData(url) {
    let response = await fetch(url);
    let data = await response.json();
    // console.log(data);
    return data;
};
getData("../data/processed/data.json") // fetch raw data
    .then(data => filterIds(data));

CodePudding user response:

Here's a technique to try, it changes the flow slightly to:

  • user loads page
  • page assigns an event listener to the dropdown
  • page loads data.json into a global variable

Only when the user changes the dropdown does it check to make sure data.json got loaded and then perform the needed filtering.

// global var to store "data" from getData
let processedData;

// assign the listener once, on page load
document.getElementById("id-filter").addEventListener('change', function(event) {
  if (!processedData) return; // still no processedData available
  
  let selection = this.value;
  
  let newData = processedData.features.map((element) => {
    // spread out our array of data and filter on specific property (namely, the id key)
    return {...element, properties: element.filter((property) => property.id === selection)};
  });

  // do something with newData
});


const setData = (data) => {
  // this just stores data in the global processedData variable
  processedData = data;
};

async function getData(url) {
    let response = await fetch(url);
    let data = await response.json();
    return data;
};

// call getData once, on page load
getData("../data/processed/data.json") // fetch raw data
    .then(data => setData(data)); 

CodePudding user response:

You need to make some changes in your filter function -

function filterIds(data) {
  let idFilter= document.getElementById("id-filter");
  let value = null;
  let selection = idFilter.addEventListener('change', function(event) {
    value = this.value;
});
data.features.map((element) => {
      const properties = element.filter((property) => 
      property.id=== value);
      return {...element, properties};
});
}

To fix Uncaught (in promise) TypeError: element.filter is not a function you need to make following changes to your getData function -

async function getData(url) {return await fetch(url).then((res) => res.json())};

Also, you must set event listener outside your filter function, at a global level.

CodePudding user response:

Maybe it's not necessary to filter, since you are indexing prior to it. Depending of what you are doing, it can be way faster.

Base example for a table, or a chart.

var jsonData = [{"type": "FeatureCollection",
       "features": [
          {"type": 'Feature', "properties": {"id": 1}, "geometry": {"type": "Point", "coordinates": [-80.71, 28.34]}},
          {"type": 'Feature', "properties": {"id": 2}, "geometry": {"type": "Point", "coordinates": [-79.89, 28.45]}},
          {"type": 'Feature', "properties": {"id": 2}, "geometry": {"type": "Point", "coordinates": [-60.79, 28.32]}}
       ]}
]

// Create a select element
document.body.append(Object.assign(document.createElement("select"), {id: "select" }))

// Count features and create options
for (let i = 0; i < jsonData[0].features.length; i  ){
  document.getElementById("select").append(
    Object.assign(document.createElement("option"), {textContent: i })
  )
}

// Read by index at selection change
select.addEventListener("change", (e) => {
  let selected =  e.target.selectedOptions[0].textContent // Cast to integer
  console.log(jsonData[0].features[selected])
})

CodePudding user response:

// ... mocked API call ...
async function fetchData() {
  return new Promise(resolve =>
    setTimeout(() => resolve({
      data: [{
        "type": "FeatureCollection",
        "features": [{
          "type": 'Feature',
          "properties": {
            "id": 1
          },
          "geometry": {
            "type": "Point",
            "coordinates": [-80.71, 28.34]
          }
        }, {
          "type": 'Feature',
          "properties": {
            "id": 2
          },
          "geometry": {
            "type": "Point",
            "coordinates": [-79.89, 28.45]
          }
        }, {
          "type": 'Feature',
          "properties": {
            "id": 2
          },
          "geometry": {
            "type": "Point",
            "coordinates": [-60.79, 28.32]
          }
        }]
      }]
    }), 2000)
  );
}

// one time data transformation in order to avoid filtering later.
function getFeaturesMapGroupedByPropertiesId(featureList) {
  return featureList.reduce((map, featureItem) => {

    const { properties: { id } } = featureItem;

    (map[id] ??= []).push(featureItem);

    return map;

  }, {});
}

function renderDropdownOptions(node, idList) {
  const { options } = node;

  // delete/reset the `options` collection.
  options.length = 0;
  // put/add initial default selected option item.
  options.add(new Option('select an id', '', true, true));

  idList.forEach(id =>
    options.add(new Option(`feature ${ id }`, id))
  );
}

function handleFeatureChangeWithBoundFeaturesMap({ currentTarget }) {
  const idBasedFeaturesMap = this;
  const featureId = currentTarget.value;

  console.log(
    `id specific feature list for id "${ featureId }" ...`,
    idBasedFeaturesMap[featureId],
  );
}

async function main() {
  console.log('... trigger fetching data ...');

  const { data } = await fetchData();
  console.log('... fetching data ... done ...', { data });

  const idBasedFeaturesMap =
    getFeaturesMapGroupedByPropertiesId(data[0].features);

  // console.log({ idBasedFeaturesMap });
  // //console.log(Object.keys(idBasedFeaturesMap));

  const dropdownNode = document.querySelector('select#feature');
  if (dropdownNode) {
  
    console.log('... synchronize dropdown data ...');

    renderDropdownOptions(
      dropdownNode,
      Object.keys(idBasedFeaturesMap),
    );
    dropdownNode
      .addEventListener(
        'change',
        handleFeatureChangeWithBoundFeaturesMap.bind(idBasedFeaturesMap)
      );  
    console.log('... synchronize dropdown data ... done!');
  }
}
main();
.as-console-wrapper {
  min-height: 100%;
  width: 80%;
  left: auto!important;
}
body { margin: 0; }
<select id="feature">
  <option value="">... initializing ...</option>
  <!--
  <option value="1">feature 1</option>
  <option value="2">feature 2</option>
  //-->
</select>

  • Related