dbopendata / db-fahrplan-api

DB Fahrplan API
31 stars 1 forks source link

JSON objects returned from open-api.bahn.de are not RFC 4627 compliant #29

Open nekrondev opened 7 years ago

nekrondev commented 7 years ago

UPDATE: Issue is releated to #7 and #8

I did a first test using go-swagger package for Go programming language to use the location service from the OpenData portal. Go's JSON unmarshaler is a RFC 4627 compliant package and it returned unmarshaling errors while processing the result.

I investigated into this matter and found the reasons why. The following code example will show the bad JSON response from the portal and a working RFC compliant result.

Playgound code can be found here: https://play.golang.org/p/UeFSnsVAQp

To make it short:

Server response was

{
"LocationList":{
  "StopLocation":{
    "name":"Itzehoe",
    "lon":"9.510290",
    "lat":"53.923870",
    "id":"008003102"
    }
  }
}

but RFC compliant (and DB API swagger yaml file defined it that way) would be

{
"LocationList":{
  "StopLocation":[{
    "name":"Itzehoe",
    "lon":9.510290,
    "lat":53.923870,
    "id":8003102
    }]
  }
}

Notice the corrected array result for StopLocation with square brackets and the correct double and int32 number formats (DB server returned quoted strings, but yaml file defined double and int32).

As for now using automated tools like go-swagger to create client stubs is not recommended as long as the RFC issues are not fixed.

Cheers, Nek

Appendix playground code:

package main

import (
    "encoding/json"
    "fmt"
    )

/*
 The http response returned from open-api.bahn.de is not RFC 4627 compliant for the following reasons:

 1. Swagger definition for StopLocation says that lon, lat and id formats are double, double and int32, however the
    returned JSON object returns them as quoted strings and not float point and integer values (i.e. w/out quotes).
    Either the swagger definition file must be modified to return strings or the resulting JSON object must be RFC compliant.

 2. If a single array element is returned the resulting JSON object will not indicate it as an array, i.e. the square brackets are missing.
    For the below example "StopLocation":[{...}] needs to be returned to be again RFC 4627 compliant, even if there is only one element in the StopLocation array.
    The missing array indicator makes parsing JSON objects with single array results not indicated as arrays troublesome and is again not RFC compliant.
*/

const (
    HTTP_PAYLOAD_BAD = `{
"LocationList":{
  "StopLocation":{
    "name":"Itzehoe",
    "lon":"9.510290",
    "lat":"53.923870",
    "id":"008003102"
    }
  }
}`

// this is an example for a good response (mark the added square brackets and formatted double and int32 value)
HTTP_PAYLOAD_GOOD = `{
"LocationList":{
  "StopLocation":[{
    "name":"Itzehoe",
    "lon":9.510290,
    "lat":53.923870,
    "id":8003102
    }]
  }
}`
)

// types auto-generated by go-swagger
type LocationResponse struct {

    // location list
    // Required: true
    LocationList *LocationList `json:"LocationList"`
}

type LocationList struct {

    // stop location
    // Required: true
    StopLocation []*StopLocation `json:"StopLocation"`
}

type StopLocation struct {

    // id
    // Required: true
    ID *int32 `json:"id"`

    // lat
    // Required: true
    Lat *float64 `json:"lat"`

    // lon
    // Required: true
    Lon *float64 `json:"lon"`

    // name
    // Required: true
    Name *string `json:"name"`
}

// Main
func main() {

    // JSON encoding bad response
    // this will give us json: cannot unmarshal object into Go value of type []*main.StopLocation
    dataBad := &LocationResponse{}
    Decode(HTTP_PAYLOAD_BAD, dataBad)

    // if fixed RFC compliant JSON object is parsed everything works like charm
    dataGood := &LocationResponse{}
    Decode(HTTP_PAYLOAD_GOOD, dataGood)
}

// Decode the returned example http response data
func Decode(payload string, result *LocationResponse) {
    err := json.Unmarshal([]byte(payload), result)
    if err != nil {
        fmt.Println(err)
    } else {
        for _, stoploc := range result.LocationList.StopLocation {
            fmt.Printf("Name:%s, Lat:%0.2f, Lon:%0.2f\n", *stoploc.Name, *stoploc.Lat, *stoploc.Lon)
        }
    }
}