如何将对象转换为JsType?

时间:2018-09-14 11:15:12

标签: java gwt gwt-jsinterop

我已声明以下JsType以便处理GeoJson兼容数据:

@JsType
public class FeatureCollection extends GeoJson {

    @JsProperty
    private Feature[] features;

    public FeatureCollection() {
        super("FeatureCollection");
        features = new Feature[]{};
    }

    public Feature[] getFeatures() {
        return features;
    }

有时,我需要将FeatureCollection对象传递给外部库(例如,通过Turfs.js执行单位转换),该外部库通过features属性访问数据。 lib向我返回了一个具有相同属性的新对象(它们遵循我的JsType一样遵循GeoJson RFC),但是我无法将其强制转换回FeatureCollection

FeatureCollection fc = new FeatureCollection();
Object o = TurfUtils.toWgs84(fc); // Works and give an object which respect the FeatureCollection scheme (ie an array of Features) when I print it on the javascript console.
FeatureCollection featureCollection = TurfUtils.toWgs84(fc); // Throw a java.lang.ClassCastException

Turf库是JsInteroped:

@JsType(isNative = true, namespace = GLOBAL, name = "turf")
public class TurfUtils {

    public static native <T extends GeoJson> T toWgs84(T geojson);
}

将FeatureCollection设为本机JsType时,它可以工作,但会阻止我使用当前的构造函数,因此,我正在寻找一种将javascript对象回退到JsType的方法。

2 个答案:

答案 0 :(得分:2)

@JsType和相关注释不会创建试图理解您 meant 要做什么的包装器,但是它们实际上会生成与您尽可能近地对应的JS代码。 >做到。这意味着如果您说“我正在制作一个新的非本地JS类型,并且它将具有这样定义的构造函数”,则GWT将说“好”并执行该操作。结果将是JS中具有构造函数的类型,但是未按定义使用该确切构造函数创建的对象却不是该类型的对象,并且如果尝试将它们视为真实对象,则可能会出错。

相反,您的FeatureCollection几乎应该是本机类型,可能在Object命名空间中是纯JsPackage.GLOBAL,并且应该有一个factory方法来代替构造函数。

或者,您可能会冒用Js.uncheckedCast说“相信我,这个对象或多或少是正确的形状(尽管它可能是错误的类型),就像使用相同的类型一样使用它” ,并且只要GWT没有理由进一步进行类型检查,它就会让您摆脱它。这可能适合在您自己的应用程序代码中使用,但要非常清楚地说明您在做什么以及何时会出错。


旁注-通常,如果在非本机JsType中有getter和setter,则应将它们标记为@JsProperty而不是标记私有字段,因此-如果将字段定为final,则其他无论如何,JS可能会在以后分配它,如果您使getter或setter进行一些验证或缓存,那么从JS进行的任何访问都将丢失它。还要记住,如果类型是JsType,它将自动导出其所有公共成员,因此您可以通过删除JsProperty和getter,并使该字段成为公共字段来实现相同的目的。

答案 1 :(得分:1)

正如Colin解释的那样,您没有任何类型可以签入GeoJson对象,因此您不能使用instanceof或其他OOP技术将其强制转换回特定的类型安全性。您必须将类型设置为native=true, name="Object", namespace=GLOBAL,然后才能使用Js.cast将其回退为GeoJson类型。

如果您想要更多OOP,则可以使用访问者模式,并将“手动类型检查”隐藏在该访问者后面,例如:

import static jsinterop.annotations.JsPackage.GLOBAL;

import javax.annotation.Nullable;
import jsinterop.annotations.JsOverlay;
import jsinterop.annotations.JsType;

@JsType(namespace = GLOBAL, name = "Object", isNative = true)
class GeoJson {
    public String type;
    public final @JsOverlay Type getTypeEnum() { return Type.valueOf(type); }
    public final @JsOverlay void setTypeEnum(Type type) { this.type = type.name(); }

    public static @JsOverlay FeatureCollection featureCollection(Feature... features) {
        FeatureCollection o = new FeatureCollection();
        o.setTypeEnum(Type.FeatureCollection);
        o.features = features;
        return o;
    }

    public static @JsOverlay Feature feature(Geometry geometry) { return feature(null, geometry); }
    public static @JsOverlay Feature feature(@Nullable String featureId, Geometry geometry) {
        Feature o = new Feature();
        o.setTypeEnum(Type.Feature);
        o.id = featureId;
        o.geometry = geometry;
        return o;
    }

    public static @JsOverlay Point point(double x, double y) { return point(new double[] { x, y }); }
    public static @JsOverlay Point point(double[] coordinates) {
        Point o = new Point();
        o.setTypeEnum(Geometry.Type.Point);
        o.coordinates = coordinates;
        return o;
    }

    public static @JsOverlay Polygon polygon(double[][] coordinates) {
        Polygon o = new Polygon();
        o.setTypeEnum(Geometry.Type.Polygon);
        o.coordinates = new double[][][] { coordinates };
        return o;
    }

    public enum Type {Feature, FeatureCollection}

    @JsType(namespace = GLOBAL, name = "Object", isNative = true)
    public static final class Feature extends GeoJson {
        public @Nullable String id;
        public Geometry geometry;
    }

    @JsType(namespace = GLOBAL, name = "Object", isNative = true)
    public static class FeatureCollection extends GeoJson {
        public Feature[] features;
    }

    @JsType(namespace = GLOBAL, name = "Object", isNative = true)
    public static abstract class Geometry {
        public String type;
        public final @JsOverlay Geometry.Type getTypeEnum() { return Geometry.Type.valueOf(type); }
        public final @JsOverlay void setTypeEnum(Geometry.Type type) { this.type = type.name(); }

        public final @JsOverlay <T> T accept(GeometryVisitor<T> fn) { switch (getTypeEnum()) {
            case Point: return fn.point((Point) this);
            case Polygon: return fn.polygon((Polygon) this);
            default: throw new UnsupportedOperationException("unexpected type " + type);
        } }

        public static @JsOverlay @Nullable Point isPoint(@Nullable Geometry g) {
            return g == null ? null : g.accept(new GeometryVisitor<Point>() {
                @Override public Point point(Point g) { return g; }
                @Override public Point polygon(Polygon p) { return null; }
            });
        }

        public static @JsOverlay @Nullable Polygon isPolygon(@Nullable Geometry g) {
            return g == null ? null : g.accept(new GeometryVisitor<Polygon>() {
                @Override public Polygon point(Point g) { return null; }
                @Override public Polygon polygon(Polygon p) { return p; }
            });
        }

        public enum Type {Point, Polygon}
    }

    @JsType(namespace = GLOBAL, name = "Object", isNative = true)
    public static class Point extends Geometry {
        public double[] coordinates;
        public final @JsOverlay double x() { return coordinates[0]; }
        public final @JsOverlay double y() { return coordinates[1]; }
    }

    @JsType(namespace = GLOBAL, name = "Object", isNative = true)
    public static final class Polygon extends Geometry {
        public double[][][] coordinates;
        public final @JsOverlay double[][] shell() { return coordinates[0]; }
    }

    public interface GeometryVisitor<T> {
        T point(Point g);
        T polygon(Polygon p);
    }
}

基于this one的示例,其中还包括杰克逊注释,因此它也可以在服务器端。