I am using Azure Location Service to retrieve certain locations. The Json I am returned is an array of objects that are populated like this (data omitted for privacy):
{
"Id": "",
"Name": "",
"Address": "",
"PostCode": "",
"Location": {
"Latitude": 123
"Longitude": -123,
"IsEmpty": false,
"Z": null,
"M": null,
"CoordinateSystem": {
"EpsgId": 123,
"Id": "123",
"Name": "ABC"
}
}
},
I am trying to deserialize into a LookUpModel object which has a Microsoft.Spatial.Geography point for its Location:
public GeographyPoint Location { get; set; }
When I use the default newtonsoft json deserializer, I receive the following error: "Could not create an instance of type Microsoft.Spatial.GeographyPoint. Type is an interface or abstract class and cannot be instantiated. Path '[0].Location.Latitude', line 11, position 17."
Therefore I'm trying to create a custom Json Deserializer which will create a GeographyPoint (using GeographyPoint.Create(Longitude = x, Latitude = y) whenever the location node is hit. So far I have:
public class GeographyPointJsonConverter : JsonConverter
{
private readonly Type[] _types;
public GeographyPointJsonConverter(params Type[] types)
{
_types = types;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
JObject o = (JObject)t;
IList<string> propertyNames = o.Properties().Select(p => p.Name).ToList();
o.AddFirst(new JProperty("Keys", new JArray(propertyNames)));
o.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override bool CanRead
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return _types.Any(t => t == typeof(GeographyPoint));
}
}
being called with: var jsonData = JsonConvert.DeserializeObject<LookUpModel[]>(json, new GeographyPointJsonConverter(typeof(LookUpModel[])));
However I cannot work out how I can successfully deserialize the Location information into a GeographyPoint? Is anyone able to point me in the right direction here please?
You class will be look like this
public partial class MyClass
{
[JsonProperty("Id")]
public string Id { get; set; }
[JsonProperty("Name")]
public string Name { get; set; }
[JsonProperty("Address")]
public string Address { get; set; }
[JsonProperty("PostCode")]
public string PostCode { get; set; }
[JsonProperty("Location")]
public Location Location { get; set; }
}
public partial class Location
{
[JsonProperty("Latitude")]
public long Latitude { get; set; }
[JsonProperty("Longitude")]
public long Longitude { get; set; }
[JsonProperty("IsEmpty")]
public bool IsEmpty { get; set; }
[JsonProperty("Z")]
public object Z { get; set; }
[JsonProperty("M")]
public object M { get; set; }
[JsonProperty("CoordinateSystem")]
public CoordinateSystem CoordinateSystem { get; set; }
}
public partial class CoordinateSystem
{
[JsonProperty("EpsgId")]
public long EpsgId { get; set; }
[JsonProperty("Id")]
[JsonConverter(typeof(ParseStringConverter))]
public long Id { get; set; }
[JsonProperty("Name")]
public string Name { get; set; }
}
In your Json, comma (,) is missing after
"Latitude": 123
Deserialize you json into object as
MyClass obj = JsonConvert.DeserializeObject<MyClass>(json);
Related
I'm currently dealing with getting data from an external API. The data I receive looks something like what is shown below. (Just a mockup; don't expect the values to make any sense. It's just to illustrate what kind of data I get.)
{
"user": [
{
"key": "12345678",
"data": [
{
"id": "Name",
"string": "Bob"
},
{
"id": "ElapsedTimeSinceLastMessage",
"timestamp": 1618233964000
},
{
"id": "Age",
"number": 27
}
]
}
]
}
I don't really know how I should be going about deserializing this JSON.
The classes I'm using to deserialize right now look like this:
public class User
{
[JsonProperty("key")]
public string Key { get; set; }
[JsonProperty("data")]
public List<DataEntry> DataEntries { get; set; }
}
public class DataEntry
{
[JsonProperty("id")]
public string Id { get; set; }
public Type Value { get; set; }
}
And I don't know what I need to set in order to deserialize the Value inside the DataEntry. Maybe someone can guide me into the right direction?
The Data part of this JSON is really just a Dictionary<string, object> in disguise. You can use a custom JsonConverter to transform the list of id/value pairs into that format for easy use.
Frist, define these classes:
class RootObject
{
[JsonProperty("user")]
public List<User> Users { get; set; }
}
class User
{
public string Key { get; set; }
[JsonConverter(typeof(CustomDataConverter))]
public Dictionary<string, object> Data { get; set; }
}
Next, define the converter:
class CustomDataConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Dictionary<string, object>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return JToken.Load(reader)
.Children<JObject>()
.ToDictionary(jo => (string)jo["id"],
jo => jo.Properties()
.Where(jp => jp.Name != "id" && jp.Value is JValue)
.Select(jp => ((JValue)jp.Value).Value)
.FirstOrDefault());
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
You can then deserialize and dump out the data like this:
var root = JsonConvert.DeserializeObject<RootObject>(json);
foreach (User user in root.Users)
{
Console.WriteLine("User Key: " + user.Key);
foreach (var kvp in user.Data)
{
Console.WriteLine(kvp.Key + ": " + kvp.Value);
}
}
Here is a working demo: https://dotnetfiddle.net/GIT4dl
One angle of attack would be with Dictionaries:
public class WithUser
{
public List<User> User { get; set; }
}
public class User
{
[JsonProperty("key")]
public string Key { get; set; }
[JsonProperty("data")]
public List<Dictionary<string,object>> DataEntries { get; set; }
}
The extraction is a bit of a pain but possible:
public static void Main()
{
var json = File.ReadAllText("Example.json");
var x = JsonConvert.DeserializeObject<WithUser>(json);
var user = x.User.Single();
var age = Extract<long>(user, "Age");
var name = Extract<string>(user, "Name");
var elapsedTimeSinceLastMessage = TimeSpan.FromTicks(Extract<long>(user, "ElapsedTimeSinceLastMessage"));
}
public static T Extract<T>(User user, string name)
{
var o = user.DataEntries
.SingleOrDefault(d => (string)d["id"] == name) // Find the one with age
.SingleOrDefault(kvp => kvp.Key != "id") // Find the not 'id' value
.Value; // Take the value
return (T)o;
}
The main issue is in the models that you created.
First of all, base on the JSON, you need another class that contains a list of Users.
public class ResultClass
{
public List<User> User { get; set; }
}
After that, because the second object of data property has not a constant name, we can't specify a constant name for it (like value). We should define the data property as an object. So the user class should be like this:
public class User
{
[JsonProperty("key")]
public string Key { get; set; }
[JsonProperty("data")]
public List<object> DataEntries { get; set; }
}
In the end, in the controller, you should deserialize the ResultJson class:
var result = JsonConvert.DeserializeObject<ResultClass>(jsonTxt);
You can use Json to C#. It generates following classes from your json string. As you can see, you can also use nullable types(long?, int?). If there is a value, sets the required variable. Otherwise leaves it as null. In this way, you can get your different type according to id of data.
public class DataEntry
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("string")]
public string String { get; set; }
[JsonProperty("timestamp")]
public long? Timestamp { get; set; }
[JsonProperty("number")]
public int? Number { get; set; }
}
public class User
{
[JsonProperty("key")]
public string Key { get; set; }
[JsonProperty("data")]
public List<DataEntry> Data { get; set; }
}
public class Root
{
public List<User> User { get; set; }
}
To Deserialize:
string response = "{\"user\":[{\"key\":\"12345678\",\"data\":[{\"id\":\"Name\",\"string\":\"Bob\"},{\"id\":\"ElapsedTimeSinceLastMessage\",\"timestamp\":1618233964000},{\"id\":\"Age\",\"number\":27}]}]}";
Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(response);
I got a JSON response like:
{
"items": [
{
"document": {
"id": "123",
"title": "title2",
"description": "Description1",
"type": "Typ1"
}
},
{
"document": {
"id": "456",
"title": "title2",
"description": "desctiption2",
"type": "Type2",
"Type2Property": "Type2Property"
}
}
]
}
As you can see above I have two values (just for example) with different properties.
In my code, I have two classes.
public class Type1
{
public string Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Type { get; set; }
}
public class Type2
{
public string Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Type { get; set; }
public string Type2Property {get; set;}
}
Question: How can I create one generic list which combines Type1 and Type2. In the future, I can have more TypeX (with different properties). So, I'd like to parse JSON into a generic list.
Update: I can filter json by the Type property from the JSON.
One way to solve this problem is to create a custom JsonConverter and override its ReadJson method.
I've introduced a couple of helper classes to be able to parse the whole sample json:
public class TopLevel
{
public MidLevel[] Items { get; set; }
}
public class MidLevel
{
public IDocument Document { get; set; }
}
[JsonConverter(typeof(DocumentTypeConverter))]
public interface IDocument
{
}
I've created an IDocument marker interface. If you wish you can use abstract class.
I've decorated the interface with a JsonConverterAttribute and specified there the custom converter.
I've changed the Type1 and Type2 classes to implement this interface:
public class Type1 : IDocument
{
...
public string Type { get; set; }
}
public class Type2 : IDocument
{
...
public string Type { get; set; }
public string Type2Property { get; set; }
}
The DocumentTypeConverter naive implementation would look like this:
(Obviously you can make more type-safe)
public class DocumentTypeConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
=> throw new NotImplementedException();
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
switch (jObject["type"].Value<string>())
{
case "Typ1":
{
var obj = new Type1();
serializer.Populate(jObject.CreateReader(), obj);
return obj;
}
case "Type2":
{
var obj = new Type2();
serializer.Populate(jObject.CreateReader(), obj);
return obj;
}
default:
throw new Exception();
}
}
public override bool CanConvert(Type objectType)
=> objectType == typeof(IDocument);
}
The CanConvert tells us that this convert can be used against IDocuments.
The ReadJson branches its logic based on the "type" field.
The actual conversion done with the Populate instead of JsonCovert.Deserialize to avoid infinite recursion.
Finally, the usage is that simple:
static void Main(string[] args)
{
var sampleJson = File.ReadAllText("sample.json");
var sample = JsonConvert.DeserializeObject<TopLevel>(sampleJson);
Console.ReadLine();
}
what I want to do is be able to deserialize any IBaseModel and it will recursively set all IBaseModel to either Safe or Unsafe depending on what is making the call. I am currently using Newtonsoft but would be happy to switch to System.Text.Json if it will achieve the desired results.
I have the following structure:
public enum RequestPermissionType
{
Safe = 1,
Unsafe = 2,
}
public interface IBaseModel {
public long Id { get; set; }
public RequestPermissionType PermissionType { get; set; }
}
public class User : IBaseModel {
public long Id { get; set; }
public RequestPermissionType PermissionType { get; set; }
public string Name { get; set; }
public Address HomeAddress { get; set; }
}
public class Address: IBaseModel {
public long Id { get; set; }
public RequestPermissionType PermissionType { get; set; }
public string street { get; set; }
}
I tried creating a JsonConverter but I still want deserialize everything normally I just want to set either so when I override ReadJson i think it keeps calling deserialize using the same converter:
public override IBaseEntity ReadJson(JsonReader reader, Type objectType, [AllowNull] IBaseEntity existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
{
var result = serializer.Deserialize<IBaseEntity >(reader);
result.PermissionType = _requestPermissionType;
return result;
}
I also tried using a converter that extended CustomCreationConverter, which works but I am trying to avoid using the activator:
public class PermissionTypeConverter : CustomCreationConverter<IBaseEntity>
{
private RequestPermissionType _requestPermissionType = RequestPermissionType.Unsafe;
public PermissionTypeConverter()
{
_requestPermissionType = RequestPermissionType.Unsafe;
}
public PermissionTypeConverter(RequestPermissionType requestPermissionType)
{
_requestPermissionType = requestPermissionType;
}
public override IBaseEntity Create(Type objectType)
{
var result = Activator.CreateInstance(objectType);
(result as IBaseEntity).PermissionType = _requestPermissionType;
return result as IBaseEntity;
}
}
Edit: Adding Json data example.
[
{
"id": 1,
"name": "User 1",
"homeAddress": {
"id": 5,
"street": "Fake Street 1"
}
},
{
"id": 2,
"name": "User 2",
"homeAddress": {
"id": 6,
"street": "Fake Street 2"
}
}
]
I have a list of items in json
"items":[
{
"id": 0,
"name": "Thats a name",
"type": 0,
"price": 3.5,
"ingredients": [1,0,2,3],
},
{
"id": 1,
"name": "This is AnotherName",
"type": 0,
"price": 3.7,
"ingredients": [5,0,6,10,2,8],
}
]
The type and ingredients properties are detailed in another object of the same JSON file. If I look it up, I know what a type 0 is, and what the ingredients are.
What I'm trying to achieve, in c#, is to have my data model not having int everywhere, but having the actual objects. For example, with the ingredients, my Item object has an Ingredients property of type List<Ingredient> and not List<int>.
Like the following :
public IEnumerable<Ingredient> Ingredients { get; set; }
public IEnumerable<FoodType> Types { get; set; }
public IEnumerable<FoodItem> Items { get; set; }
public class FoodItem
{
public int Id { get; set; }
public string Name { get; set; }
public int Type { get; set; }
public float Price { get; set; }
public IEnumerable<Ingredient> Ingredients { get; set; }
}
But in the current state of my deserialization, it crashes because it's looking for an int.
I've found keywords but not real help, about "PreserveReferenceHandling" or "isReference" but I'm not sure what those are and even less how to use them.
This is how I deserialize :
var json = r.ReadToEnd();
var items = JsonConvert.DeserializeObject<EatupDataModel>(json);
I know the following would work :
Change the json file to include actual objects and not ID's
Change data model to use int's and not objects
But I would very much like not to go that way, the first one requiring an insane amount of tedious work, and the other one forcing me to have 2 versions of pretty much the same objects, and then map the properties in between. That seems silly, surely I can't be the first person to face this.
What can I do to achieve my goal ?
You will want to clean this up a bit. But should give you a proof of concept on how to do create your custom converter.
public class Item
{
public int id { get; set; }
public string name { get; set; }
public int type { get; set; }
public double price { get; set; }
[JsonConverter(typeof(KeysJsonConverter))]
public List<Ingredient> ingredients { get; set; }
}
public class RootObject
{
public List<Item> items { get; set; }
}
public class KeysJsonConverter : JsonConverter
{
public KeysJsonConverter()
{
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanWrite is false. The type will skip the converter.");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var ingredientsList = new List<Ingredient>();
if (reader.TokenType != JsonToken.Null)
{
if (reader.TokenType == JsonToken.StartArray)
{
JToken token = JToken.Load(reader);
List<int> items = token.ToObject<List<int>>();
ingredientsList = items.Select(x => IngredientList.Ingredients.FirstOrDefault(y => y.Id == x)).ToList();
}
}
return ingredientsList;
}
public override bool CanRead
{
get { return true; }
}
public override bool CanWrite
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(object[]);
}
}
public static class IngredientList
{
public static List<Ingredient> Ingredients = new List<Ingredient>()
{
new Ingredient()
{
Id = 1,
Name = "Test 1"
},
new Ingredient()
{
Id = 2,
Name = "Test 2"
}
};
}
public class Ingredient{
public string Name { get; set; }
public int Id { get; set; }
}
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..
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);
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]);
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?