2

I'm trying to parse a json string with for example [1,2,3] to an array during deserializing.

This is my json data:

[
    {
        "id": "1",
        "district": "1",
        "lon": "4.420650000000000000",
        "lat": "51.21782000000000000",
        "bikes": "19",
        "slots": "14",
        "zip": "2018",
        "address": "Koningin Astridplein",
        "addressNumber": null,
        "nearbyStations": "3,4,5,24",
        "status": "OPN",
        "name": "001- Centraal Station - Astrid"
    }
]

And this is my c# currently mapping to a regular string, which i would like to be an array of integers.

var AvailabilityMap = new[] { new Station() };
var data = JsonConvert.DeserializeAnonymousType(json, AvailabilityMap);

public class Station
{
    public int Id { get; set; }
    public double Lon { get; set; }
    public double Lat { get; set; }
    public int Bikes { get; set; }
    public int Slots { get; set; }
    public string Address { get; set; }
    public string NearbyStations { get; set; }
    public string Status { get; set; }
    public string Name { get; set; }
}

I have found no way so far to do this in a proper way, without looping trough my current array once more..

Matt Burland
  • 44,552
  • 18
  • 99
  • 171
Joris Brauns
  • 263
  • 1
  • 3
  • 17

3 Answers3

4

Create a custom converter. Something like this:

public class StringToIntEnumerable : JsonConverter
{

    public override bool CanConvert(Type objectType)
    {
        throw new NotImplementedException();
    }

    public override bool CanWrite
    {
        get
        {
            return false;    // we'll stick to read-only. If you want to be able 
                             // to write it isn't that much harder to do.
        }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // Note: I've skipped over a lot of error checking and trapping here
        // but you might want to add some
        var str = reader.Value.ToString();
        return str.Split(',').Select(s => int.Parse(s));
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

Now change you class to use the converter by using the JsonConverterAttribute:

public class Station
{
    public int Id { get; set; }
    public double Lon { get; set; }
    public double Lat { get; set; }
    public int Bikes { get; set; }
    public int Slots { get; set; }
    public string Address { get; set; }
    [JsonConverter(typeof(StringToIntEnumerable))]
    public IEnumerable<int> NearbyStations { get; set; }  // or List<int> or int[] if 
                                                          // you prefer, just make 
                                                          // sure the convert returns 
                                                          // the same type
    public string Status { get; set; }
    public string Name { get; set; }
}

And now to deserialize:

var stations = JsonConvert.DeserializeObject<List<Station>>(json);
Matt Burland
  • 44,552
  • 18
  • 99
  • 171
2

Here is a working fiddle that uses a custom JsonConverter.

What we're doing is converting your CSV values into a proper JSON array before we convert the entire JSON string into a Station object.

Custom JsonConverter

ReadJson reads through the JSON string. First, it loads the JSON into a JObject. Next, it gets the nearbyStations property and changes it from a simple CSV string into a JavaScript array. It does this by wrapping the CSV within square brackets. Finally, we use the JsonSerializer to populate our target object and return it.

CanWrite is set to false, because this JsonConverter is only allowed to read JSON not write to JSON. As a result, we don't need to implement WriteJson. The CanConvert tests to make sure that the target object is a Station.

public class StationConverter : JsonConverter
{
    public override object ReadJson(
        JsonReader r, Type t, object v, JsonSerializer s)
    {
        JObject jObject = JObject.Load(r);

        var prop = jObject.Property("nearbyStations");
        var wrapped = string.Format("[{0}]", prop.Value);
        JArray jsonArray = JArray.Parse(wrapped);
        prop.Value = jsonArray;

        var target = new Station();
        s.Populate(jObject.CreateReader(), target);

        return target;
    }

    public override void WriteJson(JsonWriter w, object v, JsonSerializer s)
    {
        throw new NotImplementedException("Unnecessary: CanWrite is false.");
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof (Station);
    }
}

Change the Station Class to have an int[]

For the above JsonConverter to to work, change your NearbyStations property to be an int[].

public int[] NearbyStations
{
    get;
    set;
}

Usage Example with Live Fiddle

Here is an example of how to use it:

var AvailabilityMap = 
    JsonConvert.DeserializeObject<Station[]>(json, new StationConverter());

Console.WriteLine(AvailabilityMap[0].NearbyStations[0]);
Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
1

The right answer is to have properly formatted input. In this case:

 "nearbyStations": ["3","4","5","24"]

But I was faced with a similar situation in which the data could not be updated, so a solution had to be found. The easiest is to make a getter/setter that won't be touched by the serializer/deserializer. Unfortunately you can't ignore public properties with this serializer out of the box. So you have to do some clever work-arounds like reading into a DTO and using a business object for actual operations.

public class StationDTO
{
    public int Id { get; set; }
    public double Lon { get; set; }
    public double Lat { get; set; }
    public int Bikes { get; set; }
    public int Slots { get; set; }
    public string Address { get; set; }
    public string NearbyStations { get; set; }
    public string Status { get; set; }
    public string Name { get; set; }
}

...

public class Station : StationDTO
{
    public List<string> NearbyStationsList
    { 
        get 
        {
            return NearbyStations.Split(',');
        }

        set
        {
            NearbyStations = string.Join(",", value);
        }
    }
}

More information: Newtonsoft ignore attributes?

Community
  • 1
  • 1
peewee_RotA
  • 406
  • 4
  • 8