最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c# - Transforming Multiple Response multiple response body format into one format - Stack Overflow

programmeradmin4浏览0评论

Question

I'm developing an apartment booking service that aggregates data from multiple external apartment detail suppliers. Each supplier provides a response body containing property information, including a list of available rooms. My goal is to transform these diverse supplier responses into a unified 'Apartment Booking Response' format. Their are multiple Rooms available under one Property ID. But When I try to transform all the room related details to Apartment Search Details, Room related details are not mapping.

1. responseMapping.json:

Contains the JSON mappings to transform supplier response data.

{
    "ResponseMapper": "{\"PROPERTY_ID\": [ \"properties[*].propertyId\", \"property_id\" ], \"NAME\": [ \"properties[*].propertyName\", \"rooms[*].room_name\", null ], \"ADDRESS\": [ null, null], \"STAR_RATING\": [ null, null], \"AMENITIES\": [ \"properties[*].rooms[*].benefits[*].benefitName\", \"rooms[*].rates[*].amenities[*].name\"], \"IMAGES\": [ null, null], \"LAT\": [ null, null], \"LON\": [ null, null], \"CANCELLATION_POLICY_FREE_CANCELLATION_UNTIL\": [ \"properties[*].rooms[*].cancellationPolicy.date[*].before\", \"rooms[*].rates[*].cancel_penalties[*].start\"], \"CANCELLATION_POLICY_TYPE\": [ \"properties[*].rooms[*].cancellationPolicy.code\", \"rooms[*].rates[*].refundable\" ], \"PRICE_NET_RATE\": [ \"properties[*].rooms[*].rate.exclusive\", \"rooms[*].rates[*].occupancy_pricing['1'].totals.exclusive.request_currency.value\" ], \"PRICE_FINAL_RATE\": [ \"properties[*].rooms[*].rate.inclusive\", \"rooms[*].rates[*].occupancy_pricing['1'].totals.inclusive.request_currency.value\"], \"PRICE_CURRENCY\": [ \"properties[*].rooms[*].rate.currency\", \"rooms[*].rates[*].occupancy_pricing['1'].totals.inclusive.request_currency.currency\" ], \"ROOM_ID\": [ \"properties[*].rooms[*].roomId\", \"rooms[*].id\" ], \"NO_OF_ADULTS\": [ \"properties[*].rooms[*].normalBedding\", \"rooms[*].no_of_adults\"], \"ROOM_PRICE_NET_PRICE\": [ \"properties[*].rooms[*].rate.exclusive\", \"rooms[*].rates[*].occupancy_pricing['1'].totals.exclusive.request_currency.value\"], \"ROOM_PRICE_GROSS_PRICE\": [ \"properties[*].rooms[*].rate.inclusive\", \"rooms[*].rates[*].occupancy_pricing['1'].totals.inclusive.request_currency.value\"], \"ROOM_PRICE_TOTAL_PRICE\": [ \"properties[*].rooms[*].totalPayment.inclusive\", \"rooms[*].rates[*].occupancy_pricing['1'].totals.property_inclusive.request_currency.value\"], \"ROOM_FREE_CANCELLATION_UNTIL\": [ \"properties[*].rooms[*].cancellationPolicy.date[*].before\", \"rooms[*].rates[*].cancel_penalties[*].start\"], \"ROOM_TYPE\": [ \"properties[*].rooms[*].cancellationPolicy.code\", \"rooms[*].rates[*].refundable\", ], \"ROOM_PAYMENT_TIMINGS\": [ \"properties[*].rooms[*].cancellationPolicy.parameter\", \"rooms[*].rates[*].cancel_penalties[*].parameter\"]}"
}

2. StringSupplierResponseTransformer.cs:

Implements the logic to transform supplier responses to the standardized DTO.

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using TransformMultipleResponse.Model;

public class SupplierResponseTransformer
{
    private readonly JObject _responseMappings;

