Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Optional<T> #1329

Closed
mleonhard opened this issue Apr 1, 2021 · 7 comments
Closed

Support Optional<T> #1329

mleonhard opened this issue Apr 1, 2021 · 7 comments

Comments

@mleonhard
Copy link

I try to avoid using null. I want to use Moshi to convert a class to/from JSON which contains Optional<?>:

class MyRequest {
  Optional<Integer> id;
  Optional<Integer> group;
}

Moshi complains: java.lang.IllegalArgumentException: Platform class java.util.Optional in java.util.Optional<java.lang.String> requires explicit JsonAdapter to be registered.

How about adding support for it in Moshi?

@ZacSweers
Copy link
Collaborator

Any reason you can't just write an adapter for it per the message?

@mleonhard
Copy link
Author

I don't want to write an adapter. Here are the reasons why:

  • The adapter and its tests are extra code that we must maintain.
  • I will spend time learning how to write the adapter and writing it.
  • The knowledge of how to write Moshi adapters is not useful to me beyond this task.
  • My implementation may have problems. These problems could affect our production service, harming our users and our business. Additionally, we will spend time debugging, writing regression tests, and fixing the problem. And the regression tests are code to maintain.
  • When many Moshi users write and maintain their own adapters, effort is duplicated and wasted across society.

Moshi supports List<T>. Why shouldn't it also support Optional<T>? Optional appeared 7 years ago in Java 8.

I would also ask for ImmutableList support. Code is easier to maintain when it uses immutable data structures by default.

@JakeWharton
Copy link
Collaborator

List maps to a fundamental type in JSON: arrays. You can't just pick arbitrary types and ask why they aren't supported even though they're built in. We also don't support Set, for example.

Supporting Optional means we force Android users to a fairly high minimum supported version or to enable coreLibraryDesugaring. We also could conditionally install an adapter for it based on whether classloading the type throws or not.

We definitely aren't going to add support for Guava types. Someone can write a third-party library with adapters for its numerous collection APIs.

@ZacSweers
Copy link
Collaborator

ZacSweers commented Apr 2, 2021

No action to take on this. Writing your own Optional adapter is trivial, here's one you and the rest of society can copy. Or you can put it into your own library that depends on Moshi and share it with the rest of society, there are a lot of little libraries like this!

public final class OptionalFactory implements JsonAdapter.Factory {
  @Nullable
  @Override
  public JsonAdapter<?> create(Type type, Set<? extends Annotation> annotations, Moshi moshi) {
    if (!annotations.isEmpty()) return null;
    if (!(type instanceof ParameterizedType)) return null;

    Class<?> rawType = Types.getRawType(type);
    if (rawType != Optional.class) return null;

    Type optionalType = ((ParameterizedType) type).getActualTypeArguments()[0];

    JsonAdapter<?> optionalTypeAdapter = moshi.adapter(optionalType).nullSafe();

    return new OptionalJsonAdapter<>(optionalTypeAdapter);
  }

  private static class OptionalJsonAdapter<T> extends JsonAdapter<Optional<T>> {

    private final JsonAdapter<T> optionalTypeAdapter;

    public OptionalJsonAdapter(JsonAdapter<T> optionalTypeAdapter) {
      this.optionalTypeAdapter = optionalTypeAdapter;
    }

    @Nullable
    @Override
    public Optional<T> fromJson(JsonReader reader) throws IOException {
      T instance = optionalTypeAdapter.fromJson(reader);
      return Optional.ofNullable(instance);
    }

    @Override
    public void toJson(JsonWriter writer, @Nullable Optional<T> value) throws IOException {
      if (value.isPresent()) {
        optionalTypeAdapter.toJson(writer, value.get());
      } else {
        writer.nullValue();
      }
    }
  }
}

@mleonhard
Copy link
Author

Thank you both for your work on Moshi. It solves a problem for me and helps get me closer to launching my product. It also improves my opinion of Square. I have a friend who works there. I have eaten with him in the Square SF office. I consider working at Square each time I look for a job.

@ZacSweers Thanks for providing an example. It is 45-lines and 1.3KB of code. With tests, it will be 2x or 3x bigger. In my opinion, that is not a trivial amount of code to maintain.

@JakeWharton Thanks for telling me about the limitations of Android users.

List maps to a fundamental type in JSON: arrays. You can't just pick arbitrary types and ask why they aren't supported even though they're built in. We also don't support Set, for example.

To clarify, I asked only for Optional.

I avoid using null and so do many other people. Language maintainers are adding support for this coding style because so many people want it:

I understand if you personally like using null in your Java code. It's your code and you can write it however you wish. Please understand that others have different needs and want different things. Please try to exercise empathy with your users. Help us to use your library with our coding styles.

Sincerely,
Michael

@JakeWharton
Copy link
Collaborator

List maps to a fundamental type in JSON: arrays. You can't just pick arbitrary types and ask why they aren't supported even though they're built in. We also don't support Set, for example.

To clarify, I asked only for Optional.

Your intent was clear. Perhaps you recall asking it as a question:

Moshi supports List. Why shouldn't it also support Optional?

I merely obliged and answered.

Anyway despite your tone I'm supportive of this.

@BryanConradHart
Copy link

BryanConradHart commented Sep 28, 2024

to anyone else in the future, if you want to use @ZacSweers's sample code (or really any other Optional adapter that treats empty Optional as null), make sure your adapter .serializeNulls().nullSafe() or else your adapter's fromJson wont be invoked and your Optional field will be null.

If you're trying to deserialize json that was serialized by somebody else, and they do not serialize nulls, I can't find any way ho have the adapter be invoked.

It'd be nice if it was supported out of the box, because this is a bit of a gotcha.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants