Является ли C # DataContracts достаточно выразительным, чтобы правильно описать GeoJSON? - PullRequest
3 голосов
/ 17 февраля 2012

Итак, я пытался отобразить GeoJSON в C # DataContracts, чтобы мы могли легко поддерживать возвращение GeoJSON в нашем WCF / REST-apis.Чем больше я работаю с DataContracts и сериализацией, тем больше у меня возникает ощущение, что это черный ящик магии, который я не могу понять.

Как бы то ни было, я дошел до:

GeoJSON.cs:

namespace GeoJSON {

[DataContract]
[KnownType(typeof (Point))]
[KnownType(typeof (MultiPoint))]
[KnownType(typeof (LineString))]
[KnownType(typeof (MultiLineString))]
[KnownType(typeof (Polygon))]
[KnownType(typeof (MultiPolygon))]
[KnownType(typeof (Feature))]
[KnownType(typeof (FeatureCollection))]
[KnownType(typeof (Geometry))]
[KnownType(typeof(GeometryCollection))]
public abstract class GeoJson {

    // This is because WCF services can't read the DataContracts KnownType's above
    // so you need to break DRY and repeat yourself here and add this class
    // to a ServiceKnownType-attribute to every service method returning GeoJSON
    // Like this:
    // [ServiceKnownType("Types", typeof(GeoJson.KnownTypes))]
    public static class KnownTypes {
        public static Type[] Types(ICustomAttributeProvider provider) {
            return new[] {
                typeof (Point),
                typeof (MultiPoint),
                typeof (LineString),
                typeof (MultiLineString),
                typeof (Polygon),
                typeof (MultiPolygon),
                typeof (Feature),
                typeof (FeatureCollection),
                typeof (Geometry),
                typeof (GeometryCollection)
            };
        }
    }
}

#region Feature

// The idea here (and elsewhere) is that if i let the type field be enums the 
// deserializer might use that information to chose the right class. 
// Is this just crack?
[DataContract]
public enum FeatureType {
    [EnumMember] Feature
}

public class Feature : GeoJson {
    public Feature() {
        Type = FeatureType.Feature;
        Properties = new Dictionary<string, string>();
    }

    public Feature(Geometry geometry) {
        Type = FeatureType.Feature;
        Properties = new Dictionary<string, string>();
        Geometry = geometry;
    }

    [DataMember(Name = "type")]
    public FeatureType Type { get; set; }

    [DataMember(Name = "id")]
    public string Id { get; set; }

    [DataMember(IsRequired = true, Name = "geometry")]
    public Geometry Geometry { get; set; }

    // TODO: Make this support proper JSON-trees
    [DataMember(IsRequired = true, Name = "properties")]
    public IDictionary<string, string> Properties { get; set; }


}

#endregion Feature

#region FeatureCollection

[DataContract]
public enum FeatureCollectionType {
    [EnumMember]
    FeatureCollection
}

[DataContract(Name = "FeatureCollection")]
public class FeatureCollection : GeoJson {
    public FeatureCollection() {
        Type = FeatureCollectionType.FeatureCollection;
        Features = new Feature[0];
    }

    public FeatureCollection(params Feature[] features) {
        Type = FeatureCollectionType.FeatureCollection;
        Features = features ?? new Feature[0];
    }

    [DataMember(Name = "type")]
    private FeatureCollectionType Type { get; set; }

    [DataMember(Name = "features", IsRequired = true)]
    private Feature[] Features { get; set; }
}

#endregion FeatureCollection

#region GeometryCollection

[DataContract]
public enum GeometryCollectionType {
    [EnumMember] GeometryCollection
}

[DataContract(Name = "GeometryCollection")]
public class GeometryCollection : GeoJson {
    public GeometryCollection() {
        Type = GeometryCollectionType.GeometryCollection;
        Geometries = new Geometry[0];
    }

    public GeometryCollection(params Geometry[] geometries) {
        Type = GeometryCollectionType.GeometryCollection;
        Geometries = geometries ?? new Geometry[0];
    }

    [DataMember(Name = "type")]
    private GeometryCollectionType Type { get; set; }

    [DataMember(Name = "geometries", IsRequired = true)]
    private Geometry[] Geometries { get; set; }
}

#endregion GeometryCollection


#region Geometry

/* TODO: 
 *   - More constructors
 *   - Enforce some more invariants.
 */

[DataContract]
[KnownType(typeof(Point))]
[KnownType(typeof(MultiPoint))]
[KnownType(typeof(LineString))]
[KnownType(typeof(MultiLineString))]
[KnownType(typeof(Polygon))]
[KnownType(typeof(MultiPolygon))]
public abstract class Geometry : GeoJson { }

[DataContract]
public enum PointType {
    [EnumMember(Value="Point")] Point
}
[DataContract]
public class Point : Geometry {
    public Point() {
        Type = PointType.Point;
    }

    [DataMember(Name = "type", Order = 0)]
    public PointType Type { get; set; }

    [DataMember(Name = "coordinates", Order = 1)]
    public double[] Coordinates { get; set; }
}

[DataContract]
public enum MultiPointType {
    [EnumMember(Value = "MultiPoint")] MultiPoint
}
[DataContract]
public class MultiPoint : Geometry {
    public MultiPoint() {
        Type = MultiPointType.MultiPoint;
    }

