artiya4u / google-http-java-client

Automatically exported from code.google.com/p/google-http-java-client
0 stars 0 forks source link

Support for heterogeneous schemas #173

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago
External references, such as a standards document, or specification?

http://www.geojson.org/geojson-spec.html

Java environments (e.g. Java 6, Android 2.3, App Engine, or All)?

All

Please describe the feature requested.

How can we elegantly support GeoJSON?  Example from the spec:

{ "type": "FeatureCollection",
  "features": [
    { "type": "Feature",
      "geometry": {"type": "Point", "coordinates": [102.0, 0.5]},
      "properties": {"prop0": "value0"}
      },
    { "type": "Feature",
      "geometry": {
        "type": "LineString",
        "coordinates": [
          [102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]
          ]
        },
      "properties": {
        "prop0": "value0",
        "prop1": 0.0
        }
      },
    { "type": "Feature",
       "geometry": {
         "type": "Polygon",
         "coordinates": [
           [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],
             [100.0, 1.0], [100.0, 0.0] ]
           ]
       },
       "properties": {
         "prop0": "value0",
         "prop1": {"this": "that"}
         }
       }
     ]
   }

Note that "type" is case sensitive and can be any of "Point", "MultiPoint", 
"LineString", "MultiLineString", "Polygon", "MultiPolygon", 
"GeometryCollection", "Feature", or "FeatureCollection".  Each type corresponds 
to a different schema.  For example for Geometry "coordinates" JSON key has a 
different type for different geometries.  For example, for Point it may be 
represented as List<BigDecimal>, for LineString it may be represented as 
List<List<BigDecimal>>, and for Polygon it may be represented as 
List<List<List<BigDecimal>>>.

What you'd have to do today is to merge the all into something like:

class GeoJsonObject extends GenericJson {
  @Key String type;
  @Key List<Object> coordinates;
  @Key Geometry geometry;
  @Key Map<String, Object> properties;
}

Note for example how coordinates has to be List<Object> and in usage it would 
have to be cast to the appropriate type.

But really we'd ideally like something like 

class GeoJsonObject extends GenericJson {
  @Key String type;
}

class Feature extends GeoJsonObject {
  @Key Geometry geometry;
  @Key Map<String, Object> properties;
}

class Geometry extends GeoJsonObject {
  @Key Object coordinates;
}

public class Point extends Geometry {
  @Key List<BigDecimal> coordinates;
}

public class Point extends Geometry {
  @Key List<BigDecimal> coordinates;
}

public class LineString extends Geometry {
  @Key List<List<BigDecimal>> coordinates;
}

public class Polygon extends Geometry {
  @Key List<List<List<BigDecimal>>> coordinates;
}

But the question is how do we cause it to be parsed into the appropriate Java 
Type based on the "type" JSON key.

One option is we could enable some interface like:

