OpenAPITools / OpenAPITools/openapi-generator

[BUG] [Typescript-fetch] client does not map correctly from timestamp to date

Open
#15,665 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Issue: Bug
Dominant language
Java
Stars
26.8k
Forks
7.7k
PR merge metrics
PR metrics pending

Description

Bug Report Checklist

  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator (example)?
  • Have you tested with the latest master to confirm the issue still exists?
  • Have you searched for related issues/PRs?
  • What's the actual output vs expected output?
  • [Optional] Sponsorship to speed up the bug fix or feature request (example)

Description

I have an openapi file with a server side in Java and a client side in node. Both using the openapi generator.
The server side returns the fields date-time serialized as timestimap in the json, i see that ok, the problem is that in the node client side, using typescript-fetch the dates are not deserialised correclty.
Given the timestamp 1685211548 it returns the date 1970-01-20T12:06:51.548Z

I think that is because they are mapped like this

return {
        
        'type': ShipmentTypeFromJSON(json['type']),
        'price': json['price'],
        'departureDate': (new Date(json['departureDate'])),
        'arrivalDate': (new Date(json['arrivalDate'])),
    };

In the Suggest a Fix section i clarify why i think that this is the problem.

For more info the server side is in Java 17 + springboot 3 and the date field is generated like this

import org.springframework.format.annotation.DateTimeFormat;
import java.time.OffsetDateTime;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private OffsetDateTime departureDate;

and is serialized to the json as unix timestamp

openapi-generator version

6.6.0 Both client and server

OpenAPI declaration file content or url

AvaliableShipmentAirlines:
  additionalProperties: false
  type: object
  required:
    - "airlineShipmentOptions"
  properties:
    airlineShipmentOptions:
      type: array
      maxItems: 1000
      items:
        $ref: "#/components/schemas/AirlineShipmentOption"
AirlineShipmentOption:
  additionalProperties: false
  type: object
  required:
    - "airlineLogoUrl"
    - "airlineName"
    - "airlineCode"
    - "shipmentOptions"
  properties:
    airlineLogoUrl:
      maxLength: 255
      type: string
      example: https://www.somedomian.com/airlineLogo.png
    airlineName:
      maxLength: 40
      pattern: '^[A-Za-z0-9]+$'
      type: string
      example: Emirates
    airlineCode:
      maxLength: 40
      pattern: '^[A-Za-z0-9]+$'
      type: string
      example: EMX
    shipmentOptions:
        $ref: "#/components/schemas/ShipmentOptions"
ShipmentOptions:
  type: array
  maxItems: 1000
  items:
    $ref: "#/components/schemas/ShipmentOption"
ShipmentOption:
  additionalProperties: false
  type: object
  required:
    - "type"
    - "price"
    - "departureDate"
    - "arrivalDate"
  properties:
    type:
      $ref: "#/components/schemas/ShipmentType"
    price:
      type: number
      format: double
      description: Price of the shipment
      example: 1
      maximum: 999
      minimum: 0
    departureDate:
      type: string
      format: date-time
      description: Departure date of the shipment
    arrivalDate:
      type: string
      format: date-time
      description: Arrival date of the shipment

Generation Details

Client side, where the timestaps are not mapped corrreclty
package.json scripts looks like this (other names)

"scripts": {
    ...
   "clean": "rimraf ./next && rimraf .generated/*",
    "post-process": "node src/scripts/post-process.js",
    "generate-pre": "npm run clean && npx openapi-generator-cli generate --generator-key name1 name2 && npm run post-process",
    "build-name1-rest-client": "cd .generated/name1-rest-client && npm install && npm run build",
    "generate": "npm run generate-pre && npm run build-name1-rest-client && npm i .generated/name1-rest-client "
  },

openapitools.json looks like this (other names)

{
  "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
  "spaces": 2,
  "generator-cli": {
    "version": "6.6.0",
    "storageDir": ".tmp/",
    "generators": {
      "name1": {
        "generatorName": "typescript-fetch",
        "output": ".generated/name1-rest-client",
        "glob": "node_modules/@scope/name1-openapi/open-api.{yaml,yml}",
        "additionalProperties": {
          "withInterfaces": true,
          "supportsES6": "true",
          "npmName": "name1-rest-client",
          "stringEnums": true
        }
      },
      "name2": {
        "generatorName": "typescript-fetch",
        "output": ".generated/name2-rest-client",
        "glob": "node_modules/@scope/name2-private-openapi/open-api.{yaml,yml}",
        "additionalProperties": {
          "withInterfaces": true,
          "supportsES6": "true",
          "npmName": "name2-rest-client",
          "stringEnums": true
        }
      }
    }
  }
}

