is it possible to get metadata of an OData service in JSON format?
When I try to use format=json , it doesn't work. Here is what I tried:
http://odata.informea.org/services/odata.svc/$metadata/?format=json
The $metadata document is in the CSDL format, which currently only has an XML representation. (As a side note, if you do want to request the json format for a different kind of OData payload, make sure the format query token has a $ in front of it: $format=json.)
So, no it is not possible. You can, however, get the service document in JSON, which is a subset of the $metadata document:
http://odata.informea.org/services/odata.svc?$format=json
This won't have type information, but it will list the available entry points of the service (i.e., the entity sets).
As an alternative to ?$format=json, you could also just set the following two headers :
Accept: application/json
Content-Type: application/json; charset=utf-8
I'm not sure which is the minimum Odata version required, but this works perfectly for me on Microsoft Dynamics NAV 2016, which uses Odata v4.
I agreed with the previous answer. This isn't supported by the specification but some OData frameworks / libraries are about to implement this feature.
I think about Olingo. This is could be helpful for you if you also implement the server side. See this issue in the Olingo JIRA for more details:
OLINGO-570 - https://issues.apache.org/jira/browse/OLINGO-570
Hope it helps you,
Thierry
You can use jQuery to get the relevant information from an OData service $metadata.
Take for example:
You write a unit test to check the OData entities property names matches with your application entities. Then you have to retrieve the properties of the OData entity.
$.ajax({
type: "GET",
url: "/destinations/odata-service/$metadata",
beforeSend: function() {
console.log("before send check");
},
dataType: "xml",
contentType: "application/atom+xml",
context: document.body,
success: function(xml) {
console.log("Success ResourceTypes");
var ODataTypeINeed = $(xml).find('EntityType').filter(function(){
return $(this).attr('Name') == 'ODataTypeINeed'
});
$(ODataTypeINeed).find('Property').each(function() {
console.log($(this).attr('Name')); //List of OData Entity properties
});
},
error: function(err) {
console.log(err);
}
});
I wrote a simple provider to parse out some of the needed information from the metadata, Feel free to expand on it. First you'll need some simple models, to expresss the data, we'll want to convert from there ugly XML names
export class ODataEntityType
{
name: string;
properties: ODataProperty[];
}
export class ODataProperty
{
name: string;
type: ODataTypes;
isNullable: boolean;
}
//Hack Until Ionic supports TS 2.4
export class ODataTypeMap
{
"Edm.Int32" = ODataTypes.Int;
"Edm.Int64" = ODataTypes.Long;
"Edm.Decimal" = ODataTypes.Decimal;
"Edm.Double" = ODataTypes.Double;
"Edm.Guid" = ODataTypes.Guid;
"Edm.String" = ODataTypes.String;
"Edm.Boolean" = ODataTypes.Bool;
"Edm.DateTime" = ODataTypes.DateTime;
"Edm.DateTimeOffset" = ODataTypes.DateTimeOffset;
}
export enum ODataTypes
{
Int,
Long,
Decimal,
Double,
Guid,
String,
Bool,
DateTime,
DateTimeOffset
}
This is the provider:
import { Injectable } from "#angular/core";
import { Http } from "#angular/http";
import * as X2JS from 'x2js';
import * as _ from 'underscore';
import { ODataEntityType, ODataProperty, ODataTypes, ODataTypeMap } from "../models/ODataEntityType";
#Injectable()
export class ODataMetadataToJsonProvider {
x2js = new X2JS();
public entityTypeMap: Dictionary = new Dictionary();
public entityTypes : ODataEntityType[];
constructor(public http: Http) {
}
parseODataMetadata(metadataUrl: string) {
this.http.get(metadataUrl).subscribe(data => {
let metadata: any = this.x2js.xml2js(data.text());
let rawEntityTypes = _.filter(metadata.Edmx.DataServices.Schema, x => x["EntityType"] != null);
if(rawEntityTypes.length == 0)
{
return;
}
this.entityTypes = _.map(rawEntityTypes[0]["EntityType"], t => {
let oDataEntityType = new ODataEntityType();
oDataEntityType.name = t["_Name"];
oDataEntityType.properties = _.map(t["Property"], p => {
let property = new ODataProperty();
property.name = p["_Name"];
let typeStr: string = p["_Type"];
property.type = ODataTypeMap[typeStr];
property.isNullable = !!p["_Nullable"];
return property;
});
return oDataEntityType;
});
});
}
}
In 2021, possibly...
The OData 4.01 specification includes support for JSON: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_MetadataDocumentRequest
Microsoft's OData implementation added support from the following versions:
ODataLib 7.7.3 (released 24 Sep 2020)
WebAPI 7.5.4 (released 29 Dec 2020)
Additionally, JSON Metadata is only supported at platform implementing .NETStardard 2.0. [sic].
If the service you're calling supports it, you can do it in the query string...
$format=application/json
$format=json
...or using the http headers:
Accept=application/json
It it doesn't... ask the provider of the service to upgrade?
Related
I am trying to create an endpoint in a HTTP API that receives data periodically from remote devices.
There is a technological shift happening in this project where devices have previously reported data in XML whereas future implementations will shift towards JSON.
I am writing this API in NestJS (7.x) and TypeScript. Data will be coming in through the same endpoint (POST /) and data format is differentiated by the Content-Type header.
#Controller()
export class IngressController {
constructor(private readonly ingressService: IngressService) {
}
/* ... */
#Post('')
#Header('Cache-Control', 'none')
#HttpCode(HttpStatus.NO_CONTENT)
public async receive(
#Headers('Content-Type') contentType: string,
#Req() req: any,
#Body() body: string,
): Promise<InsertResponse> {
if (IngressController.isJson(contentType)) {
return { inserted: await this.ingressService.insertJsonString(req.body) };
}
if (IngressController.isXml(contentType)) {
return { inserted: await this.ingressService.insertXmlString(req.body) };
}
throw new BadRequestException(contentType, 'Unsupported Content-Type');
}
/* ... */
}
Future devices will report data in JSON (indicated by the Content-Type: application/json header in the HTTP request), legacy devices report in XML (Content-Type: application/xml).
It works splendidly for JSON. However, my problem is that req.body (or body respectively) is an empty object in the XML case. I presume the NestJS middleware is doing something and getting confused by XML, but I have found no hints as to allow XML payloads side-by-side with JSON. I don't mind parsing it manually.
As you suspected NestJS has a built-in bodyparser that will not be able to parse xml. What you could do is to plug in a custom middleware where you decide whether to parse the request body as xml or pass the request on the the next handler.
Something like this should work (I'm using express-xml-bodyparser in this example):
import {NestFactory} from '#nestjs/core';
import {AppModule} from './app.module';
import {Request} from "express";
const xmlParser = require('express-xml-bodyparser');
const xmlParserMidleware = xmlParser();
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use((req: Request, res: any, next: any) => {
if (req.path.includes("/api/json-or-xml-handler") && req.header('Content-Type')?.includes('xml')) {
return xmlParserMidleware(req, res, next);
}
next();
});
await app.listen(8020);
}
bootstrap();
Then, in your controller body will either be the parsed json-object or an object representation of your xml:
#Controller()
export class TestControllerController {
#Post('/api/json-or-xml-handler')
receive(#Body() body: any) {
console.log(body);
// ...
}
}
So I'm getting the following JSON structure from my asp.net core api:
{
"contentType": null,
"serializerSettings": null,
"statusCode": null,
"value": {
"productName": "Test",
"shortDescription": "Test 123",
"imageUri": "https://bla.com/bla",
"productCode": null,
"continuationToken": null
}
}
I have the following typescript function that invokes the API to get the above response:
public externalProduct: ProductVM;
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get("api/product?productCode=" + code)
.map((data: ProductVM) => {
this.externalProduct = data; //not working...
console.log("DATA: " + data);
console.log("DATA: " + data['value']);
return data;
});
}
ProductVM:
export interface ProductVM {
productName: string;
shortDescription: string;
imageUri: string;
productCode: string;
continuationToken: string;
}
My problem is that I can't deserialize it to ProductVM. The console logs just produce [object Object]
How can I actually map the contents of the value in my json response to a ProductVM object?
Is it wrong to say that data is a ProductVM in the map function? I have tried lots of different combinations but I cannot get it to work!
I'm unsure whether I can somehow automatically tell angular to map the value array in the json response to a ProductVM object or if I should provide a constructor to the ProductVM class (it's an interface right now), and extract the specific values in the json manually?
The data object in the map method chained to http is considered a Object typed object. This type does not have the value member that you need to access and therefore, the type checker is not happy with it.
Objects that are typed (that are not any) can only be assigned to untyped objects or objects of the exact same type. Here, your data is of type Object and cannot be assigned to another object of type ProductVM.
One solution to bypass type checking is to cast your data object to a any untyped object. This will allow access to any method or member just like plain old Javascript.
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get("api/product?productCode=" + code)
.map((data: any) => this.externalProduct = data.value);
}
Another solution is to change your API so that data can deliver its content with data.json(). That way, you won't have to bypass type checking since the json() method returns an untyped value.
Be carefull though as your any object wil not have methods of the ProductVM if you ever add them in the future. You will need to manually create an instance with new ProductVM() and Object.assign on it to gain access to the methods.
From angular documentation: Typechecking http response
You have to set the type of returned data when using new httpClient ( since angular 4.3 ) => this.http.get<ProductVM>(...
public externalProduct: ProductVM;
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get<ProductVM>("api/product?productCode=" + code)
.map((data: ProductVM) => {
this.externalProduct = data; // should be allowed by typescript now
return data;
});
}
thus typescript should leave you in peace
Have you tried to replace
this.externalProduct = data;
with
this.externalProduct = data.json();
Hope it helps
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get("api/product?productCode=" + code)
.map(data => {
this.externalProduct = <ProductVM>data;
console.log("DATA: " + this.externalProduct);
return data;
});
}
So, first we convert the response into a JSON.
I store it into response just to make it cleaner. Then, we have to navigate to value, because in your data value is the object that corresponds to ProductVM.
I would do it like this though:
Service
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get(`api/product?productCode=${code}`)
.map(data => <ProductVM>data)
.catch((error: any) => Observable.throw(error.json().error || 'Server error'));
}
Component
this.subscription = this.myService.getProductExternal(code).subscribe(
product => this.externalProduct = product,
error => console.warn(error)
);
I used this approach in a client which uses the method
HttpClient.get<GENERIC>(...).
Now it is working. Anyway, I do not understand, why I do not receive a type of T back from the http client, if I don't use the solution provided in the answer above.
Here is the client:
// get
get<T>(url: string, params?: [{key: string, value: string}]): Observable<T> {
var requestParams = new HttpParams()
if (params != undefined) {
for (var kvp of params) {
params.push(kvp);
}
}
return this.httpClient.get<T>(url, {
observe: 'body',
headers: this.authHeaders,
params: requestParams
}).pipe(
map(
res => <T>res
)
);
}
I'm working on integrating the watson-speech.js javascript library with a Spring-based server using the Watson Java SDK. I'm trying to send the output from a WatsonSpeech.SpeechToText.recognizeMicrophone call to the server with no luck. The Speech java classes appear to have the appropriate #SerializedName annotations that match the json being sent from the client, but I'm getting UnrecognizedPropertyException errors from Jackson.
Unrecognized field "keywords_result" (class com.ibm.watson.developer_cloud.speech_to_text.v1.model.SpeechResults), not marked as ignorable (2 known properties: "resultIndex", "results"])
Here's the controller method:
#RequestMapping(value = "/postWatsonRequest", method = RequestMethod.POST)
#ResponseBody
#ResponseStatus(value=HttpStatus.OK)
public ResponseObject postWatsonRequest(#RequestBody SpeechResults speechResults) {
...
}
I'm clearly missing something. Do I need to unpack the json manually on the server side (custom deserializer?) or format it into an acceptable json string on the client side?
It turned out to be a couple of mistakes on my part and although I'm not sure this is the best solution it does work. Here's the full code for anyone that's interested. Key things that made it work:
You must use the receive-jason event to capture the full json result. The data event appears to only return the final text
The result data had to be wrapped in a valid json wrapper - data:{message:data} (this was my big mistake)
Do not include contentType: 'application/json; charset=utf-8', in the ajax call or the controller will not recognize the json data
The Watson Java SDK WebSocketManager receives an okhttp3.ResponseBody from Watson from which it extracts a string. I presume this is similar to what the javascript SDK receives so I used the same code from the WebSocketManager to convert the JSON.stringify string to a SpeechResults object in the controller.
From the okhttp3.ResponseBody javadoc:
A one-shot stream from the origin server to the client application with the raw bytes of the response body
Watson javascript
function listen(token) {
stream = WatsonSpeech.SpeechToText.recognizeMicrophone({
token: token,
readableObjectMode: true,
objectMode: true,
word_confidence: true,
format: false,
keywords: keywordsArray,
keywords_threshold : 0.5,
continuous : false
//interim_results : false
//keepMicrophone: navigator.userAgent.indexOf('Firefox') > 0
});
stream.setEncoding('utf8');
stream.on('error', function(err) {
console.log(err);
stream.stop();
});
stream.on('receive-json', function(msg) {
console.log(msg);
if (msg.state != 'listening') {
if (msg.results[0].final) {
console.log('receive-json: ' + msg);
postResults(msg);
stream.stop();
}
}
});
}
Ajax post
function postResults(results) {
var data = JSON.stringify(results);
console.log('stringify: ' + data);
$.ajax({
type: 'POST',
url: appContextPath + '/postWatsonResult',
dataType: 'json',
data: {message:data}
})
.done(function(data) {
console.log('done data: '+ data);
})
.fail(function(jqXHR, status, error) {
var data = jqXHR.responseJSON;
console.log('fail data: '+ data);
});
}
Spring controller
#RequestMapping(value = "/postWatsonResult", method = RequestMethod.POST)
#ResponseBody
#ResponseStatus(value=HttpStatus.OK)
public ResponseObject postWatsonResult(#RequestParam("message") String message, Locale locale) {
logger.info("postWatsonRequest");
JsonObject json = new JsonParser().parse(message).getAsJsonObject();
SpeechResults results = null;
if (json.has("results")) {
results = GSON.fromJson(message, SpeechResults.class);
}
if (results != null) {
logger.debug("results: " + results.getResults().get(0).getAlternatives().get(0).getTranscript());
}
return new ResponseObject();
}
I still think it should be possible somehow to use #RequestBody SpeechResults speechResults so I'll continue to play around with this, but at least I have a working solution.
This is a question about molding some API data to fit some needs. I've heard it called "munging." I guess the heart of if is really re-formatting some JSON, but It would be ideal to do it the Ember data way...
I'm getting this data in an Emberjs setting - but it shouldn't really matter - ajax, ic-ajax, fetch, etc... I'm getting some data:
...
model: function() {
var libraryData = ajax({
url: endPoint,
type: 'GET',
dataType: 'jsonp'
});
// or most likely the ember-data way
// this.store.findAll(...
console.log(libraryData);
return libraryData;
}
...
The URL is getting me something like this:
var widgetResults = {
"settings": {
"amazonchoice":null,
"show":{
"showCovers":null,
"showAuthors":null
},
"style":null,
"domain":"www.librarything.com",
"textsnippets":{
"by":"by",
"Tagged":"Tagged","readreview":"read review","stars":"stars"
}
},
"books":{
"116429012":{
"book_id":"116429012",
"title":"The Book of Three (The Chronicles of Prydain Book 1)",
"author_lf":"Alexander, Lloyd",
"author_fl":"Lloyd Alexander",
// ...
The promise that is actually returned is slightly different.
My goal is to get to those books and iterate over them - but in my case it wants an array. that #each loops over must be an Array. You passed {settings: [object Object], books: [object Object]} - which makes sense.
In and ideal API the endpoint would be / http:/site.com/api/v2/books
and retrieve the data in this format:
{
"book_id":"116428944",
"title":"The Phantom Tollbooth",
"author_lf":"Juster, Norton",
"author_fl":"Norton Juster",
...
},
{
"book_id":"116428944",
"title":"The Phantom Tollbooth",
"author_lf":"Juster, Norton",
"author_fl":"Norton Juster",
...
},
{
... etc.
I would expect to just drill down with dot notation, or to use some findAll() but I'm just shooting in the dark. Librarything in specific is almost done with their new API - but suggest that I should be able to loop through this data and reformat it in an ember friendly way. I have just looped through and returned an array in this codepen - but haven't had luck porting it... something about the returned promise is mysterious to me.
How should I go about this? am I pointed in the wrong direction?
I've tried using the RESTAdapter - but didn't have much luck dealing with more unconventional endpoints.
Custom Adapters / Serializers ?
this article just appeared: "Fit any backend into ember with custom adapters and serializers
Full url with endpoint in question
model (just title to test)
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string')
});
route ( per #Artych )
export default Ember.Route.extend({
model() {
$.ajax({
url: endPoint,
type: 'GET',
dataType: 'jsonp'
}).then((widgetResults) => {
// modify payload to RESTAdapter
var booksObj = widgetResults.books;
var booksArray = Object.keys(booksObj).map((element) => {
var book = booksObj[element];
book.id = book.book_id;
delete book.book_id;
return book;
});
console.log(booksArray);
this.store.pushPayload({books: booksArray});
});
return this.store.peekAll('book');
}
});
template
{{#each model as |book|}}
<article>
<h1>{{book.title}}</h1>
</article>
{{/each}}
There is straightforward solution to process your payload in model():
Define book model.
Process your payload in model() hook:
model() {
$.ajax({
url: endPoint,
type: 'GET',
dataType: 'jsonp'
}).then((widgetResults) => {
// modify payload to RESTAdapter
var booksObj = widgetResults.books;
var booksArray = Object.keys(booksObj).map((element) => {
var book = booksObj[element];
book.id = book.book_id;
delete book.book_id;
return book;
});
this.store.pushPayload({books: booksArray});
});
return this.store.peekAll('book');
}
Iterate model in controller or template as usual.
Working jsbin:
ember 1.13
ember 2.0
You want a custom serializer to translate the data from that format into JSON-API. JSON-API is an extremely well thought-out structure, so well in fact that ember-data has adopted it as the default format used internally. Some of the benefits are that it defines a structure for objects themselves, separating attributes from relationships; a means for embedding or including associated resources; defines a place for errors and other metadata.
In short, for whatever you're trying to do, JSON-API probably has already done a lot of the decision-making for you. And, by subclassing from DS.JSONSerializer, you'll be mapping right into the format that ember-data needs.
To do this, you create a custom adapter using ember generate serializer books:
// app/serializers/book.js
import DS from 'ember-data';
export default DS.JSONSerializer.extend({
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
// payload will contain your example object
// You should return a JSON-API document
const doc = {};
// ...
return doc;
}
});
For your example data, the output of the normalization should look something like this:
{
"data": [
{
"type": "books",
"id": 116429012,
"attributes": {
"title": "The Book of Three (The Chronicles of Prydain Book 1)",
"author_lf": "Alexander, Lloyd",
"author_fl": "Lloyd Alexander"
}
},
{
"type": "books",
"id": 1234,
"attributes": {
}
}
],
"meta": {
"settings": {
"amazonchoice":null,
"show":{
"showCovers":null,
"showAuthors":null
},
"style":null,
"domain":"www.librarything.com",
"textsnippets":{
"by":"by",
"Tagged":"Tagged","readreview":"read review","stars":"stars"
}
}
}
};
Then do
this.get('store').findAll('books').then((books) => {
const meta = books.get('meta');
console.log(meta.settings.domain);
books.forEach((book) => {
console.log(book.get('title'));
});
});
Code is not tested, but hopefully it gets you started.
Define settings and book models. Arrange for the API to respond to the endpoint /books returning data in the format:
{
settings: { ... },
books: [
{
id: xxx,
...
}
]
}
Retrieve the data in the model hook with this.store.findAll('book').
Iterate over the books in your template with {{#each model as |book|}}.
Be default, Dart-RPC uses JSON serialisation when transferring objects (class instances) between the server and the client.
How can I use Protobuf (Protocol Buffers) serialisation instead?
Is it possible to specify the serialisation method (like a content-type) using the Accept request header?
Here's what I tried,
I've used the following .proto definition file representing a Person entity:
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
}
Which generated person.pb.dart for me using the protoc-gen-dart plugin, by running the command:
protoc person.proto --dart_out=. --plugin ./protoc-gen-dart
And some boilerplate dart-rpc code:
import 'dart:io';
import 'package:rpc/rpc.dart';
import 'person.pb.dart';
const String _API_PREFIX = '/api';
final ApiServer _apiServer =
new ApiServer(apiPrefix: _API_PREFIX, prettyPrint: true);
main() async {
_apiServer.addApi(new Cloud());
_apiServer.enableDiscoveryApi();
HttpServer server = await HttpServer.bind(InternetAddress.ANY_IP_V4, 8080);
server.listen(_apiServer.httpRequestHandler);
}
#ApiClass(version: 'v1')
class Cloud {
#ApiMethod(method: 'GET', path: 'resource/{name}')
Person getResource(String name) {
Person p = new Person()
..id = 1
..name = name
..email = 'a#a.a';
return p; // ??? p.writeToBuffer(); ???
}
}
Update
Opened a feature request: https://github.com/dart-lang/rpc/issues/62
rpc only supports JSON. You can create a feature request in the GitHub repository.