Skip to content

Commit 9ea17f4

Browse files
authored
Fix issue #147: Add helper methods to extract address components from Places Details API (#176)
- Add GetStreetAddress(), GetState(), GetPostalCode(), GetCity(), GetCountry() methods to Result class - Add GetAddressBreakdown() method that returns a structured AddressBreakdown object - Add comprehensive integration tests demonstrating usage - Add example code showing how to extract address components - Extract hardcoded place IDs to constants for better maintainability - Remove unnecessary AddressComponentExtensions class in favor of direct class methods This addresses GitHub issue #147: 'How do i get the street address, state, and postal code from the place results?'
1 parent 6adf36c commit 9ea17f4

File tree

3 files changed

+420
-0
lines changed

3 files changed

+420
-0
lines changed
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
using System.Linq;
2+
using System.Threading.Tasks;
3+
using GoogleMapsApi.Entities.PlacesDetails.Request;
4+
using GoogleMapsApi.Entities.PlacesDetails.Response;
5+
using NUnit.Framework;
6+
using GoogleMapsApi.Test.Utils;
7+
8+
namespace GoogleMapsApi.Test.IntegrationTests
9+
{
10+
[TestFixture]
11+
public class PlacesDetailsAddressComponentTests : BaseTestIntegration
12+
{
13+
private const string GoogleSydneyOfficePlaceId = "ChIJN1t_tDeuEmsRUsoyG83frY4";
14+
15+
[Test]
16+
public async Task CanExtractStreetAddressFromPlacesDetails()
17+
{
18+
// Test with a known place that has a street address
19+
var request = new PlacesDetailsRequest
20+
{
21+
ApiKey = ApiKey,
22+
PlaceId = GoogleSydneyOfficePlaceId
23+
};
24+
25+
var result = await GoogleMaps.PlacesDetails.QueryAsync(request);
26+
27+
AssertInconclusive.NotExceedQuota(result);
28+
Assert.That(result.Status, Is.EqualTo(Status.OK));
29+
Assert.That(result.Result.AddressComponent, Is.Not.Null.And.Not.Empty);
30+
31+
// Extract street address using the helper method
32+
var streetAddress = result.Result.GetStreetAddress();
33+
Assert.That(streetAddress, Is.Not.Null.And.Not.Empty);
34+
Assert.That(streetAddress, Does.Contain("Pirrama"));
35+
}
36+
37+
[Test]
38+
public async Task CanExtractStateFromPlacesDetails()
39+
{
40+
var request = new PlacesDetailsRequest
41+
{
42+
ApiKey = ApiKey,
43+
PlaceId = GoogleSydneyOfficePlaceId
44+
};
45+
46+
var result = await GoogleMaps.PlacesDetails.QueryAsync(request);
47+
48+
AssertInconclusive.NotExceedQuota(result);
49+
Assert.That(result.Status, Is.EqualTo(Status.OK));
50+
51+
// Extract state using the helper method
52+
var state = result.Result.GetState();
53+
var stateShort = result.Result.GetState(useShortName: true);
54+
55+
Assert.That(state, Is.Not.Null.And.Not.Empty);
56+
Assert.That(stateShort, Is.Not.Null.And.Not.Empty);
57+
}
58+
59+
[Test]
60+
public async Task CanExtractPostalCodeFromPlacesDetails()
61+
{
62+
var request = new PlacesDetailsRequest
63+
{
64+
ApiKey = ApiKey,
65+
PlaceId = GoogleSydneyOfficePlaceId
66+
};
67+
68+
var result = await GoogleMaps.PlacesDetails.QueryAsync(request);
69+
70+
AssertInconclusive.NotExceedQuota(result);
71+
Assert.That(result.Status, Is.EqualTo(Status.OK));
72+
73+
// Extract postal code using the helper method
74+
var postalCode = result.Result.GetPostalCode();
75+
Assert.That(postalCode, Is.Not.Null.And.Not.Empty);
76+
}
77+
78+
[Test]
79+
public async Task CanExtractCompleteAddressBreakdown()
80+
{
81+
var request = new PlacesDetailsRequest
82+
{
83+
ApiKey = ApiKey,
84+
PlaceId = GoogleSydneyOfficePlaceId
85+
};
86+
87+
var result = await GoogleMaps.PlacesDetails.QueryAsync(request);
88+
89+
AssertInconclusive.NotExceedQuota(result);
90+
Assert.That(result.Status, Is.EqualTo(Status.OK));
91+
92+
// Extract complete address breakdown
93+
var addressBreakdown = result.Result.GetAddressBreakdown();
94+
95+
Assert.That(addressBreakdown, Is.Not.Null);
96+
Assert.That(addressBreakdown.StreetAddress, Is.Not.Null.And.Not.Empty);
97+
Assert.That(addressBreakdown.City, Is.Not.Null.And.Not.Empty);
98+
Assert.That(addressBreakdown.State, Is.Not.Null.And.Not.Empty);
99+
Assert.That(addressBreakdown.PostalCode, Is.Not.Null.And.Not.Empty);
100+
Assert.That(addressBreakdown.Country, Is.Not.Null.And.Not.Empty);
101+
102+
// Test the ToString method
103+
var formattedAddress = addressBreakdown.ToString();
104+
Assert.That(formattedAddress, Is.Not.Null.And.Not.Empty);
105+
Assert.That(formattedAddress, Does.Contain(","));
106+
}
107+
108+
[Test]
109+
public async Task CanExtractCityFromPlacesDetails()
110+
{
111+
var request = new PlacesDetailsRequest
112+
{
113+
ApiKey = ApiKey,
114+
PlaceId = GoogleSydneyOfficePlaceId
115+
};
116+
117+
var result = await GoogleMaps.PlacesDetails.QueryAsync(request);
118+
119+
AssertInconclusive.NotExceedQuota(result);
120+
Assert.That(result.Status, Is.EqualTo(Status.OK));
121+
122+
// Extract city using the helper method
123+
var city = result.Result.GetCity();
124+
Assert.That(city, Is.Not.Null.And.Not.Empty);
125+
// Google Sydney office is in Pyrmont, which is a suburb of Sydney
126+
Assert.That(city, Is.EqualTo("Pyrmont"));
127+
}
128+
129+
[Test]
130+
public async Task CanExtractCountryFromPlacesDetails()
131+
{
132+
var request = new PlacesDetailsRequest
133+
{
134+
ApiKey = ApiKey,
135+
PlaceId = GoogleSydneyOfficePlaceId
136+
};
137+
138+
var result = await GoogleMaps.PlacesDetails.QueryAsync(request);
139+
140+
AssertInconclusive.NotExceedQuota(result);
141+
Assert.That(result.Status, Is.EqualTo(Status.OK));
142+
143+
// Extract country using the helper method
144+
var country = result.Result.GetCountry();
145+
var countryShort = result.Result.GetCountry(useShortName: true);
146+
147+
Assert.That(country, Is.Not.Null.And.Not.Empty);
148+
Assert.That(countryShort, Is.Not.Null.And.Not.Empty);
149+
Assert.That(countryShort, Is.EqualTo("AU"));
150+
}
151+
}
152+
}