    public SupplierResponseTransformer(string responseMappingsFile)
    {
        string mappingsJson = File.ReadAllText(responseMappingsFile).Trim();
        JObject mappingsObject = JObject.Parse(mappingsJson);
        string responseMapperString = mappingsObject["ResponseMapper"].ToString();
        _responseMappings = JObject.Parse(responseMapperString);
    }

    public SupplierResponseTransformer(JObject responseMappings)
    {
        _responseMappings = responseMappings;
    }

    public async Task<List<ApartmentSearchResponseDTO>> TransformResponseAsync(string supplierResponseString)
    {
        JToken supplierData = JToken.Parse(supplierResponseString);
        List<JToken> allSupplierEntries = new List<JToken>();

        if (supplierData.Type == JTokenType.Array)
        {
            foreach (var item in supplierData)
            {
                if (item.Type == JTokenType.Array)
                {
                    allSupplierEntries.AddRange(GetSupplierEntries(item));
                }
                else if (item.Type == JTokenType.Object)
                {
                    allSupplierEntries.Add(item);
                }
            }
        }
        else if (supplierData.Type == JTokenType.Object)
        {
            allSupplierEntries.Add(supplierData);
        }
        else
        {
            throw new JsonReaderException("Unexpected JSON format");
        }

        var tasks = allSupplierEntries.Select(supplierEntry => Task.Run(() => TransformSupplierResponse((JObject)supplierEntry))).ToList();
        var responses = await Task.WhenAll(tasks);
        return responses.ToList();
    }

    private IEnumerable<JToken> GetSupplierEntries(JToken token)
    {
        if (token.Type == JTokenType.Array)
        {
            foreach (var item in token)
            {
                if (item.Type == JTokenType.Object)
                {
                    yield return item;
                }
                else if (item.Type == JTokenType.Array)
                {
                    foreach (var subItem in GetSupplierEntries(item))
                    {
                        yield return subItem;
                    }
                }
            }
        }
        else if (token.Type == JTokenType.Object)
        {
            yield return token;
        }
    }

    //
    private ApartmentSearchResponseDTO TransformSupplierResponse(JObject supplierEntry)
    {
        var propertyId = GetValue<int>(supplierEntry, "PROPERTY_ID");
        var propertyName = GetValue<string>(supplierEntry, "NAME");
        var address = GetValue<string>(supplierEntry, "ADDRESS");
        var starRating = GetValue<int>(supplierEntry, "STAR_RATING");
        var amenities = GetValue<List<string>>(supplierEntry, "AMENITIES");
        var images = GetValue<List<string>>(supplierEntry, "IMAGES");
        var lat = GetValue<double>(supplierEntry, "LAT");
        var lon = GetValue<double>(supplierEntry, "LON");
        var freeCancellationUntil = GetValue<string>(supplierEntry, "CANCELLATION_POLICY_FREE_CANCELLATION_UNTIL");
        var cancellationType = GetValue<string>(supplierEntry, "CANCELLATION_POLICY_TYPE");
        var netRate = GetValue<float>(supplierEntry, "PRICE_NET_RATE");
        var finalRate = GetValue<float>(supplierEntry, "PRICE_FINAL_RATE");
        var currency = GetValue<string>(supplierEntry, "PRICE_CURRENCY");

        // Get all available room IDs
        var rooms = new List<RoomDto>();
        var roomIdTokens = new List<JToken>();
        var roomIdPath = "";

        // Find the first working path for room IDs
        foreach (var path in GetPaths("ROOM_ID"))
        {
            if (string.IsNullOrEmpty(path))
                continue;

            try
            {
                var tokens = supplierEntry.SelectTokens(path).ToList();
                if (tokens.Any())
                {
                    roomIdTokens = tokens;
                    roomIdPath = path;
                    break;
                }
            }
            catch
            {
                continue;
            }
        }

        // Iterate through each room ID token
        for (int i = 0; i < roomIdTokens.Count; i++)
        {
            var roomIdToken = roomIdTokens[i];
            var roomId = roomIdToken.ToString();

            // Create a room DTO with default values
            var room = new RoomDto
            {
                RoomId = roomId,
                NoOfAdults = 0,
                Price = new RoomPriceDto
                {
                    NetPrice = 0,
                    GrossPrice = 0,
                    TotalPrice = 0
                },
                Policies = new RoomPoliciesDto
                {
                    Cancellation = new RoomCancellationDto
                    {
                        FreeCancellationUntil = null,
                        Type = null,
                        PaymentTimings = new List<string>()
                    }
                }
            };

            // Helper function to set values safely
            void SafeSetValue<T>(string placeholder, Action<T> setter)
            {
                foreach (var path in GetPaths(placeholder))
                {
                    if (string.IsNullOrEmpty(path))
                        continue;

                    try
                    {
                        var tokens = supplierEntry.SelectTokens(path).ToList();
                        if (tokens.Count > i)
                        {
                            var token = tokens[i];
                            if (token != null)
                            {
                                if (typeof(T) == typeof(string))
                                {
                                    setter((T)(object)token.ToString());
                                }
                                else if (typeof(T) == typeof(int) || typeof(T) == typeof(float) || typeof(T) == typeof(double))
                                {
                                    setter((T)Convert.ChangeType(token, typeof(T)));
                                }
                                else if (typeof(T) == typeof(List<string>))
                                {
                                    var list = new List<string>();
                                    if (token.Type == JTokenType.Array)
                                    {
                                        foreach (var item in token)
                                        {
                                            list.Add(item.ToString());
                                        }
                                    }
                                    else
                                    {
                                        list.Add(token.ToString());
                                    }
                                    setter((T)(object)list);
                                }
                                break;
                            }
                        }
                    }
                    catch
                    {
                        continue;
                    }
                }
            }

            // Set room values using our helper
            SafeSetValue<int>("NO_OF_ADULTS", value => room.NoOfAdults = value);
            SafeSetValue<float>("ROOM_PRICE_NET_PRICE", value => room.Price.NetPrice = value);
            SafeSetValue<float>("ROOM_PRICE_GROSS_PRICE", value => room.Price.GrossPrice = value);
            SafeSetValue<float>("ROOM_PRICE_TOTAL_PRICE", value => room.Price.TotalPrice = value);
            SafeSetValue<string>("ROOM_FREE_CANCELLATION_UNTIL", value => room.Policies.Cancellation.FreeCancellationUntil = value);
            SafeSetValue<string>("ROOM_TYPE", value => room.Policies.Cancellation.Type = value);
            SafeSetValue<List<string>>("ROOM_PAYMENT_TIMINGS", value => room.Policies.Cancellation.PaymentTimings = value);

            rooms.Add(room);
        }

        var transformedResponse = new ApartmentSearchResponseDTO
        {
            Results = new List<ApartmentResultDTO>
        {
            new ApartmentResultDTO
            {
                PropertyId = propertyId,
                Name = propertyName,
                Address = address,
                Location = new LocationDto
                {
                    Lat = lat,
                    Lon = lon
                },
                StarRating = starRating,
                Amenities = amenities,
                Images = images,
                CancellationPolicy = new CancellationPolicyDto
                {
                    FreeCancellationUntil = freeCancellationUntil,
                    Type = cancellationType
                },
                Price = new PriceDto
                {
                    NetRate = netRate,
                    FinalRate = finalRate,
                    Currency = currency
                },
                Rooms = rooms
            }
        },
            Meta = null
        };

        return transformedResponse;
    }


    private T GetValue<T>(JToken source, string placeholder)
    {
        var paths = GetPaths(placeholder);
        foreach (var path in paths)
        {
            try
            {
                if (string.IsNullOrEmpty(path))
                    continue;

                var tokens = source.SelectTokens(path).ToList();
                if (tokens.Count == 1)
                {
                    var token = tokens.First();
                    if (typeof(T) == typeof(List<string>) && token.Type == JTokenType.Array)
                    {
                        return token.ToObject<T>();
                    }
                    return token.Value<T>();
                }
                else if (tokens.Count > 1)
                {
                    if (typeof(T) == typeof(List<string>))
                    {
                        var list = tokens.Select(t => t.ToString()).ToList();
                        return (T)(object)list;
                    }
                }
            }
            catch (Exception)
            {
                continue;
            }
        }
        return default;
    }

