blob: 43803f4b6c933a58e524eab859ca1ece21d5cf83 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* {
oldname:'odata-json.js',
updated:'20140514 12:59'
}*/
var utils = require('./../datajs.js').utils;
var oDataUtils = require('./utils.js');
var oDataHandler = require('./handler.js');
var odataNs = "odata";
var odataAnnotationPrefix = odataNs + ".";
var contextUrlAnnotation = "@" + odataAnnotationPrefix + "context";
var assigned = utils.assigned;
var defined = utils.defined;
var extend = utils.extend;
var isArray = utils.isArray;
var isDate = utils.isDate;
var isObject = utils.isObject;
var normalizeURI = utils.normalizeURI;
var parseInt10 = utils.parseInt10;
var contentType = oDataUtils.contentType;
var formatDateTimeOffset = oDataUtils.formatDateTimeOffset;
var formatDuration = oDataUtils.formatDuration;
var formatJsonLight = oDataUtils.formatJsonLight;
var formatNumberWidth = oDataUtils.formatNumberWidth;
var getCanonicalTimezone = oDataUtils.getCanonicalTimezone;
var handler = oDataUtils.handler;
var isComplex = oDataUtils.isComplex;
var isCollectionType = oDataUtils.isCollectionType;
var lookupComplexType = oDataUtils.lookupComplexType;
var lookupEntityType = oDataUtils.lookupEntityType;
var lookupSingleton = oDataUtils.lookupSingleton;
var lookupEntitySet = oDataUtils.lookupEntitySet;
var lookupDefaultEntityContainer = oDataUtils.lookupDefaultEntityContainer;
var lookupProperty = oDataUtils.lookupProperty;
var MAX_DATA_SERVICE_VERSION = oDataUtils.MAX_DATA_SERVICE_VERSION;
var maxVersion = oDataUtils.maxVersion;
var parseDateTime = oDataUtils.parseDateTime;
var parseDuration = oDataUtils.parseDuration;
var parseTimezone = oDataUtils.parseTimezone;
var payloadTypeOf = oDataUtils.payloadTypeOf;
var traverse = oDataUtils.traverse;
// CONTENT START
var PAYLOADTYPE_FEED = "f";
var PAYLOADTYPE_ENTRY = "e";
var PAYLOADTYPE_PROPERTY = "p";
var PAYLOADTYPE_COLLECTION = "c";
var PAYLOADTYPE_ENUMERATION_PROPERTY = "enum";
var PAYLOADTYPE_SVCDOC = "s";
var PAYLOADTYPE_ENTITY_REF_LINK = "erl";
var PAYLOADTYPE_ENTITY_REF_LINKS = "erls";
var PAYLOADTYPE_VALUE = "v";
var PAYLOADTYPE_DELTA = "d";
var DELTATYPE_FEED = "f";
var DELTATYPE_DELETED_ENTRY = "de";
var DELTATYPE_LINK = "l";
var DELTATYPE_DELETED_LINK = "dl";
var jsonMediaType = "application/json";
var jsonContentType = oDataHandler.contentType(jsonMediaType);
// The regular expression corresponds to something like this:
// /Date(123+60)/
//
// This first number is date ticks, the + may be a - and is optional,
// with the second number indicating a timezone offset in minutes.
//
// On the wire, the leading and trailing forward slashes are
// escaped without being required to so the chance of collisions is reduced;
// however, by the time we see the objects, the characters already
// look like regular forward slashes.
var jsonDateRE = /^\/Date\((-?\d+)(\+|-)?(\d+)?\)\/$/;
var minutesToOffset = function (minutes) {
/// <summary>Formats the given minutes into (+/-)hh:mm format.</summary>
/// <param name="minutes" type="Number">Number of minutes to format.</param>
/// <returns type="String">The minutes in (+/-)hh:mm format.</returns>
var sign;
if (minutes < 0) {
sign = "-";
minutes = -minutes;
} else {
sign = "+";
}
var hours = Math.floor(minutes / 60);
minutes = minutes - (60 * hours);
return sign + formatNumberWidth(hours, 2) + ":" + formatNumberWidth(minutes, 2);
};
var parseJsonDateString = function (value) {
/// <summary>Parses the JSON Date representation into a Date object.</summary>
/// <param name="value" type="String">String value.</param>
/// <returns type="Date">A Date object if the value matches one; falsy otherwise.</returns>
var arr = value && jsonDateRE.exec(value);
if (arr) {
// 0 - complete results; 1 - ticks; 2 - sign; 3 - minutes
var result = new Date(parseInt10(arr[1]));
if (arr[2]) {
var mins = parseInt10(arr[3]);
if (arr[2] === "-") {
mins = -mins;
}
// The offset is reversed to get back the UTC date, which is
// what the API will eventually have.
var current = result.getUTCMinutes();
result.setUTCMinutes(current - mins);
result.__edmType = "Edm.DateTimeOffset";
result.__offset = minutesToOffset(mins);
}
if (!isNaN(result.valueOf())) {
return result;
}
}
// Allow undefined to be returned.
};
// Some JSON implementations cannot produce the character sequence \/
// which is needed to format DateTime and DateTimeOffset into the
// JSON string representation defined by the OData protocol.
// See the history of this file for a candidate implementation of
// a 'formatJsonDateString' function.
var jsonParser = function (handler, text, context) {
/// <summary>Parses a JSON OData payload.</summary>
/// <param name="handler">This handler.</param>
/// <param name="text">Payload text (this parser also handles pre-parsed objects).</param>
/// <param name="context" type="Object">Object with parsing context.</param>
/// <returns>An object representation of the OData payload.</returns>
var recognizeDates = defined(context.recognizeDates, handler.recognizeDates);
var inferJsonLightFeedAsObject = defined(context.inferJsonLightFeedAsObject, handler.inferJsonLightFeedAsObject);
var model = context.metadata;
var dataServiceVersion = context.dataServiceVersion;
var json = (typeof text === "string") ? JSON.parse(text) : text;
var metadataMap = { none : 0, minimal : 1, full : 2, all : 3 };
var payloadFormat = 1;//minmal
try {
payloadFormat = metadataMap[context.contentType.properties["odata.metadata"]];//TODO convert to lower before comparism
} catch(err) {
payloadFormat = 1;
}
payloadFormat = (payloadFormat === undefined) ? 1 : payloadFormat;
var demandedFormat = 1;//minmal
try {
demandedFormat = metadataMap[context.extendMetadataToLevel]; //TODO convert to lower before comparism
} catch(err) {
demandedFormat = 1;
}
demandedFormat = (demandedFormat === undefined) ? 1 : demandedFormat;
if ( payloadFormat >= demandedFormat) {
//there is no need to add additional metadata
if (recognizeDates) {
if (payloadFormat === 0) {
//error no typeinformation in payload, conversion of dates not possible
} else if (payloadFormat === 1) { //minimal
//TODO use metadata in context to determine which properties need to be converted
} else {
return convertPrimitivetypesOnMetadataFull(json); //should be fast
}
} else {
return json;
}
} else {
if (payloadFormat === 2) {
//demandedFormat is all
//metaData=full, no metadata in context required
//insert the missing type information for strings, bool, etc.
//guess types for nummber as defined in the odata-json-format-v4.0.doc specification
return extendMetadataFromPayload(json,context,recognizeDates);
} else if (payloadFormat === 1) { //minmal
if (!utils.isArray(model)) { // array was default for model in datajsV3 3.0
//TODO use metadata in context to determine which properties need to be converted
// and extend the metadata
return extendMetadataFromContext(json,context,model,demandedFormat, recognizeDates);
} else {
//error metadata in context required, TODO: throw a to be defined exception
}
} else {
// the payload contains no context url only guessing possible
return json;
}
}
};
var extendMetadataFromContext = function(json,context, model,demandedFormat,recognizeDates) {
return jsonLightReadPayload(json, model, demandedFormat,recognizeDates, false, context.contentType.properties['odata.metadata']);
};
var convertPrimitivetypesOnMetadataFull = function(data) {
/// <summary>Converts some primitive data types in payload</summary>
/// <param name="data">Date which will be extendet</param>
/// <returns>An object representation of the OData payload.</returns>
if ( utils.isObject(data) ) {
for (var key in data) {
if (data.hasOwnProperty(key)) {
if (key.indexOf('@') === -1) {
if (utils.isArray(data[key])) {
for ( var i = 0; i < data[key].length; ++i) {
convertPrimitivetypesOnMetadataFull(data[key][i]);
}
} else if (utils.isObject(data[key])) {
if (data[key] !== null) {
convertPrimitivetypesOnMetadataFull(data[key]);
}
} else {
var type = data[key+'@odata.type'];
if ( type === undefined ) {
var typeFromObject = typeof data[key];
//TODO check the datatype
} else if ( type === '#DateTimeOffset' ) {
data[key] = oDataUtils.parseDateTimeOffset(data[key],true);
} else if ( type === '#DateTime' ) {
data[key] = oDataUtils.parseDateTimeOffset(data[key],true);
}
//TODO handle more types there
}
}
}
}
}
return data;
};
var addType = function(data, name, value ) {
var fullName = name+'@odata.type';
if ( data[fullName] === undefined) {
data[fullName] = value;
}
};
var extendMetadataFromPayload = function(data,context,recognizeDates) {
/// <summary>Adds typeinformation for String, Boolean and numerical EDM-types.
/// The type is determined from the odata-json-format-v4.0.doc specification
///</summary>
/// <param name="data">Date which will be extendet</param>
/// <param name="context" type="Object">Object with parsing context.</param>
/// <param name="recognizeDates" type="Boolean">
/// True if strings formatted as datetime values should be treated as datetime values. False otherwise.
/// </param>
/// <returns>An object representation of the OData payload.</returns>
if ( utils.isObject(data) ) {
for (var key in data) {
if (data.hasOwnProperty(key)) {
if (key.indexOf('@') === -1) {
if (utils.isArray(data[key])) {
for ( var i = 0; i < data[key].length; ++i) {
extendMetadataFromPayload(data[key][i], context, recognizeDates);
}
} else if (utils.isObject(data[key])) {
if (data[key] !== null) {
extendMetadataFromPayload(data[key],context, recognizeDates);
}
} else {
if (recognizeDates) {
var type = data[key+'@odata.type'];
if ( type === undefined ) {
var typeFromObject = typeof data[key];
//TODO check the datatype
} else if ( type === '#DateTimeOffset' ) {
data[key] = oDataUtils.parseDateTimeOffset(data[key],true);
} else if ( type === '#DateTime' ) {
data[key] = oDataUtils.parseDateTimeOffset(data[key],true);
}
//TODO handle more types there
}
var typeFromObject = typeof data[key];
if ( typeFromObject === 'string' ) {
addType(data,key,'#String');
} else if (typeFromObject ==='boolean') {
addType(data,key,'#Bool');
} else if (typeFromObject ==='number') {
if ( data[key] % 1 === 0 ) { // has fraction
addType(data,key,'#Integer');// the biggst integer
} else {
addType(data,key,'#Decimal');// the biggst float single,doulbe,decimal
}
}
}
}
}
}
}
return data;
};
/*
var jsonToString = function (data) {
/// <summary>Converts the data into a JSON string.</summary>
/// <param name="data">Data to serialize.</param>
/// <returns type="String">The JSON string representation of data.</returns>
var result; // = undefined;
// Save the current date.toJSON function
var dateToJSON = Date.prototype.toJSON;
try {
// Set our own date.toJSON function
Date.prototype.toJSON = function () {
return formatDateTimeOffsetJSON(this);
};
result = JSON.stringify(data, jsonReplacer);
result = result.replace(/\/Date\(([0-9.+-]+)\)\//g, "\\/Date($1)\\/");
} finally {
// Restore the original toJSON function
Date.prototype.toJSON = dateToJSON;
}
return result;
};*/
var jsonSerializer = function (handler, data, context) {
/// <summary>Serializes the data by returning its string representation.</summary>
/// <param name="handler">This handler.</param>
/// <param name="data">Data to serialize.</param>
/// <param name="context" type="Object">Object with serialization context.</param>
/// <returns type="String">The string representation of data.</returns>
var dataServiceVersion = context.dataServiceVersion || "4.0";
var cType = context.contentType = context.contentType || jsonContentType;
if (cType && cType.mediaType === jsonContentType.mediaType) {
context.dataServiceVersion = maxVersion(dataServiceVersion, "4.0");
var newdata = formatJsonLightRequestPayload(data);
if (newdata) {
return JSON.stringify(newdata);
}
}
return undefined;
};
var formatJsonLightRequestPayload = function (data) {
if (!data) {
return data;
}
if (isPrimitive(data)) {
return data;
}
if (isArray(data)) {
var newArrayData = [];
var i, len;
for (i = 0, len = data.length; i < len; i++) {
newArrayData[i] = formatJsonLightRequestPayload(data[i]);
}
return newArrayData;
}
var newdata = {};
for (var property in data) {
if (isJsonLightSerializableProperty(property)) {
newdata[property] = formatJsonLightRequestPayload(data[property]);
}
}
return newdata;
};
var jsonReplacer = function (_, value) {
/// <summary>JSON replacer function for converting a value to its JSON representation.</summary>
/// <param value type="Object">Value to convert.</param>
/// <returns type="String">JSON representation of the input value.</returns>
/// <remarks>
/// This method is used during JSON serialization and invoked only by the JSON.stringify function.
/// It should never be called directly.
/// </remarks>
if (value && value.__edmType === "Edm.Time") {
return formatDuration(value);
} else {
return value;
}
};
var jsonLightMakePayloadInfo = function (kind, type) {
/// <summary>Creates an object containing information for the json light payload.</summary>
/// <param name="kind" type="String">JSON light payload kind, one of the PAYLOADTYPE_XXX constant values.</param>
/// <param name="typeName" type="String">Type name of the JSON light payload.</param>
/// <returns type="Object">Object with kind and type fields.</returns>
/// <field name="kind" type="String">Kind of the JSON light payload. One of the PAYLOADTYPE_XXX constant values.</field>
/// <field name="type" type="String">Data type of the JSON light payload.</field>
return { kind: kind, type: type || null };
};
/// <summary>Creates an object containing information for the context</summary>
/// ...
/// <returns type="Object">Object with type information
/// attribute detectedPayloadKind(optional): see constants starting with PAYLOADTYPE_
/// attribute deltaKind(optional): deltainformation, one of the following valus DELTATYPE_FEED | DELTATYPE_DELETED_ENTRY | DELTATYPE_LINK | DELTATYPE_DELETED_LINK
/// attribute typeName(optional): name of the type
/// attribute type(optional): object containing type information for entity- and complex-types ( null if a typeName is a primitive)
/// </returns>
var parseContextUriFragment = function( fragments, model ) {
var ret = {};
if (fragments.indexOf('/') === -1 ) {
if (fragments.length === 0) {
// Capter 10.1
ret.detectedPayloadKind = PAYLOADTYPE_SVCDOC;
return ret;
} else if (fragments === 'Edm.Null') {
// Capter 10.15
ret.detectedPayloadKind = PAYLOADTYPE_VALUE;
ret.isNullProperty = true;
return ret;
} else if (fragments === 'Collection($ref)') {
// Capter 10.11
ret.detectedPayloadKind = PAYLOADTYPE_ENTITY_REF_LINKS;
return ret;
} else if (fragments === '$ref') {
// Capter 10.12
ret.detectedPayloadKind = PAYLOADTYPE_ENTITY_REF_LINK;
return ret;
} else {
//TODO check for navigation resource
}
}
ret.type = undefined;
ret.typeName = undefined;
var fragmentParts = fragments.split("/");
for(var i = 0; i < fragmentParts.length; ++i) {
var fragment = fragmentParts[i];
if (ret.typeName === undefined) {
//preparation
if ( fragment.indexOf('(') !== -1 ) {
//remove the query function, cut fragment to matching '('
var index = fragment.length - 2 ;
for ( var rCount = 1; rCount > 0 && index > 0; --index) {
if ( fragment.charAt(index)=='(') {
rCount --;
} else if ( fragment.charAt(index)==')') {
rCount ++;
}
}
if (index === 0) {
//TODO throw error
}
//remove the projected entity from the fragment; TODO decide if we want to store the projected entity
var inPharenthesis = fragment.substring(index+2,fragment.length - 1);
fragment = fragment.substring(0,index+1);
if (utils.startsWith(fragment, 'Collection')) {
ret.detectedPayloadKind = PAYLOADTYPE_COLLECTION;
// Capter 10.14
ret.typeName = inPharenthesis;
var type = lookupEntityType(ret.typeName, model);
if ( type !== null) {
ret.type = type;
continue;
}
type = lookupComplexType(ret.typeName, model);
if ( type !== null) {
ret.type = type;
continue;
}
ret.type = null;//in case of #Collection(Edm.String) only lastTypeName is filled
continue;
} else {
// projection: Capter 10.7, 10.8 and 10.9
ret.projection = inPharenthesis;
}
}
var container = lookupDefaultEntityContainer(model);
//check for entity
var entitySet = lookupEntitySet(container.entitySet, fragment);
if ( entitySet !== null) {
ret.typeName = entitySet.entityType;
ret.type = lookupEntityType( ret.typeName, model);
ret.name = fragment;
ret.detectedPayloadKind = PAYLOADTYPE_FEED;
// Capter 10.2
continue;
}
//check for singleton
var singleton = lookupSingleton(container.singleton, fragment);
if ( singleton !== null) {
ret.typeName = singleton.entityType;
ret.type = lookupEntityType( ret.typeName, model);
ret.name = fragment;
ret.detectedPayloadKind = PAYLOADTYPE_ENTRY;
// Capter 10.4
continue;
}
if (jsonLightIsPrimitiveType(fragment)) {
ret.typeName = fragment;
ret.type = null;
ret.detectedPayloadKind = PAYLOADTYPE_VALUE;
continue;
}
//TODO throw ERROR
} else {
//check for $entity
if (utils.endsWith(fragment, '$entity') && (ret.detectedPayloadKind === PAYLOADTYPE_FEED)) {
//TODO ret.name = fragment;
ret.detectedPayloadKind = PAYLOADTYPE_ENTRY;
// Capter 10.3 and 10.6
continue;
}
//check for derived types
if (fragment.indexOf('.') !== -1) {
// Capter 10.6
ret.typeName = fragment;
var type = lookupEntityType(ret.typeName, model);
if ( type !== null) {
ret.type = type;
continue;
}
type = lookupComplexType(ret.typeName, model);
if ( type !== null) {
ret.type = type;
continue;
}
//TODO throw ERROR invalid type
}
//check for property value
if ( ret.detectedPayloadKind === PAYLOADTYPE_FEED || ret.detectedPayloadKind === PAYLOADTYPE_ENTRY) {
var property = lookupProperty(ret.type.property, fragment);
if (property !== null) {
ret.typeName = property.type;
ret.type = lookupComplexType(ret.typeName, model);
ret.name = fragment;
ret.detectedPayloadKind = PAYLOADTYPE_PROPERTY;
// Capter 10.15
}
continue;
}
if (fragment === '$delta') {
ret.deltaKind = DELTATYPE_FEED;
continue;
} else if (utils.endsWith(fragment, '/$deletedEntity')) {
ret.deltaKind = DELTATYPE_DELETED_ENTRY;
continue;
} else if (utils.endsWith(fragment, '/$link')) {
ret.deltaKind = DELTATYPE_LINK;
continue;
} else if (utils.endsWith(fragment, '/$deletedLink')) {
ret.deltaKind = DELTATYPE_DELETED_LINK;
continue;
}
//TODO throw ERROr
}
}
return ret;
};
var jsonLightPayloadInfo = function (data, model) {
/// <summary>Infers the information describing the JSON light payload from its metadata annotation, structure, and data model.</summary>
/// <param name="data" type="Object">Json light response payload object.</param>
/// <param name="model" type="Object">Object describing an OData conceptual schema.</param>
/// <remarks>
/// If the arguments passed to the function don't convey enough information about the payload to determine without doubt that the payload is a feed then it
/// will try to use the payload object structure instead. If the payload looks like a feed (has value property that is an array or non-primitive values) then
/// the function will report its kind as PAYLOADTYPE_FEED unless the inferFeedAsComplexType flag is set to true. This flag comes from the user request
/// and allows the user to control how the library behaves with an ambigous JSON light payload.
/// </remarks>
/// <returns type="Object">
/// Object with kind and type fields. Null if there is no metadata annotation or the payload info cannot be obtained..
/// </returns>
var metadataUri = data[contextUrlAnnotation];
if (!metadataUri || typeof metadataUri !== "string") {
return null;
}
var fragmentStart = metadataUri.lastIndexOf("#");
if (fragmentStart === -1) {
return jsonLightMakePayloadInfo(PAYLOADTYPE_SVCDOC);
}
var fragment = metadataUri.substring(fragmentStart + 1);
return parseContextUriFragment(fragment,model);
};
var jsonLightReadPayload = function (data, model, demandedFormat,recognizeDates, inferFeedAsComplexType, contentTypeOdata) {
/// <summary>Converts a JSON light response payload object into its library's internal representation.</summary>
/// <param name="data" type="Object">Json light response payload object.</param>
/// <param name="model" type="Object">Object describing an OData conceptual schema.</param>
/// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
/// <param name="inferFeedAsComplexType" type="Boolean">True if a JSON light payload that looks like a feed should be reported as a complex type property instead.</param>
/// <param name="contentTypeOdata" type="string">Includes the type of json ( minimalmetadata, fullmetadata .. etc )</param>
/// <returns type="Object">Object in the library's representation.</returns>
if (!isComplex(data)) {
return data;
}
contentTypeOdata = contentTypeOdata || "minimal";
var baseURI = data[contextUrlAnnotation];
var payloadInfo = jsonLightPayloadInfo(data, model, inferFeedAsComplexType);
if (assigned(payloadInfo)) {
payloadInfo.contentTypeOdata = contentTypeOdata;
}
var typeName = null;
if (payloadInfo) {
delete data[contextUrlAnnotation];
typeName = payloadInfo.type;
switch (payloadInfo.detectedPayloadKind) {
case PAYLOADTYPE_FEED:
return jsonLightReadFeed(data, payloadInfo, baseURI, model, demandedFormat,recognizeDates);
case PAYLOADTYPE_COLLECTION:
return jsonLightReadTopCollectionProperty(data, typeName, baseURI, model, recognizeDates);
case PAYLOADTYPE_PRIMITIVE:
return jsonLightReadTopPrimitiveProperty(data, typeName, baseURI, recognizeDates);
case PAYLOADTYPE_SVCDOC:
return jsonLightReadSvcDocument(data, baseURI);
case PAYLOADTYPE_LINKS:
return jsonLightReadLinksDocument(data, baseURI);
}
}
return jsonLightReadObject(data, payloadInfo, baseURI, model, recognizeDates);
};
var jsonLightGetEntryKey = function (data, entityModel) {
/// <summary>Gets the key of an entry.</summary>
/// <param name="data" type="Object">JSON light entry.</param>
/// <param name="entityModel" type="String">Object describing the entry Model</param>
/// <returns type="string">Entry instance key.</returns>
var entityInstanceKey;
var entityKeys = entityModel.key[0].propertyRef;
var type;
entityInstanceKey = "(";
if (entityKeys.length == 1) {
type = lookupProperty(entityModel.property, entityKeys[0].name).type;
entityInstanceKey += formatLiteral(data[entityKeys[0].name], type);
} else {
var first = true;
for (var i = 0; i < entityKeys.length; i++) {
if (!first) {
entityInstanceKey += ",";
} else {
first = false;
}
type = lookupProperty(entityModel.property, entityKeys[i].name).type;
entityInstanceKey += entityKeys[i].name + "=" + formatLiteral(data[entityKeys[i].name], type);
}
}
entityInstanceKey += ")";
return entityInstanceKey;
};
var jsonLightReadFeed = function (data, feedInfo, baseURI, model, demandedFormat,recognizeDates) {
var entries = [];
var items = data.value;
for (i = 0, len = items.length; i < len; i++) {
//TODO SK check if items[i] has @odata.type and use this type instead of feedinfo
if ( items[i]['@odata.type'] !== undefined) {
var typeName = items[i]['@odata.type'].substring(1);
var type = lookupEntityType( typeName, model);
var entryInfo = {
contentTypeOdata : feedInfo.contentTypeOdata,
detectedPayloadKind : feedInfo.detectedPayloadKind,
name : feedInfo.name,
type : type,
typeName : typeName
};
entry = jsonLightReadObject(items[i], entryInfo, baseURI, model, demandedFormat,recognizeDates);
} else {
entry = jsonLightReadObject(items[i], feedInfo, baseURI, model, demandedFormat,recognizeDates);
}
entries.push(entry);
}
data.value = entries;
return data;
};
/*
var jsonNormalizeData = function (data, baseURI) {
/// <summary>
/// Normalizes the specified data into an intermediate representation.
/// like the latest supported version.
/// </summary>
/// <param name="data" optional="false">Data to update.</param>
/// <param name="baseURI" optional="false">URI to use as the base for normalizing references.</param>
var isSvcDoc = isComplex(data) && !data.__metadata && isArray(data.EntitySets);
return isSvcDoc ? normalizeServiceDocument(data, baseURI) : data;
};*/
var formatLiteral = function (value, type) {
/// <summary>Formats a value according to Uri literal format</summary>
/// <param name="value">Value to be formatted.</param>
/// <param name="type">Edm type of the value</param>
/// <returns type="string">Value after formatting</returns>
value = "" + formatRowLiteral(value, type);
value = encodeURIComponent(value.replace("'", "''"));
switch ((type)) {
case "Edm.Binary":
return "X'" + value + "'";
case "Edm.DateTime":
return "datetime" + "'" + value + "'";
case "Edm.DateTimeOffset":
return "datetimeoffset" + "'" + value + "'";
case "Edm.Decimal":
return value + "M";
case "Edm.Guid":
return "guid" + "'" + value + "'";
case "Edm.Int64":
return value + "L";
case "Edm.Float":
return value + "f";
case "Edm.Double":
return value + "D";
case "Edm.Geography":
return "geography" + "'" + value + "'";
case "Edm.Geometry":
return "geometry" + "'" + value + "'";
case "Edm.Time":
return "time" + "'" + value + "'";
case "Edm.String":
return "'" + value + "'";
default:
return value;
}
};
var formatRowLiteral = function (value, type) {
switch (type) {
case "Edm.Binary":
return convertByteArrayToHexString(value);
default:
return value;
}
};
var checkProperties = function(data,objectInfoType,baseURI,model, demandedFormat, recognizeDates) {
for (var name in data) {
if (name.indexOf("@") === -1) {
var curType = objectInfoType;
var propertyValue = data[name];
var property = lookupProperty(curType.property,name); //TODO SK add check for parent type
while (( property === null) && (curType.baseType !== undefined)) {
curType = lookupEntityType(curType.baseType, model);
property = lookupProperty(curType.property,name);
}
if ( isArray(propertyValue)) {
data[name+'@odata.type'] = '#' + property.type;
for ( var i = 0; i < propertyValue.length; i++) {
jsonLightReadComplexObject(propertyValue[0], property,baseURI,model,demandedFormat, recognizeDates);
}
} else if (isObject(propertyValue) && (propertyValue !== null)) {
jsonLightReadComplexObject(propertyValue, property,baseURI,model,demandedFormat, recognizeDates);
} else {
if (demandedFormat === 3) {
data[name+'@odata.type'] = '#' + property.type;
} else {
if ( property.type != "Edm.String" &&
property.type != "Edm.Boolean" &&
property.type != "Edm.Int32" &&
property.type != "Edm.Single" &&
property.type != "Edm.Double" ) {
data[name+'@odata.type'] = '#' + property.type;
}
}
}
}
}
};
var jsonLightReadComplexObject = function (data, property, baseURI, model, demandedFormat, recognizeDates) {
var type = property.type;
if (isCollectionType(property.type)) {
type =property.type.substring(11,property.type.length-1);
}
data['@odata.type'] = '#'+type;
var propertyType = lookupComplexType(type, model);
if (propertyType === null) {
return; //TODO check what to do if the type is not known e.g. type #GeometryCollection
}
checkProperties(data,propertyType ,baseURI,model, demandedFormat, recognizeDates);
};
var jsonLightReadObject = function (data, objectInfo, baseURI, model, demandedFormat, recognizeDates) {
//var obj = {};
data['@odata.type'] = '#'+objectInfo.typeName;
var keyType = objectInfo.type;
while (( keyType.key === undefined) && (keyType.baseType !== undefined)) {
keyType = lookupEntityType(keyType.baseType, model);
}
var lastIdSegment = objectInfo.name + jsonLightGetEntryKey(data, keyType);
data['@odata.id'] = baseURI.substring(0, baseURI.lastIndexOf("$metadata")) + lastIdSegment;
data['@odata.editLink'] = lastIdSegment;
var serviceURI = baseURI.substring(0, baseURI.lastIndexOf("$metadata"));
//jsonLightComputeUrisIfMissing(data, entryInfo, actualType, serviceURI, dataModel, baseTypeModel);
checkProperties(data,objectInfo.type,baseURI,model, demandedFormat, recognizeDates);
return data;
};
var jsonHandler = oDataHandler.handler(jsonParser, jsonSerializer, jsonMediaType, MAX_DATA_SERVICE_VERSION);
jsonHandler.recognizeDates = false;
jsonHandler.useJsonLight = true;
jsonHandler.inferJsonLightFeedAsObject = false;
exports.jsonHandler = jsonHandler;
// DATAJS INTERNAL START
exports.jsonParser = jsonParser;
exports.jsonSerializer = jsonSerializer;
exports.parseJsonDateString = parseJsonDateString;
exports.jsonLightPayloadInfo = jsonLightPayloadInfo;
// DATAJS INTERNAL END
//exports.jsonNormalizeData = jsonNormalizeData;
//exports.normalizeServiceDocument = normalizeServiceDocument;
/*
var jsonReadAdvertisedActionsOrFunctions = function (value) {
/// <summary>Reads and object containing action or function metadata and maps them into a single array of objects.</summary>
/// <param name="value" type="Object">Object containing action or function metadata.</param>
/// <returns type="Array">Array of objects containing metadata for the actions or functions specified in value.</returns>
var result = [];
for (var name in value) {
var i, len;
for (i = 0, len = value[name].length; i < len; i++) {
result.push(extend({ metadata: name }, value[name][i]));
}
}
return result;
};*/
/*
var jsonApplyMetadata = function (value, metadata, dateParser, recognizeDates) {
/// <summary>Applies metadata coming from both the payload and the metadata object to the value.</summary>
/// <param name="value" type="Object">Data on which the metada is going to be applied.</param>
/// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
/// <param name="dateParser" type="function">Function used for parsing datetime values.</param>
/// <param name="recognizeDates" type="Boolean">
/// True if strings formatted as datetime values should be treated as datetime values. False otherwise.
/// </param>
/// <returns type="Object">Transformed data.</returns>
if (value && typeof value === "object") {
var dataTypeName;
var valueMetadata = value.__metadata;
if (valueMetadata) {
if (valueMetadata.actions) {
valueMetadata.actions = jsonReadAdvertisedActionsOrFunctions(valueMetadata.actions);
}
if (valueMetadata.functions) {
valueMetadata.functions = jsonReadAdvertisedActionsOrFunctions(valueMetadata.functions);
}
dataTypeName = valueMetadata && valueMetadata.type;
}
var dataType = lookupEntityType(dataTypeName, metadata) || lookupComplexType(dataTypeName, metadata);
var propertyValue;
if (dataType) {
var properties = dataType.property;
if (properties) {
var i, len;
for (i = 0, len = properties.length; i < len; i++) {
var property = properties[i];
var propertyName = property.name;
propertyValue = value[propertyName];
if (property.type === "Edm.DateTime" || property.type === "Edm.DateTimeOffset") {
if (propertyValue) {
propertyValue = dateParser(propertyValue);
if (!propertyValue) {
throw { message: "Invalid date/time value" };
}
value[propertyName] = propertyValue;
}
} else if (property.type === "Edm.Time") {
if (propertyValue) {
value[propertyName] = parseDuration(propertyValue);
}
}
}
}
} else if (recognizeDates) {
for (var name in value) {
propertyValue = value[name];
if (typeof propertyValue === "string") {
value[name] = dateParser(propertyValue) || propertyValue;
}
}
}
}
return value;
};*/
/*
var isJsonLight = function (contentType) {
/// <summary>Tests where the content type indicates a json light payload.</summary>
/// <param name="contentType">Object with media type and properties dictionary.</param>
/// <returns type="Boolean">True is the content type indicates a json light payload. False otherwise.</returns>
if (contentType) {
var odata = contentType.properties["odata.metadata"];
return odata === "none" || odata === "minimal" || odata === "full";
}
return false;
};*/
/*
var normalizeServiceDocument = function (data, baseURI) {
/// <summary>Normalizes a JSON service document to look like an ATOM service document.</summary>
/// <param name="data" type="Object">Object representation of service documents as deserialized.</param>
/// <param name="baseURI" type="String">Base URI to resolve relative URIs.</param>
/// <returns type="Object">An object representation of the service document.</returns>
var workspace = { collections: [] };
var i, len;
for (i = 0, len = data.EntitySets.length; i < len; i++) {
var title = data.EntitySets[i];
var collection = {
title: title,
href: normalizeURI(title, baseURI)
};
workspace.collections.push(collection);
}
return { workspaces: [workspace] };
};*/