3

そのため、WCF/REST API で GeoJSON を返すことを簡単にサポートできるように、GeoJSON を C# DataContracts にマップしようとしました。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 サービスで使用するときにも発生します。何か案は?これを逆シリアル化する際に問題が発生するかどうか、またはこのアイデアがまったく合理的かどうかについてのヒントはありますか?

4

1 に答える 1

0

I can say definitively that there is no way to get around the __type emission, unless you don't serialize the type in polymorphic scenarios.

One possible solution is create some type of wrapper operation that won't invoke polymorphism, and return the object via that operation instead of via the poly method.

The JSON serializer does have a flag called alwaysEmitTypeInformation, but it's something you turn ON to always emit __type. There is now way to turn it off, mainly to avoid unintentional user errors.

See also my answer here: Rename __type-field in WCF services

于 2012-02-22T16:16:52.577 に答える