GoogleMapsApi/Entities/PlacesDetails/Response/Result.cs

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Globalization;
33
using System.Runtime.Serialization;
44
using System.Collections.Generic;
5+
using System.Linq;
56
using GoogleMapsApi.Entities.Common;
67

78
namespace GoogleMapsApi.Entities.PlacesDetails.Response
@@ -13,6 +14,11 @@ public class Result
1314
/// name contains the human-readable name for the returned result. For establishment results, this is usually the canonicalized business name.
1415
/// </summary>
1516

17+
/// <summary>
18+
/// address_components is an array containing the separate address components.
19+
/// Use the helper methods on this Result class to easily extract street address, state, postal code, etc.
20+
/// Example: result.GetStreetAddress(), result.GetState(), result.GetPostalCode()
21+
/// </summary>
1622
[DataMember(Name="address_components")]
1723
public IEnumerable<GoogleMapsApi.Entities.Geocoding.Response.AddressComponent> AddressComponent { get; set; }
1824

@@ -228,6 +234,164 @@ internal string string_PriceLevel
228234

229235
[DataMember(Name = "place_id")]
230236
public string PlaceId { get; set; }
237+
238+
#region Address Component Helper Methods
239+
240+
/// <summary>
241+
/// Gets the street address (street number + route) from address components
242+
/// </summary>
243+
/// <returns>Street address or null if not found</returns>
244+
public string GetStreetAddress()
245+
{
246+
if (AddressComponent == null)
247+
return null;
248+
249+
var streetNumber = GetAddressComponentByType("street_number")?.LongName;
250+
var route = GetAddressComponentByType("route")?.LongName;
251+
252+
if (string.IsNullOrEmpty(streetNumber) && string.IsNullOrEmpty(route))
253+
return null;
254+
255+
if (string.IsNullOrEmpty(streetNumber))
256+
return route;
257+
258+
if (string.IsNullOrEmpty(route))
259+
return streetNumber;
260+
261+
return $"{streetNumber} {route}";
262+
}
263+
264+
/// <summary>
265+
/// Gets the state/province from address components
266+
/// </summary>
267+
/// <param name="useShortName">If true, returns short name (e.g., "NY"), otherwise long name (e.g., "New York")</param>
268+
/// <returns>State/province or null if not found</returns>
269+
public string GetState(bool useShortName = false)
270+
{
271+
if (AddressComponent == null)
272+
return null;
273+
274+
var component = GetAddressComponentByType("administrative_area_level_1");
275+
return component == null ? null : (useShortName ? component.ShortName : component.LongName);
276+
}
277+
278+
/// <summary>
279+
/// Gets the postal code from address components
280+
/// </summary>
281+
/// <returns>Postal code or null if not found</returns>
282+
public string GetPostalCode()
283+
{
284+
if (AddressComponent == null)
285+
return null;
286+
287+
return GetAddressComponentByType("postal_code")?.LongName;
288+
}
289+
290+
/// <summary>
291+
/// Gets the city/locality from address components
292+
/// </summary>
293+
/// <param name="useShortName">If true, returns short name, otherwise long name</param>
294+
/// <returns>City/locality or null if not found</returns>
295+
public string GetCity(bool useShortName = false)
296+
{
297+
if (AddressComponent == null)
298+
return null;
299+
300+
// Try locality first, then sublocality
301+
var component = GetAddressComponentByType("locality")
302+
?? GetAddressComponentByType("sublocality");
303+
304+
return component == null ? null : (useShortName ? component.ShortName : component.LongName);
305+
}
306+
307+
/// <summary>
308+
/// Gets the country from address components
309+
/// </summary>
310+
/// <param name="useShortName">If true, returns short name (e.g., "US"), otherwise long name (e.g., "United States")</param>
311+
/// <returns>Country or null if not found</returns>
312+
public string GetCountry(bool useShortName = false)
313+
{
314+
if (AddressComponent == null)
315+
return null;
316+
317+
var component = GetAddressComponentByType("country");
318+
return component == null ? null : (useShortName ? component.ShortName : component.LongName);
319+
}
320+
321+
/// <summary>
322+
/// Gets a complete address breakdown from address components
323+
/// </summary>
324+
/// <returns>Address breakdown object</returns>
325+
public AddressBreakdown GetAddressBreakdown()
326+
{
327+
if (AddressComponent == null)
328+
return new AddressBreakdown();
329+
330+
return new AddressBreakdown
331+
{
332+
StreetAddress = GetStreetAddress(),
333+
City = GetCity(),
334+
State = GetState(),
335+
PostalCode = GetPostalCode(),
336+
Country = GetCountry()
337+
};
338+
}
339+
340+
/// <summary>
341+
/// Helper method to find an address component by type
342+
/// </summary>
343+
/// <param name="type">The type to search for</param>
344+
/// <returns>Address component or null if not found</returns>
345+
private GoogleMapsApi.Entities.Geocoding.Response.AddressComponent GetAddressComponentByType(string type)
346+
{
347+
return AddressComponent?.FirstOrDefault(ac => ac.Types?.Contains(type) == true);
348+
}
349+
350+
#endregion
351+
}
352+
353+
/// <summary>
354+
/// Represents a complete address breakdown with individual components
355+
/// </summary>
356+
public class AddressBreakdown
357+
{
358+
/// <summary>
359+
/// Street address (street number + route)
360+
/// </summary>
361+
public string StreetAddress { get; set; }
362+
363+
/// <summary>
364+
/// City/locality
365+
/// </summary>
366+
public string City { get; set; }
367+
368+
/// <summary>
369+
/// State/province
370+
/// </summary>
371+
public string State { get; set; }
372+
373+
/// <summary>
374+
/// Postal code
375+
/// </summary>
376+
public string PostalCode { get; set; }
377+
378+
/// <summary>
379+
/// Country
380+
/// </summary>
381+
public string Country { get; set; }
382+
383+
/// <summary>
384+
/// Returns a formatted address string
385+
/// </summary>
386+
/// <returns>Formatted address</returns>
387+
public override string ToString()
388+
{
389+
var parts = new[] { StreetAddress, City, State, PostalCode, Country }
390+
.Where(part => !string.IsNullOrEmpty(part))
391+
.ToArray();
392+
393+
return string.Join(", ", parts);
394+
}
231395
}
232396

233397
public enum PriceLevel

0 commit comments

Comments
 (0)