Server side:

<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>6.6.0</version>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputSpec>${open-api.path}</inputSpec>
                <enablePostProcessFile>false</enablePostProcessFile>
                <library>spring-boot</library>
                <generateModels>true</generateModels>
                <generatorName>spring</generatorName>
                <generateApiDocumentation>true</generateApiDocumentation>
                <generateModelDocumentation>true</generateModelDocumentation>
                <generateApiTests>true</generateApiTests>
                <typeMappings>
                    <typeMapping>Date=OffsetDateTime</typeMapping>
                </typeMappings>
                <importMappings>Date=java.time.OffsetDateTime</importMappings>
                <configOptions>
                    <removeOperationIdPrefix>true</removeOperationIdPrefix>
                    <apiNameSuffix>DTO</apiNameSuffix>
                    <delegatePattern>true</delegatePattern>
                    <generateApi>true</generateApi>
                    <skipOperationExample>false</skipOperationExample>
                    <java17>true</java17>
                    <dateLibrary>java17</dateLibrary>
                    <groupId>com.aGreatName</groupId>
                    <basePackage>com.aGreatName</basePackage>
                    <modelPackage>com.aGreatName.model</modelPackage>
                    <apiPackage>com.aGreatName.api</apiPackage>
                    <configPackage>com.aGreatName</configPackage>
                    <configHelp>true</configHelp>
                    <interfaceOnly>true</interfaceOnly>
                    <skipDefaultInterface>false</skipDefaultInterface>
                    <useTags>true</useTags>
                </configOptions>
                <additionalProperties>
                    <additionalProperty>modelNameSuffix=DTO</additionalProperty>
                    <additionalProperty>useSpringBoot3=true</additionalProperty>
                </additionalProperties>
            </configuration>
        </execution>
    </executions>
</plugin>

Steps to reproduce

Use date-time in the openapi spec and generate client-side code using typescript fetch, then receive timestamp for that field in the json of the response.

Suggest a fix

Generate the code more like this or use a function that does the check and returns the date.

return {
        
        'type': ShipmentTypeFromJSON(json['type']),
        'price': json['price'],
        'departureDate': isNaN(json['departureDate']) ? (new Date(json['departureDate'])) : (new Date(1000 * json['departureDate'])),
        'arrivalDate': isNaN(json['arrivalDate']) ? (new Date(json['arrivalDate'])) : (new Date(1000 * json['arrivalDate'])),
    };

My workaround is this script that i apply to the output folder after generating the code and is working fine for me.

const fs = require('fs');
const path = require('path');

const generatedDir = '.generated';

function applyCustomMapping(directory) {
    const files = fs.readdirSync(directory);

    files.forEach((file) => {
        const filePath = path.join(directory, file);
        const fileStat = fs.statSync(filePath);

        if (fileStat.isDirectory()) {
            applyCustomMapping(filePath);
        } else {
            const fileContent = fs.readFileSync(filePath, 'utf8');

            // Replace "(new Date(json['fieldName']))" with "isNaN(json['fieldName']) ? (new Date(json['fieldName'])) : (new Date(1000 * json['fieldName']))"
            const modifiedContent = fileContent.replace(/\(new Date\(json\['(.*?)'\]\)\)/g, "isNaN(json['$1']) ? (new Date(json['$1'])) : (new Date(1000 * json['$1']))");
            fs.writeFileSync(filePath, modifiedContent, 'utf8');
        }
    });
}

applyCustomMapping(generatedDir);

console.log('Custom date mapping applied successfully.');

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the typescript-fetch model deserializer shown in the issue and the openapi-generator-cli generate entry point; the generated files are placed under .generated, with post-processing in src/scripts/post-process.js. Reproduce the 1685211548 date-time response and trace how date-time is mapped. Done means generated clients correctly deserialize the reported timestamp without the workaround.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, typescript
Domain
api, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.