tazatechnology / openapi_spec

Dart based OpenAPI specification generator and parser
BSD 3-Clause "New" or "Revised" License
8 stars 5 forks source link

Fix deserialization of anyOf types with lists #30

Closed davidmigloz closed 10 months ago

davidmigloz commented 10 months ago

It was good that you added support for deserializing anyOf types because there's also a case for that in the OpenAI API (although they have not adjusted the spec appropriately yet - https://github.com/openai/openai-openapi/pull/99).

When you create an embedding, OpenAI can return the embedding as a list of floats or as a base64-encoded string. So the schema looks like this:

Embedding:
  type: object
  description: |
    Represents an embedding vector returned by embedding endpoint.
  properties:
    index:
      type: integer
      description: The index of the embedding in the list of embeddings.
    embedding:
      title: EmbeddingVector
      description: |
        The embedding vector, which is a list of floats. The length of vector depends on the model as listed in the [embedding guide](https://platform.openai.com/docs/guides/embeddings).
      oneOf:
        - type: string
          description: The embedding vector as a base64-encoded string.
        - type: array
          description: The embedding vector as a list of floats.
          items:
            type: number
    object:
      type: string
      description: The object type, which is always "embedding".
  required:
    - index
    - object
    - embedding

The previous implementation was generating the following JSON converter:

class _EmbeddingVectorConverter
    implements JsonConverter<EmbeddingVector, Object?> {
  const _EmbeddingVectorConverter();

  @override
  EmbeddingVector fromJson(Object? data) {
    if (data is String) {
      return EmbeddingVector.string(data);
    }
    if (data is List<double>) {
      return EmbeddingVector.arrayNumber(data);
    }
    throw Exception('Unexpected value for EmbeddingVector: $data');
  }

  @override
  Object? toJson(EmbeddingVector data) {
    return switch (data) {
      _UnionEmbeddingVectorString(value: final v) => v,
      _UnionEmbeddingVectorArrayNumber(value: final v) => v,
    };
  }
}

However, it failed to deserialize a list of floats because data is of type List<dynamic>, which is not a List<double>.

This PR changes the condition to check that data is a List and all the items of the list are of the specified type.

This is how the new JSON converter looks like:

class _EmbeddingVectorConverter
    implements JsonConverter<EmbeddingVector, Object?> {
  const _EmbeddingVectorConverter();

  @override
  EmbeddingVector fromJson(Object? data) {
    if (data is String) {
      return EmbeddingVector.string(data);
    }
    if (data is List && data.every((item) => item is double)) {
      return EmbeddingVector.arrayNumber(data.cast());
    }
    throw Exception('Unexpected value for EmbeddingVector: $data');
  }

  @override
  Object? toJson(EmbeddingVector data) {
    return switch (data) {
      _UnionEmbeddingVectorString(value: final v) => v,
      _UnionEmbeddingVectorArrayNumber(value: final v) => v,
    };
  }
}

cc @walsha2

walsha2 commented 10 months ago

openapi_spec (v0.7.2) - Published.

@davidmigloz good find!