5

I'm trying to write an API - the expected output (for a 3rd party) as shown on their website is the JSON below:

{
"api_version" : 4 ,
"hotel_ids" : [97497],   
"hotels" :
    [
        {
            "hotel_id": 97497,
            "room_types":
                {
                    "Fenway Room":
                        {
                            "url": "someurlhere",
                            "price": 178.50, 
                            "room_code": "SINGLE"                               
                        }
                }
        }
    ]
}

I'm using the online tool: http://json2csharp.com/

It gives me the following classes:

public class FenwayRoom
{
public string url { get; set; }
public double price { get; set; }
public string room_code { get; set; }
}

public class RoomTypes
{
public FenwayRoom __invalid_name__Fenway Room { get; set; }
}

public class Hotel
{
public int hotel_id { get; set; }
public RoomTypes room_types { get; set; }
}

public class RootObject
{
public int api_version { get; set; }
public List<int> hotel_ids { get; set; }
public List<Hotel> hotels { get; set; }
}

You can see in RoomTypes:

FenwayRoom __invalid_name__Fenway Room { get; set; }

I'm wondering if their spec for the JSON is wrong, or is there a way for me to create the classes to return the JSON they are expecting?

In the example, I believe the room type "Fenway Room" is a variable - so I don't think it can also be a class name. But it may just be that I don't know how to create a class like this.

I just can't figure out the "Fenway Room" - which I think needs to have something like: "Room Name":"Fenway Room" - but maybe there is another way of defining the classes so that it doesn't need a label "Room Name".

I'd appreciate any help in confirming if it is possible to create classes to match against their JSON.

Thanks, Mark

Mark
  • 7,778
  • 24
  • 89
  • 147
  • Did you try `JavaScriptSerializer` ? It has a generic method to convert Here is a link to check out http://msdn.microsoft.com/en-us/library/bb355316.aspx – Alex Denysenko Oct 31 '13 at 13:29
  • 2
    The problem seems to be "Fenway Room" having a space in it - therefore that tool can't auto-generate a mapping for it since spaces aren't allowed in identifiers. So you may have to use some custom serialisation technique that doesn't rely on direct identifier mapping. Of course if the supplier can modify their feed to remove the space, all the better – Stephen Byrne Oct 31 '13 at 13:30
  • Hi Alex - no, what is that? I have to create the object to return in my MVC C# code - and the object has to return example code that is pasted first above. I just can't figure out the "Fenway Room" - which I think needs to have something like: "Room Name":"Fenway Room" - but maybe there is another way of defining the classes so that it doesn't need a label "Room Name". Thanks, Mark – Mark Oct 31 '13 at 13:31
  • My advice would be either to do this by hand, or use something like JSON.net to read the objects without strongly-typing. Automated tools aren't going to look at properties like `room_types` and think *that should be an array, because it's got an 's' at the end*. It's going to generate a literal object. It shouldn't take too long to write up a decent structure. – CodingIntrigue Oct 31 '13 at 13:31
  • Hi Stephen - it's for TripAdvisor - I have to comply with them, they won't change for me :( – Mark Oct 31 '13 at 13:32
  • Read about `JavsScriptSerializer` and about serialization in general. It is a big topic. http://msdn.microsoft.com/en-us/library/vstudio/ms233843.aspx Still I don't think you can 'create' classes with json. (dynamic?) – Alex Denysenko Oct 31 '13 at 13:33
  • Possible duplicate of... http://stackoverflow.com/questions/8738031/deserializing-json-using-json-net-with-dynamic-data – Alex Dresko Oct 31 '13 at 13:59
  • Please remove your other question related to the topic [http://stackoverflow.com/q/19708883/674700](http://stackoverflow.com/q/19708883/674700) and edit this one if you want to provide more details – Alex Filipovici Oct 31 '13 at 15:53

4 Answers4

1

Alter your classes to the following structure:

public class Rootobject
{
    public int api_version { get; set; }
    public List<int> hotel_ids { get; set; }
    public List<Hotel> hotels { get; set; }
}

public class Hotel
{
    public int hotel_id { get; set; }
    public Room_Types room_types { get; set; }
}

public class Room_Types
{
    public List<Room> Rooms { get; set; }
}

public class Room
{
    public string Type { get; set; }
    public string Url { get; set; }
    public float Price { get; set; }
}

Create the following class, which implements the JsonConverter abstract class:

public abstract class MyJsonConverter<T> : JsonConverter
{
    protected abstract T Create(Type objectType, JObject jObject);

    public override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }

    public override object ReadJson
        (JsonReader reader, 
        Type objectType,
        object existingValue,
        JsonSerializer serializer)
    {
        JObject jObject = JObject.Load(reader);
        T target = Create(objectType, jObject);
        serializer.Populate(jObject.CreateReader(), target);
        return target;
    }

    public override void WriteJson
        (JsonWriter writer, 
        object value, 
        JsonSerializer serializer)
    {
        //
    }
}

Implement the MyJsonConverter<T> class as follows:

public class RoomConverter : MyJsonConverter<Room_Types>
{
    protected override Room_Types Create(Type objectType, JObject jObject)
    {
        var rooms = jObject.Cast<JToken>().Select(t => new Room
        {
            Type = ((JProperty)t).Name,
            Url = ((JProperty)t).First
                .SelectToken("url").ToString(),
            Price = float.Parse(((JProperty)t).First
                .SelectToken("price").ToString())
        }).ToList();
        return new Room_Types() { Rooms = rooms };
    }
    public override void WriteJson
        (JsonWriter writer, 
        object value, 
        JsonSerializer serializer)
    {
        writer.WriteStartObject();
        ((Room_Types)value).Rooms.ForEach(r =>
        {
            writer.WritePropertyName(r.Type);
            writer.WriteStartObject();
            writer.WritePropertyName("url");
            writer.WriteValue(r.Url);
            writer.WritePropertyName("price");
            writer.WriteValue(r.Price);
            writer.WriteEndObject();
        });
        writer.WriteEndObject();
    }
}

Now you may deserialize / serialize like this:

var result = JsonConvert
    .DeserializeObject<Rootobject>(jsonText, new RoomConverter());
var serialized = JsonConvert
    .SerializeObject(result, new RoomConverter());
Alex Filipovici
  • 31,789
  • 6
  • 54
  • 78
1

When using Json.NET the output

"room_types":
{
    "Fenway Room":
    {
        "url": "someurlhere",
        "price": 178.50, 
        "room_code": "SINGLE"                               
        }
    }
}

look exactly what you would get serializing a Dictionary.

Change your class to

public class Room
{
    public string url { get; set; }
    public double price { get; set; }
    public string room_code { get; set; }
}

public class Hotel
{
    public int hotel_id { get; set; }
    public Dictionary<string, Room> room_types { get; set; }
}

public class RootObject
{
    public int api_version { get; set; }
    public List<int> hotel_ids { get; set; }
    public List<Hotel> hotels { get; set; }
}

This is all predicated on using Json.NET, which gives you this serialization/deserialization of dictionaries in this format for free. You can do this with the .NET framework serializer, but you need to do extra work.

btlog
  • 4,760
  • 2
  • 29
  • 38
0

Try adding the JsonPropteryAttribute.

public class RoomTypes
{
    [JsonProperty(PropertyName="FenWay Room")]
    public FenwayRoom room { get; set; }
}
Kristof Degrave
  • 4,142
  • 22
  • 32
0

If you use this tool (http://jsonclassgenerator.codeplex.com/) it will generate slightly better C# that supports those property names.

// Generated by Xamasoft JSON Class Generator // http://www.xamasoft.com/json-class-generator

using System; using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Example.SampleResponse1JsonTypes;

namespace Example.SampleResponse1JsonTypes
{

    internal class FenwayRoom
    {

        [JsonProperty("url")]
        public string Url { get; set; }

        [JsonProperty("price")]
        public double Price { get; set; }

        [JsonProperty("room_code")]
        public string RoomCode { get; set; }
    }

    internal class RoomTypes
    {

        [JsonProperty("Fenway Room")]
        public FenwayRoom FenwayRoom { get; set; }
    }

    internal class Hotel
    {

        [JsonProperty("hotel_id")]
        public int HotelId { get; set; }

        [JsonProperty("room_types")]
        public RoomTypes RoomTypes { get; set; }
    }

}

namespace Example
{

    internal class SampleResponse1
    {

        [JsonProperty("api_version")]
        public int ApiVersion { get; set; }

        [JsonProperty("hotel_ids")]
        public int[] HotelIds { get; set; }

        [JsonProperty("hotels")]
        public Hotel[] Hotels { get; set; }
    }

}
Alex Dresko
  • 5,179
  • 3
  • 37
  • 57
  • Hi - sorry, I don't think I'm putting it across properly. "FenwayRoom" is a room name, not a class - so if I had 50 different rooms, I don't think I can dynamically create classes in C# code in visual studio. I *believe* "FenwayRoom" should be "RoomName": "FenwayRoom" - but I'm wondering if there is a default property, so that "FenwayRoom" doesn't need "RoomName" so I can return the JSON the API is expecting. Thanks, Mark – Mark Oct 31 '13 at 13:52
  • For the RoomType object, the description in the API says: room_types Map that uses a short description as the key and an availability room type object as the value. - so how can you have a dynamic name as a key in a C# class? Maybe I've jsut asked the question incorrectly - I'll post again - and thank everyone for their help. – Mark Oct 31 '13 at 13:59