Как привести объект к JsType?

Я объявляю следующий 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 свойства. Библиотека возвращает мне новый объект с теми же свойствами (они следуют за RFC GeoJson, как и мой JsType), но я не могу привести его обратно к 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 ответа

@JsType и связанные аннотации не создают оболочки, которые пытаются понять, что вы хотели сделать, но на самом деле они генерируют код JS, который максимально соответствует тому, что вы делали. Это означает, что если вы говорите "я создаю новый не родной тип JS, и у него будет такой конструктор, как этот", GWT скажет "хорошо" и сделает это. И результатом будет тип в JS с конструктором, но объекты, созданные не с помощью этого точного конструктора по определению, не относятся к этому типу, и вы можете получить ошибку, если попытаетесь обработать их так, как если бы они были.

Вместо этого ваш FeatureCollection почти наверняка должен быть нативным типом, вероятно простым Object в JsPackage.GLOBAL namespace, и вместо конструктора у вас должен быть фабричный метод.

В качестве альтернативы, вы можете рискнуть использовать Js.uncheckedCast чтобы сказать "поверьте мне, этот объект более или менее имеет правильную форму (хотя это может быть неправильный тип), просто используйте его, как если бы он был того же типа", и пока GWT не имеет оснований для дальнейшей проверки типов, он позволит вам сойти с рук. Это, вероятно, подходит для использования в вашем собственном коде приложения, но с очень четкими заметками о том, что вы делаете и когда это пойдет не так.


Примечание: как правило, если у вас есть геттеры и сеттеры не на нативном JsType, вы должны пометить их как @JsProperty вместо того, чтобы отмечать приватное поле, так что - если вы сделали поле финальным, другой JS мог бы назначить его позже в любом случае, если вы заставили получатель или установщик выполнить некоторую проверку или кэширование, любой доступ из JS пропустил бы это. Помните также, что если тип JsType он автоматически экспортирует все свои открытые члены, так что вы можете достичь того же, просто удалив JsProperty и добытчик, и сделать поле публичным.

Как объяснил Колин, у вас нет типа, чтобы проверить GeoJson объект, поэтому вы не можете использовать instanceof или другие методы ООП, чтобы привести его обратно к безопасности конкретного типа. Вы должны установить тип как native=true, name="Object", namespace=GLOBAL и тогда вы можете использовать Js.cast отбросить его обратно как GeoJson тип.

Если вы хотите что-то более ООП, вы можете использовать шаблон посетителя и скрыть "ручную проверку типов" за этим посетителем, например:

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

Пример, основанный на этом, который также включает в себя аннотацию Джексона, поэтому он может быть и на стороне сервера.

Другие вопросы по тегам