From Raw API Responses to Smart Search Results: Transforming a Basic Location API

When building a hotel booking platform, one of the most critical features is helping users find the right locations quickly and accurately. I integrated with a third-party location API, but the raw results were... let's just say they needed some work.

The Challenge

  • Duplicate locations with slight variations
  • Incomplete location names with trailing commas
  • Generic results that didn't match user intent
  • Poor prioritization of relevant results

Sound familiar? This is exactly the challenge I faced when working on our hotel booking backend system. Let me walk you through how I solved it.

The Raw API Response

Here's what we were getting from the external location service:

// Raw API response - notice the mess
const locations = [
  {
    id: '599181',
    name: "Hong Kong, People's Republic of China",
    code: 'CN',
    lat: 22.233358,
    lng: 114.122479,
  },
  {
    id: '674307',
    name: 'Hong Kong, ',
    code: 'HK',
    lat: 22.28333,
    lng: 114.15875,
  },
  {
    id: '638571',
    name: 'Hong Kong, Hong Kong',
    code: 'HK',
    lat: 22.28333,
    lng: 114.15875,
  },
  {
    id: '667696',
    name: 'Hong Kong, ',
    code: 'HK',
    lat: 22.28333,
    lng: 114.15875,
  },
  {
    id: '674318',
    name: 'Hong Kong, ',
    code: 'HK',
    lat: 22.28333,
    lng: 114.15875,
  },
  {
    id: '600656',
    name: "Hong Kong International Airport, People's Republic of China",
    code: 'CN',
    lat: 22.245097,
    lng: 114.118412,
  },
  {
    id: '638579',
    name: 'Hong Kong Island, Hong Kong, Hong Kong',
    code: 'HK',
    lat: 22.24798,
    lng: 114.186745,
  },
  {
    id: '638590',
    name: 'Hong Kong East, Hong Kong, Hong Kong',
    code: 'HK',
    lat: 22.269901,
    lng: 114.224871,
  },
  {
    id: '638529',
    name: 'Hong Kong, Hong Kong',
    code: 'HK',
    lat: 22.28333,
    lng: 114.15875,
  },
  {
    id: '638586',
    name: 'Lantau Island, Hong Kong, Hong Kong',
    code: 'HK',
    lat: 22.318955,
    lng: 114.03946,
  },
  {
    id: '638530',
    name: 'Kowloon, Hong Kong, Hong Kong',
    code: 'HK',
    lat: 22.325375,
    lng: 114.198885,
  },
  {
    id: '667510',
    name: 'New Territories, Hong Kong, ',
    code: 'HK',
    lat: 22.396845,
    lng: 114.148319,
  },
  {
    id: '667513',
    name: 'Sha Tin, Hong Kong, ',
    code: 'HK',
    lat: 22.379604,
    lng: 114.187775,
  },
  {
    id: '667517',
    name: 'Tsuen Wan, Hong Kong, ',
    code: 'HK',
    lat: 22.374465,
    lng: 114.110401,
  },
  {
    id: '638583',
    name: 'Tin Shui Wai, Hong Kong, Hong Kong',
    code: 'HK',
    lat: 22.459955,
    lng: 114.003495,
  },
  {
    id: '667535',
    name: 'Tuen Mun, Hong Kong, ',
    code: 'HK',
    lat: 22.39107,
    lng: 113.97633,
  },
  {
    id: '638534',
    name: 'Aberdeen, Hong Kong, Hong Kong',
    code: 'HK',
    lat: 22.24781,
    lng: 114.160715,
  },
  {
    id: '638563',
    name: 'Tsing Yi, Hong Kong, Hong Kong',
    code: 'HK',
    lat: 22.34146,
    lng: 114.09705,
  },
  {
    id: '638531',
    name: 'Cheung Chau, Hong Kong, Hong Kong',
    code: 'HK',
    lat: 22.214255,
    lng: 114.02919,
  },
  {
    id: '638532',
    name: 'Sha Tin, Hong Kong, Hong Kong',
    code: 'HK',
    lat: 22.379604,
    lng: 114.187775,
  },
]

Notice the issues? Trailing commas, duplicates, and inconsistent formatting. Not exactly user-friendly.

The Solution

I implemented data processing function that transforms these raw responses into clean, prioritized, and user-friendly results. Here's how:

In all the search responses from api first would always be accurate and formatted so we will process rest of the responses and keep the 1st at top.

Step 1: Duplicate Detection

First, I created a function to identify and remove duplicates based on semantic similarity rather than exact matches:

function areLocationsDuplicate(loc1, loc2) {
  // Clean and normalize names
  const cleanName1 = loc1.name.toLowerCase().replace(/,\s*$/, '').trim()
  const cleanName2 = loc2.name.toLowerCase().replace(/,\s*$/, '').trim()

  // Extract main location name (first part before comma)
  const mainName1 = cleanName1.split(',')[0].trim()
  const mainName2 = cleanName2.split(',')[0].trim()

  return mainName1 === mainName2
}