    private List<string> GetPaths(string placeholder)
    {
        if (!_responseMappings.ContainsKey(placeholder))
        {
            return new List<string>();
        }

        JToken pathsToken = _responseMappings[placeholder];
        if (pathsToken.Type == JTokenType.Array)
        {
            return pathsToken.ToObject<List<string>>();
        }
        else if (pathsToken.Type == JTokenType.String)
        {
            return new List<string> { pathsToken.ToString() };
        }
        return new List<string>();
    }
}

3. Sample Response Body input

// Response 1:
string expediaResponseString = @"[
           [
           {
               ""property_id"": ""15375843"",
               ""status"": ""available"",
               ""rooms"": [
                 {
                    ""id"": ""321209120"",
                    ""room_name"": ""Property 1 Room (Beach Hideaway)"",
                 },
                                  {
                    ""id"": ""321209120"",
                    ""room_name"": ""Property 1 Room (Beach Hideaway)"",
                 }
                ]
            },
            {
               ""property_id"": ""15375844"",
               ""status"": ""available"",
               ""rooms"": [
                 {
                    ""id"": ""321209120"",
                    ""room_name"": ""Property 2 Room (Beach Resort)"",
                 },
                                  {
                    ""id"": ""321209120"",
                    ""room_name"": ""Property 2 Room (Beach Resort)"",
                 }
                ]
            }
            ]
            ]"
            

// ------------------------------------------------------------------------------

// Response 2:
string agodaResponseString = @"[
      {
    ""searchId"": 1642180748269790000,
    ""properties"": [
        {
            ""propertyId"": 12159,
            ""propertyName"": ""Meeru Maldives Resort Island"",
            ""rooms"": [
                {
                 ""roomId"": 484877002,
                 ""roomName"": ""Two Bedroom Villa"",
                 },                {
                 ""roomId"": 484877003,
                 ""roomName"": ""Three Bedroom Villa"",
                 },
            ]
        },
        {
            ""propertyId"": 12157,
            ""propertyName"": ""Medhufushi Island Resort"",
            ""rooms"": [
                {
                 ""roomId"": 484877004,
                 ""roomName"": ""Three Bedroom Villa"",
                 },                {
                 ""roomId"": 484877005,
                 ""roomName"": ""Three Bedroom Villa"",
                 },
            ]
        }       
    ]
}
]";

4. Output of each Response body

Output which im getting if I pass expedia response body is correct.

4.1. Output if I pass Expedia Response body:

