If I have this JSON where there is a header with the tags version
, generator
, om3s
and elements
. elements
can be of type node
or way
and the associated JSON keys vary based on type. I am trying to use JsonSubTypes to convert each element type to a C# class.
Example JSON:
[
{
"version": 0.6,
"generator": "Overpass API 0.7.55.7 8b86ff77",
"osm3s": {
"timestamp_osm_base": "2019-05-21T18:03:02Z",
"copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL."
},
"elements": [
{
"type": "node",
"id": 4949106384,
"lat": 32.2686857,
"lon": -107.738218,
"tags": {
"highway": "turning_circle"
}
},
{
"type": "way",
"id": 14527404,
"nodes": [
142882281,
3048075541,
1598998260
],
"tags": {
"highway": "residential",
"name": "West Apple Street",
"tiger:cfcc": "A41",
"tiger:county": "Luna, NM",
"tiger:name_base": "Apple",
"tiger:name_direction_prefix": "W",
"tiger:name_type": "St",
"tiger:reviewed": "no"
}
}
]
}
]
I am trying to deserialize it using:
var json = JsonConvert.DeserializeObject<OSMdata>(jsonText);
Where OSMdata
looks like:
[JsonConverter(typeof(JsonSubtypes), "type")]
[JsonSubtypes.KnownSubType(typeof(Element.Node), "node")]
[JsonSubtypes.KnownSubType(typeof(Element.Edge), "way")]
public abstract class OSMdata
{
public float version { get; set; }
public string generator { get; set; }
public Osm3s osm3s { get; set; }
public Element[] elements { get; set; }
}
public class Osm3s : OSMdata
{
public DateTime timestamp_osm_base { get; set; }
public string copyright { get; set; }
}
public class Element : OSMdata
{
public class Node : Element
{
public string type { get; } = "node";
public long id { get; set; }
public float lat { get; set; }
public float lon { get; set; }
public NodeTags tags { get; set; }
}
public class NodeTags : Node
{
public string highway { get; set; }
public string _ref { get; set; }
}
public class Edge : Element
{
public string type { get; } = "way";
public long id { get; set; }
public long[] nodes { get; set; }
public EdgeTags tags { get; set; }
}
public class EdgeTags : Edge
{
public string highway { get; set; }
public string name { get; set; }
public string cfcc { get; set; }
public string county { get; set; }
public string oneway { get; set; }
}
}
Which returns:
Unhandled Exception: System.ArgumentNullException: Value cannot be null.
at System.RuntimeType.MakeGenericType(Type[] instantiation)
at JsonSubTypes.JsonSubtypes.CreateCompatibleList(Type targetContainerType, Type elementType)
at JsonSubTypes.JsonSubtypes.ReadArray(JsonReader reader, Type targetType, JsonSerializer serializer)
at JsonSubTypes.JsonSubtypes.ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.DeserializeConvertable(JsonConverter converter, JsonReader reader, Type objectType, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings)
at newapp.Program.Main(String[] args) in C:\Users\RDCRLDDH\source\repos\newapp\newapp\Program.cs:line 23
While I don't understand this error and am looking for a solution, here are a few questions I would like clarity on:
Am I properly constructing the class OSMdata
? I think I am following the examples correctly, but am not sure if I am properly assigning the classes Node
and Edge
to the parent class OSMdata
.
How will the deserializer know to assign the tag "tiger:cfcc"
to the Cfcc
property in EdgeTags
?
I couldn't figure out how to deserialize directly from the JSON, but found a suitable workaround that converts the JSON to JArray
and iterates though each element and converting to a C# class that is composed of nodes and edges.
Using the class Element
:
[JsonConverter(typeof(JsonSubtypes), "type")]
[JsonSubtypes.KnownSubType(typeof(Element.Node), "node")]
[JsonSubtypes.KnownSubType(typeof(Element.Edge), "way")]
public class Element
{
public class Node : Element
{
public string type { get; } = "node";
public long id { get; set; }
public float lat { get; set; }
public float lon { get; set; }
public NodeTags tags { get; set; }
}
public class NodeTags : Node
{
public string highway { get; set; }
public string _ref { get; set; }
}
public class Edge : Element
{
public string type { get; } = "way";
public long id { get; set; }
public long[] nodes { get; set; }
public EdgeTags tags { get; set; }
}
public class EdgeTags : Edge
{
[JsonProperty("highway")]
public string Highway { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("tiger:cfcc")]
public string cfcc { get; set; }
[JsonProperty("tiger:county")]
public string County { get; set; }
[JsonProperty("oneway")]
public string Oneway { get; set; }
}
You can parse thje json using:
JArray jsonSearch = JArray.Parse(jsonText);
You can then create a list consisting of each element using:
IList<JToken> results = jsonSearch[0]["elements"].Children().ToList();
Then you can iterate through results
and convert the data to C# objects using:
var element_list = new List<Element>();
foreach (JObject element in results)
{
Element myelement = element.ToObject<Element>();
element_list.Add(myelement);
}
Of the questions I posed, the second is still relevant.
You can assign a property with an invalid name to a C# class using JsonProperty
before you create the property name within the class.
[JsonProperty("tiger:cfcc")]
public string cfcc { get; set; }
Thanks @dbc and other helpful comments for pointing me in the right direction!