Get Key Value Pairs from JSON Object with JavaScript and TypeScript

Easily get, parse, and process JSON data. Learn how to write convenient and intuitive utility functions.

Get Key Value Pairs from JSON Object with JavaScript and TypeScript
Photo by Pankaj Patel / Unsplash

Quickly parse a JSON (javascript object notation) string into a JavaScript object and get a required key-value pair from JSON data.

JSON is a lightweight data-interchange format widely used by many web applications and services on the internet. Although the origin of the term implies that it is strongly related to JavaScript, it is a programming language independent data exchange format.

According to your needs, there are different ways to parse JSON:

Parse JSON Object in One Line

const itemJSON = '{ "id": 23, "name": "pencil", "color": "red" }';
const name = JSON.parse(itemJSON).name;

Using the above example, I converted JSON format to JS object. You can now access json value of any key value pair using dot notation for key names: itemJSON.name.

Parse JSON Array in One Line

JSON data may contain different data types. The following example shows how to parse an array of objects.

const itemsJSON = '[
  { "id": 23, "name": "pencil", "color": "red" },
  { "id": 25, "name": "eraser", "color": "white" }
]';
JSON.parse(itemsJSON).forEach(item => console.info(item.name));

That's it. You can stop reading further.

Now, for others curious about how to code better, let's dig deeper.

What Do You Need?

Software development is fundamentally dependent on needs. A good code for one condition may be less suitable for another need. The concept of good here does not mean producing incorrect or correct results. Code already has to produce accurate results. Good code is efficient, easy to maintain, and provides an intuitive interface to the user.

We will develop some utility functions for hypothetical needs. For simplicity, I will assume the JSON data is a JSON array. This is an array of a given object, which is shallow, so we can focus on top-level keys to avoid recursion. In reality, most of the functions below should support recursion because of nested JSON objects. We will also assume all the files are valid JSON.

Here are our hypothetical user stories:

As an ACME API (our hypothetical API) user,

  1. I want to give every function a file name, so I can work with JSON files efficiently without parsing JSON data manually.
  2. I want to remove null value from a specified key (except "id"), so I can use the JSON structure in ACME API, which does not accept null value for specific keys.
  3. I want to execute a javascript function for every entry with the given value so that I can transform the old value into a new value.
  4. I want to filter an object's properties based on property names except "id," so I can hide sensitive information from sending to the web server.
  5. I want to sort JSON fields based on alphabetical orders of object keys, so I can log easy to read & find text format.

I purposefully added similar user stories to show a technique to combine them in a shared function.

First, we define types for TypeScript and a variable to store file contents for caching purposes.

type FilePath = string;
interface Item {
  id: number;
  name?: string | null;
  color?: string | null;
}
type Data = Item[];

Why do we define FilePath type instead of shorter type string? When we name the type, we can easily see what a function parameter type is easy.

Now, I have added a parseJSONFile function to read and parse JSON data from a local file for the first story. Since it is a text-based format, we can read json data from a local file like any text file.

export async function parseJSONFile<T>(filePath: string): Promise<T> {
  const content = await readFile(filePath, "utf8");
  return JSON.parse(content) as T;
}

We can write a separate javascript function for each story, which is the easiest way to start. However, 2 and 4 have a common need. The user wants to filter object keys for different conditions. We can add an iterator function to iterate each key-value pair of the inner objects and utilize that function for all our filtering needs. I will use a callback function parameter to process the entries having the given value.

async function filter(filePath: FilePath, filterFunction: FilterFunction): Promise<Data> {
  const data = await parseJSONFile<Data>(filePath);

  return data.map(
    (item) => Object.fromEntries(Object.entries(item).filter(([k, v]) => filterFunction([k as keyof Item, v as Item[keyof Item]]))) as Item,
  );
}

Now I will add two filtering functions easily. Since id it shouldn't be removed. We will throw an error message if json key is equal to id.

export async function filterItemKey(filePath: FilePath, keyToRemove: string): Promise<Data> {
  if (keyToRemove === "id") throw new Error("'id' cannot be removed.");
  return filter(filePath, ([key]) => !(key === keyToRemove));
}

export async function filterItemNullKey(filePath: FilePath, keyToRemove: string): Promise<Data> {
  if (keyToRemove === "id") throw new Error("'id' cannot be removed.");
  return filter(filePath, ([key, value]) => !(key === keyToRemove && value === null));
}

Like the previous example, I will add a map iterator for user story 3. Again I will use a callback function here:

async function map(filePath: FilePath, transformFunction: TransformFunction): Promise<Data> {
  const data = await parseJSONFile<Data>(filePath);

  return data.map((item) =>
    Object.fromEntries(Object.entries(item).map(([k, v]) => transformFunction([k as keyof Item, v as Item[keyof Item]]))),
  ) as unknown as Data;
}

export async function mapItem(filePath: FilePath, transformFunction: TransformFunction): Promise<Data> {
  return map(filePath, ([key, value]) => transformFunction([key, value]));
}

From now on, we can add as many filter functions and transform utility functions as we need.

The following code shows us an example of how to use the functions:

console.info(await filterItemKey("data/array.json", "color"));
console.info(await filterItemNullKey("data/array.json", "color"));
console.info(await mapItem("data/array.json", ([key]) => [key, "***"]));

How to Sort Object Keys Alphabetically?

There is one final user story. Story 5 requires us to sort object key names alphabetically for readability purposes. Historically, it was impossible to sort the key names of objects because JavaScript engines, unlike Maps, do not guarantee ordering the key names of JavaScript objects. However, for this example, most JavaScript engines today return keys in the order in which they were added. So looking above examples, I will leave it to you as an exercise.

Also, we can use the same technique to get JSON data from a web server, 3rd party service, etc. This can also be a good exercise for the curious reader. Excellence comes with practice. I can't recommend you enough to do these exercises.

I hope you enjoyed s there a better code structure for the given user stories? Share your thoughts and exercise solutions with other readers in the comments section to encourage others.