[
  {
    "Results": [
      {
        "PropertyId": 15375843,
        "Name": "Room (Beach Hideaway)",
        "Address": null,
        "Location": {
          "Lat": 0.0,
          "Lon": 0.0
        },
        "StarRating": 0,
        "Amenities": [
          "Free WiFi",
          "Free breakfast"
        ],
        "Images": null,
        "CancellationPolicy": {
          "FreeCancellationUntil": "04/23/2025 17:30:00",
          "Type": "True"
        },
        "Price": {
          "NetRate": 2600.0,
          "FinalRate": 3317.6,
          "Currency": "USD"
        },
        "SupplierDetails": null,
        "Rooms": [
          {
            "RoomId": "321209120",
            "NoOfAdults": 0,
            "Price": {
              "NetPrice": 2600.0,
              "GrossPrice": 3317.6,
              "TotalPrice": 4629.6
            },
            "Policies": {
              "Cancellation": {
                "FreeCancellationUntil": "23-04-2025 17:30:00",
                "Type": "True",
                "PaymentTimings": []
              }
            }
          },
          {
            "RoomId": "321209120",
            "NoOfAdults": 0,
            "Price": {
              "NetPrice": 2600.0,
              "GrossPrice": 3317.6,
              "TotalPrice": 4629.6
            },
            "Policies": {
              "Cancellation": {
                "FreeCancellationUntil": "23-04-2025 17:30:00",
                "Type": "True",
                "PaymentTimings": []
              }
            }
          }
        ]
      }
    ],
    "Meta": null
  },
  {
    "Results": [
      {
        "PropertyId": 15375843,
        "Name": "Room (Beach Hideaway)",
        "Address": null,
        "Location": {
          "Lat": 0.0,
          "Lon": 0.0
        },
        "StarRating": 0,
        "Amenities": [
          "Free WiFi",
          "Free breakfast"
        ],
        "Images": null,
        "CancellationPolicy": {
          "FreeCancellationUntil": "04/23/2025 17:30:00",
          "Type": "True"
        },
        "Price": {
          "NetRate": 2600.0,
          "FinalRate": 3317.6,
          "Currency": "USD"
        },
        "SupplierDetails": null,
        "Rooms": [
          {
            "RoomId": "321209120",
            "NoOfAdults": 0,
            "Price": {
              "NetPrice": 2600.0,
              "GrossPrice": 3317.6,
              "TotalPrice": 4629.6
            },
            "Policies": {
              "Cancellation": {
                "FreeCancellationUntil": "23-04-2025 17:30:00",
                "Type": "True",
                "PaymentTimings": []
              }
            }
          }
        ]
      }
    ],
    "Meta": null
  }
]

4.2. Output if i pass Agoda Response body:

[
  {
    "Results": [
      {
        "PropertyId": 12159,
        "Name": "Meeru Maldives Resort Island",
        "Address": null,
        "Location": {
          "Lat": 0.0,
          "Lon": 0.0
        },
        "StarRating": 0,
        "Amenities": [
          "Breakfast",
          "Dinner included",
          "Buffet dinner",
     ],
        "Images": null,
        "CancellationPolicy": {
          "FreeCancellationUntil": "03/22/2025 00:00:00",
          "Type": "1D100P_100P"
        },
        "Price": {
          "NetRate": 715.39,
          "FinalRate": 912.84,
          "Currency": "USD"
        },
        "SupplierDetails": null,
        "Rooms": [
          {
            "RoomId": "972779372",
            "NoOfAdults": 4,
            "Price": {
              "NetPrice": 1952.62,
              "GrossPrice": 2491.54,
              "TotalPrice": 2539.54
            },
            "Policies": {
              "Cancellation": {
                "FreeCancellationUntil": "22-03-2025 00:00:00",
                "Type": "1D100P_100P",
                "PaymentTimings": [
                  "{\r\n  \"days\": 2,\r\n  \"charge\": \"P\",\r\n  \"value\": 0\r\n}",
                  "{\r\n  \"days\": 1,\r\n  \"charge\": \"P\",\r\n  \"value\": 100\r\n}",
                  "{\r\n  \"days\": 0,\r\n  \"charge\": \"P\",\r\n  \"value\": 100\r\n}"
                ]
              }
            }
          }
        ]
      }
    ],
    "Meta": null
  }
]

5. Issue in Response Body Structure

  • Output which im getting while im passing Expedia Response body is correct, giving the proper expeceted result.
  • If i pass Agods Response body is not giving the expected output, because its not giving me all the property's and all the rooms available in each respective property which is given in agodaResponse body.
  • Although all the methods in SupplierResponseTransformer class should be Generic methods and should not hardcode the response body field paths in properties[*].rooms[] or "property_id" in any methods in SupplierResponseTransformer class.
  • where if i pass Expedia Response body, it is giving all the property's and rooms available in the respective property in proper structure.

I want to adjust the methods in SupplierResponseTransformer class so it can able to transform any type of response body structure to 'Apartment Booking Response' format.

发布评论

评论列表(0)

  1. 暂无评论