Search code examples
javaarraysgsonobservable

GSON: deserialize object which requires field owner


I have ObservableField<O, V> class, of which instances are held by custom entity classes to keep track of data changes.

However, I couldn't find a way to write a custom TypeAdapterFactory implementation for this ObservableField class, because I don't know how to get the field owner in an uninitialized state.

The actual ObservableField<O, V> looks like this.

public class ObservableField<O, V> {

    private final O owner;
    private V value;
    private final List<ObservableFieldHandler<O, V>> handlers = new ArrayList<>();

    public ObservableField(O owner, V value) {
        this.owner = owner;
        this.value = value;
    }

    public O getOwner() {
        return owner;
    }

    public V get() {
        return value;
    }

    public void set(V value) {
        V old = this.value;
        this.value = value;
        this.handlers.forEach(it -> it.accept(this, old, value));
    }

    public void subscribe(ObservableFieldHandler<O, V> handler) {
        handlers.add(handler);
    }

    public boolean unsubscribe(ObservableFieldHandler<O, V> handler) {
        return handlers.remove(handler);
    }
}

This is an example of entity class that uses ObservableField

@Getter
@Setter
public class Member {

    private final UUID id;
    private final ObservableField<Member, Integer> balance;

    public Member(UUID id) {
        this.id = id;
        this.balance = new ObservableField(this, 0);
    }
}

This is my ObservableFieldTypeAdapterFactory I wrote but did not work.

public class ObservableFieldTypeAdapterFactory implements TypeAdapterFactory {

    @SuppressWarnings("unchecked")
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        if (type.getRawType() != ObservableField.class) return null;
        Type param = ((ParameterizedType) type.getType()).getActualTypeArguments()[0];
        return (TypeAdapter<T>) new TypeAdapter<ObservableField<?, ?>>() {
            @Override
            public void write(JsonWriter out, ObservableField<?, ?> value) throws IOException {
                gson.toJson(value.get(), param, out);
            }

            @Override
            public ObservableField<?, ?> read(JsonReader in) throws IOException {
                return new ObservableField<>(/* FIXME: get field owner!*/, gson.fromJson(in, param));
            }
        };
    }
}

Is there any way to get the field owner in an uninitialized state? (If I remember correctly, GSON uses the Unsafe class to create an instance of the uninitialized object, then fills fields later.)


Solution

  • I could not find a solution, so I added ObservableField#initialize method to lazily initialize the owner to resolve this issue.

    public class ObservableField<O, V> {
    
        private O owner;
        private V value;
        private final List<ObservableFieldHandler<O, V>> handlers = new ArrayList<>();
    
        public ObservableField(V value) {
            this.value = value;
        }
    
        public O getOwner() {
            if (owner == null) {
                throw new IllegalStateException("Not initialized");
            }
            return owner;
        }
    
        public void initialize(O owner) {
            if (this.owner != null) {
                if (this.owner.equals(owner)) return;
                throw new IllegalStateException("Already initialized");
            }
    
            this.owner = owner;
        }
    
        public V get() {
            return value;
        }
    
        public void set(V value) {
            V old = this.value;
            this.value = value;
            this.handlers.forEach(it -> it.accept(this, old, value));
        }
    
        public void subscribe(ObservableFieldHandler<O, V> handler) {
            handlers.add(handler);
        }
    
        public boolean unsubscribe(ObservableFieldHandler<O, V> handler) {
            return handlers.remove(handler);
        }
    }