使用C#反序列化Open Street Map JSON

3

如果我有这个JSON,其中有一个带有标记的头部versiongeneratorom3selements。其中elements可以是node或者way类型,相应的JSON键因类型而异。我正在尝试使用JsonSubTypes将每个元素类型转换为C#类。

示例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"
        }
      }
    ]
  }
]

我正在尝试使用以下方式进行反序列化:

var json = JsonConvert.DeserializeObject<OSMdata>(jsonText);

“OSMdata”的格式如下:

[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; }
    }
}

返回:

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

虽然我不明白这个错误是什么,并正在寻找解决方案,但以下是我想要澄清的一些问题:

问题

我是否正确构造了类OSMdata? 我认为我正在正确地遵循示例,但不确定是否正确地将类NodeEdge分配给父类OSMdata

反序列化程序将如何知道将标签"tiger:cfcc"分配给EdgeTags中的Cfcc 属性?


1
另一种解决方案是添加一个 NodeOrEdge 类,并编写它们之间的自定义转换。让库处理缺失的键。 - SGKoishi
2
与其加载到一些巨大的“动态”对象,然后手动反序列化,不如使用多态的“元素”数组将其反序列化为模型。有关详细信息,请参见Json.Net Serialization of Type with Polymorphic Child ObjectHow to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?Deserializing polymorphic json classes without type information using json.net - dbc
@dbc 感谢阅读! - dubbbdan
1
每当你使用 dynamic 关键字时,一定要自问是否有更好的方法。 - TheGeneral
@dubbbdan - 不用谢。这些回答解决了你的问题吗?如果没有,你可以使用JToken.ToObject<NodeTags>()JToken.ToObject<Edge>(),如将JToken内容反序列化为对象将JToken(或字符串)转换为给定类型所示。希望这些其他问题中的一个能够解决你的问题。 - dbc
2个回答

1
将你的类声明如下:

    // no longer abstract
    public class OSMdata
    {
        public float version { get; set; }
        public string generator { get; set; }
        public Osm3s osm3s { get; set; }

        // for arrays or collection this line must be present here
        [JsonConverter(typeof(JsonSubtypes), "type")]
        public Element[] elements { get; set; }
    }

    // no need to inherits from OSMData
    public class Osm3s
    {
        public DateTime timestamp_osm_base { get; set; }
        public string copyright { get; set; }
    }


    [JsonConverter(typeof(JsonSubtypes), "type")]
    [JsonSubtypes.KnownSubType(typeof(Node), "node")]
    [JsonSubtypes.KnownSubType(typeof(Edge), "way")]
    public abstract class Element : OSMdata
    {
        public abstract string type { get; }
    }

    public class Node : Element
    {
        public override 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
    {
        public string highway { get; set; }
        public string _ref { get; set; }
    }

    public class Edge : Element
    {
        public override string type { get; } = "way";
        public long id { get; set; }
        public long[] nodes { get; set; }
        public EdgeTags tags { get; set; }
    }

    public class EdgeTags
    {
        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; }
    }

并使用以下方法进行反序列化:

var json = JsonConvert.DeserializeObject<OSMdata>(jsonText);

查看运行示例:https://dotnetfiddle.net/pdJ0ab


EdgeTagsEdge 相关联是否合适(即 public class EdgeTags : Edge)? - dubbbdan

0
我找不到直接从JSON反序列化的方法,但找到了一个适当的解决方法,将JSON转换为JArray并遍历每个元素,并将其转换为由节点和边组成的C#类。
使用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; }
    }

你可以使用以下方式解析json:

JArray jsonSearch = JArray.Parse(jsonText);

然后,您可以使用以下代码创建由每个元素组成的列表:

IList<JToken> results = jsonSearch[0]["elements"].Children().ToList();

然后您可以通过results进行迭代,并使用以下方式将数据转换为C#对象:

var element_list = new List<Element>();

foreach (JObject element in results)
{
    Element myelement = element.ToObject<Element>();
    element_list.Add(myelement);
}

在我提出的问题中,第二个问题仍然相关。

您可以在创建类内属性名称之前使用 JsonProperty 将具有无效名称的属性分配给 C# 类。

[JsonProperty("tiger:cfcc")]
public string cfcc { get; set; }

感谢@dbc和其他有帮助的评论,指引我朝着正确的方向前进!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接