Key insight: By comparing the main location name (before the first comma), we catch duplicates even when the full names differ slightly.

Step 2: Prioritization System

I developed a scoring algorithm that evaluates each location based on multiple criteria:

function calculatePriority(location, firstLocationCode, keyword) {
  let score = 0
  const name = location.name.toLowerCase()

  // Reward more descriptive names
  const nameParts = location.name.split(',').map((part) => part.trim())
  score += nameParts.length * 2

  // Penalize empty or incomplete parts
  const emptyParts = nameParts.filter(
    (part) => part === '' || part === ' '
  ).length
  score -= emptyParts * 10

  // Reward specific location types
  if (name.endsWith(keyword)) score += 20
  if (name.includes('international')) score += 12
  if (name.includes('airport')) score += 5
  if (name.includes('island')) score += 10

  // Penalize very generic names
  if (name.match(/^[a-z\s]+,\s*$/)) score -= 20

  // Boost priority for locations with matching codes
  if (location.code && location.code === firstLocationCode) {
    score += 2
  }

  return score
}

Step 3: The Complete Processing Pipeline

Here's the main processing function that ties everything together:

function processLocationData(data, keyword) {
  const locations = data || []

  if (locations.length <= 1) {
    return locations
  }

  // Keep the first result at the top (always most accurate)
  const firstLocation = locations[0]
  const remainingLocations = locations.slice(1)

  // Calculate priority scores and sort
  const processedRemainingLocations = remainingLocations
    .map((location) => ({
      ...location,
      priority: calculatePriority(location, firstLocation.code, keyword),
    }))
    .sort((a, b) => b.priority - a.priority)

  // Remove duplicates, keeping highest priority versions
  const uniqueRemainingLocations = []

  for (const location of processedRemainingLocations) {
    const isDuplicate = uniqueRemainingLocations.some((existing) =>
      areLocationsDuplicate(location, existing)
    )

    if (!isDuplicate) {
      uniqueRemainingLocations.push(location)
    }
  }

  // Clean up and return results
  const cleanedRemainingLocations = uniqueRemainingLocations.map(
    ({ priority, ...location }) => location
  )

  return [firstLocation, ...cleanedRemainingLocations]
}

Pro tip: The first result from the API is always kept at the top since it's typically the most accurate match. We process the rest to ensure quality.

The Results: From Chaos to Clarity

The transformation was remarkable. Here's a side-by-side comparison:

Before

1. Hong Kong, People's Republic of China
2. Hong Kong,
3. Hong Kong, Hong Kong
4. Hong Kong,
5. Hong Kong,
6. Hong Kong International Airport, People's Republic of China
7. Hong Kong Island, Hong Kong, Hong Kong
8. Hong Kong East, Hong Kong, Hong Kong
9. Hong Kong, Hong Kong
10. Lantau Island, Hong Kong, Hong Kong
11. Kowloon, Hong Kong, Hong Kong
12. New Territories, Hong Kong,
13. Sha Tin, Hong Kong,
14. Tsuen Wan, Hong Kong,
15. Tin Shui Wai, Hong Kong, Hong Kong
16. Tuen Mun, Hong Kong,
17. Aberdeen, Hong Kong, Hong Kong
18. Tsing Yi, Hong Kong, Hong Kong
19. Cheung Chau, Hong Kong, Hong Kong
20. Sha Tin, Hong Kong, Hong Kong

After

1. Hong Kong, People's Republic of China
2. Hong Kong International Airport, People's Republic of China
3. Hong Kong Island, Hong Kong, Hong Kong
4. Lantau Island, Hong Kong, Hong Kong
5. Hong Kong East, Hong Kong, Hong Kong
6. Kowloon, Hong Kong, Hong Kong
7. Tin Shui Wai, Hong Kong, Hong Kong
8. Aberdeen, Hong Kong, Hong Kong
9. Tsing Yi, Hong Kong, Hong Kong
10. Cheung Chau, Hong Kong, Hong Kong
11. Sha Tin, Hong Kong, Hong Kong
12. Hong Kong, Hong Kong
13. New Territories, Hong Kong,
14. Tsuen Wan, Hong Kong,
15. Tuen Mun, Hong Kong,

Key Benefits

User Experience

Clean, readable location names without formatting issues

Relevance

More specific and useful locations appear first

Deduplication

No more confusing duplicate entries

Intelligence

System understands context and prioritizes accordingly

Scalability

Algorithm works with any location data structure

The Takeaway

This experience taught me that sometimes the most impactful improvements come not from building complex new features, but from thoughtful enhancements to existing systems. By applying smart algorithms to clean, deduplicate, and prioritize API responses, we transformed a basic location search into an intelligent, user-friendly experience.

The key is to think like your users:

What would they actually want to see? How can you make their lives easier? Sometimes the best code is the code that makes the complex look simple.


Have you faced similar challenges with third-party APIs? I'd love to hear how you solved them. Feel free to reach out on GitHub or Instagram.