class GeoJsonObject extends GenericJson implements JsonMultiType {
...
  Type chooseType(ArrayMap map) {
    switch(map.get("type")) {
      case "Feature": return Feature.class;
      case "Point": return Point.class;
      case "LineString": return LineString.class;
      case "Polygon": return Polygon.class;
...
  }
}

and then internally we would parse it into an ArrayMap first, and then reparse 
it into the given type afterwards.

Another option is to generalize it a bit more using a post-process step, e.g.:

class GeoJsonObject extends GenericJson implements PostProcess {
...
  GeoJsonObject postProcess(ArrayMap map) {
    switch(map.get("type")) {
      case "Feature": return parseAs(map, Feature.class);
      case "Point": return parseAs(map, Point.class);
      case "LineString": return parseAs(map, LineString.class);
      case "Polygon": return parseAs(map, Polygon.class);
...
  }
}

and we'd have to provide this new "parseAs" method.

Of course I'm open to other ideas as well, so feel free to suggest other 
approaches.

Original issue reported on code.google.com by yan...@google.com on 13 Dec 2012 at 2:11

GoogleCodeExporter commented 9 years ago

Original comment by yan...@google.com on 10 Jan 2013 at 12:30

GoogleCodeExporter commented 9 years ago
Some initial thoughts:

Should LineString be @Key List<List<BigDecimal>> coordinates or @Key 
List<Point> coordinates?

How would you initialize a Polygon? Does Java have a simple initializer for 
Lists of Lists of Lists?

Original comment by jmcg...@google.com on 10 Jan 2013 at 4:47

GoogleCodeExporter commented 9 years ago
James, I think those are good questions.  But I hadn't intended for that to be 
the purpose of this feature request.  So if you don't mind let me narrow the 
focus of this feature request to support for heterogeneous schemas, meaning 
schemas for whom there is a choice of which schema to use based on the value of 
a single top-level property.  GeoJson is a good example of that, but in our 
implementation we will suppose that other use cases exist, so we will implement 
it to work for the most general use cases.

I think it may be worthwhile to look at other JSON <-> Java mapping libraries 
like Jackson or GSON and see how they implement this.

Original comment by yan...@google.com on 12 Jan 2013 at 3:12

GoogleCodeExporter commented 9 years ago

Original comment by yan...@google.com on 18 Jan 2013 at 3:09

GoogleCodeExporter commented 9 years ago
For reference, GSON allows you to register a JSON deserializer or a type 
adapter:

http://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/google/gson/J
sonDeserializer.html
http://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/google/gson/T
ypeAdapter.html

Jackson has an even more sophisticated mechanism with even a built-in concept 
of registering different types based on the value of a property:

http://programmerbruce.blogspot.com/2011/05/deserialize-json-with-jackson-into.h
tml

Sample code snippet copied from the above blog:

@JsonTypeInfo(  
    use = JsonTypeInfo.Id.NAME,  
    include = JsonTypeInfo.As.PROPERTY,  
    property = "type")  
@JsonSubTypes({  
    @Type(value = Cat.class, name = "cat"),  
    @Type(value = Dog.class, name = "dog") })  
abstract class Animal  
{  
  public String name;  
}  

class Dog extends Animal  
{  
  public String breed;  
  public String leashColor;  
}  

class Cat extends Animal  
{  
  public String favoriteToy;  
}

I like this annotation based approach, and that seems to me the most appealing 
approach for use case at hand.  We may have to provide greater customization in 
the future, but for now this seems like the simplest solution.

Original comment by yan...@google.com on 19 Jan 2013 at 2:25

GoogleCodeExporter commented 9 years ago

Original comment by yan...@google.com on 24 Jan 2013 at 1:49

GoogleCodeExporter commented 9 years ago

Original comment by yan...@google.com on 5 Feb 2013 at 4:48

GoogleCodeExporter commented 9 years ago
Something I would like to see handled is the unknown case. Supporting a 
'default' case if the JSON type is not known to the client.

Original comment by yincrash on 4 Mar 2013 at 7:28

GoogleCodeExporter commented 9 years ago

Original comment by yan...@google.com on 25 Mar 2013 at 7:32

GoogleCodeExporter commented 9 years ago
https://codereview.appspot.com/9618044/

Original comment by ngmic...@google.com on 28 May 2013 at 3:43

GoogleCodeExporter commented 9 years ago
Hi Michael,
A "default" case was not part of the initial set of requirements for the 
feature, but it certainly could be useful. If this is something you would like 
to see and have a use-case for, please feel free to open a new feature request, 
and include as much detail as possible.
One way you could use the features implemented is to make your type field a 
value type, such as an int. Currently, if the type field is null, an exception 
is thrown. However, if the type field is an unspecified int, it will have a 
value of 0. You could then define 0 to map to whatever default type you want. 
Of course, this is not a perfect design, as it wouldn't work if a value of "0" 
actually had meaning to your data.

Original comment by ngmic...@google.com on 28 Jun 2013 at 12:10