    [DataMember(Name = "type", Order = 0)]
    public MultiPointType Type { get; set; }

    [DataMember(Name = "coordinates", Order = 1)]
    public double[][] Coordinates { get; set; }
}

[DataContract]
public enum LineStringType {
    [EnumMember] LineString
}
[DataContract]
public class LineString : Geometry {
    public LineString() {
        Type = LineStringType.LineString;
    }

    [DataMember(Name = "type", Order = 0)]
    public LineStringType Type { get; set; }

    [DataMember(Name = "coordinates", Order = 1)]
    public double[] Coordinates { get; set; }
}

[DataContract]
public enum MultiLineStringType {
    [EnumMember] MultiLineString
}
[DataContract]
public class MultiLineString : Geometry {
    public MultiLineString() {
        Type = MultiLineStringType.MultiLineString;
    }

    [DataMember(Name = "type", Order = 0)]
    public MultiLineStringType Type { get; set; }

    [DataMember(Name = "coordinates", Order = 1)]
    public double[][] Coordinates { get; set; }
}

[DataContract]
public enum PolygonType {
    [EnumMember] Polygon
}
[DataContract]
public class Polygon : Geometry {
    public Polygon() {
        Type = PolygonType.Polygon;
    }

    [DataMember(Name = "type", Order = 0)]
    public PolygonType Type { get; set; }

    [DataMember(Name = "coordinates", Order = 1)]
    public double[][] Coordinates { get; set; }
}

[DataContract]
public enum MultiPolygonType {
    [EnumMember] MultiPolygon
}
[DataContract]
public class MultiPolygon : Geometry {
    public MultiPolygon() {
        Type = MultiPolygonType.MultiPolygon;
    }

    [DataMember(Name = "type", Order = 0)]
    public MultiPolygonType Type { get; set; }

    [DataMember(Name = "coordinates", Order = 1)]
    public double[][][] Coordinates { get; set; }

}

#endregion Geometry 
}

Program.cs:

namespace Test {
internal class Program {
    private static void Main(string[] args) {
        var linestring = new LineString {
            Coordinates = new[] {12.0, 57.1, 13, 14}
        };

        var point = new Point {
            Coordinates = new[] { 12.0, 57.1 }
        };

        var geometryCollection = new GeometryCollection(point, linestring);

        var feature = new Feature(linestring);

        var featureCollection = new FeatureCollection(
            feature,
            new Feature(point)
        );


        try {
            WriteObject("LineString", linestring);
            WriteObject("Point", point);
            WriteObject("GeometryCollection",geometryCollection);
            WriteObject("Feature", feature);
            WriteObject("FeatureCollection", featureCollection);
        }
        catch (Exception ex) {
            Console.WriteLine("An exception occured: " + ex.Message);
        }
        Console.ReadKey();
    }

    public static void WriteObject(string label, GeoJson json) {
        var stream = new MemoryStream();
        var ser = new DataContractJsonSerializer(json.GetType());
        ser.WriteObject(stream, json);
        stream.Position = 0;
        var sr = new StreamReader(stream);
        Console.Write("\n" + label + ":\n\t");
        Console.WriteLine(sr.ReadToEnd());
    }
}
}

Вывод программы:

LineString:
    {"type":0,"coordinates":[12,57.1,13,14]}

Point:
    {"type":0,"coordinates":[12,57.1]}

GeometryCollection:
    {"geometries":[{"__type":"Point:#GeoJSON","type":0,"coordinates":[12,57.1]},{"__type":"LineString:#GeoJSON","type":0,"coordinates":[12,57.1,13,14]}],"type":0}

Feature:
    {"geometry":{"__type":"LineString:#GeoJSON","type":0,"coordinates":[12,57.1,13,14]},"id":null,"properties":[],"type":0}

FeatureCollection:
    {"features":[{"geometry":{"__type":"LineString:#GeoJSON","type":0,"coordinates":[12,57.1,13,14]},"id":null,"properties":[],"type":0},{"geometry":{"__type":"Point:#GeoJSON","type":0,"coordinates":[12,57.1]},"id":null,"properties":[], "type":0}],"type":0}

Проблемы

  1. Я получаю эти __type-теги, разбросанные вокруг
  2. Поля типов сериализуются в «0» вместо их фактических имен.

Я получаю эти проблемы также при использовании их в WCFСервисы.Есть идеи?Любой намек на то, будут ли у меня проблемы с десериализацией этого или эта идея вообще разумна?

1 Ответ

0 голосов
/ 22 февраля 2012

Я могу однозначно сказать, что нет способа обойти эмиссию __type, если вы не сериализуете тип в полиморфных сценариях.

Одним из возможных решений является создание некоторого типа операции-оболочки, которая не будет вызывать полиморфизм, и возврат объекта с помощью этой операции, а не с помощью метода poly.

Сериализатор JSON имеет флаг AlwaysEmitTypeInformation, но вы включаете его, чтобы всегда выдавать __type. Теперь есть способ отключить его, главным образом, чтобы избежать непреднамеренных ошибок пользователя.

См. Также мой ответ здесь: Переименовать __type-field в службах WCF

...