lib/odata/json.js (624 lines of code) (raw):

/* * 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. */ /** @module odata/json */ var utils = require('./../utils.js'); var oDataUtils = require('./odatautils.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 isArray = utils.isArray; //var isDate = utils.isDate; var isObject = utils.isObject; //var normalizeURI = utils.normalizeURI; var parseInt10 = utils.parseInt10; var getFormatKind = utils.getFormatKind; var convertByteArrayToHexString = utils.convertByteArrayToHexString; var formatDateTimeOffset = oDataUtils.formatDateTimeOffset; var formatDuration = oDataUtils.formatDuration; var formatNumberWidth = oDataUtils.formatNumberWidth; var getCanonicalTimezone = oDataUtils.getCanonicalTimezone; var handler = oDataUtils.handler; var isComplex = oDataUtils.isComplex; var isPrimitive = oDataUtils.isPrimitive; 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 isPrimitiveEdmType = oDataUtils.isPrimitiveEdmType; var isGeographyEdmType = oDataUtils.isGeographyEdmType; var isGeometryEdmType = oDataUtils.isGeometryEdmType; 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); var jsonSerializableMetadata = ["@odata.id", "@odata.type"]; /** Extend JSON OData payload with metadata * @param handler - This handler. * @param text - Payload text (this parser also handles pre-parsed objects). * @param {Object} context - Object with parsing context. * @return An object representation of the OData payload. */ function jsonParser(handler, text, context) { var recognizeDates = defined(context.recognizeDates, handler.recognizeDates); var model = context.metadata; var json = (typeof text === "string") ? JSON.parse(text) : text; var metadataContentType; if (assigned(context.contentType) && assigned(context.contentType.properties)) { metadataContentType = context.contentType.properties["odata.metadata"]; //TODO convert to lower before comparism } var payloadFormat = getFormatKind(metadataContentType, 1); // none: 0, minimal: 1, full: 2 // No errors should be throw out if we could not parse the json payload, instead we should just return the original json object. if (payloadFormat === 0) { return json; } else if (payloadFormat === 1) { return addMinimalMetadataToJsonPayload(json, model, recognizeDates); } else if (payloadFormat === 2) { // to do: using the EDM Model to get the type of each property instead of just guessing. return addFullMetadataToJsonPayload(json, model, recognizeDates); } else { return json; } } // 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+)?\)\/$/; // 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 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; } }; /** Serializes a ODataJs payload structure to the wire format which can be send to the server * @param handler - This handler. * @param data - Data to serialize. * @param {Object} context - Object with serialization context. * @returns {String} The string representation of data. */ function jsonSerializer(handler, data, context) { 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 = formatJsonRequestPayload(data); if (newdata) { return JSON.stringify(newdata,jsonReplacer); } } return undefined; } /** Convert OData objects for serialisation in to a new data structure * @param data - Data to serialize. * @returns {String} The string representation of data. */ function formatJsonRequestPayload(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] = formatJsonRequestPayload(data[i]); } return newArrayData; } var newdata = {}; for (var property in data) { if (isJsonSerializableProperty(property)) { newdata[property] = formatJsonRequestPayload(data[property]); } } return newdata; } /** Determine form the attribute name if the attribute is a serializable property * @param attribute * @returns {boolean} */ function isJsonSerializableProperty(attribute) { if (!attribute) { return false; } if (attribute.indexOf("@odata.") == -1) { return true; } var i, len; for (i = 0, len = jsonSerializableMetadata.length; i < len; i++) { var name = jsonSerializableMetadata[i]; if (attribute.indexOf(name) != -1) { return true; } } return false; } /** Creates an object containing information for the json payload. * @param {String} kind - JSON payload kind * @param {String} type - Type name of the JSON payload. * @returns {Object} Object with kind and type fields. */ function jsonMakePayloadInfo(kind, type) { return { kind: kind, type: type || null }; } /** Add metadata to an JSON payload complex object containing full metadata * @param {Object} data - Data structure to be extended * @param {Object} model - Metadata model * @param {Boolean} recognizeDates - Flag indicating whether datetime literal strings should be converted to JavaScript Date objects. */ function addFullMetadataToJsonPayload(data, model, recognizeDates) { var type; 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) { addFullMetadataToJsonPayload(data[key][i], model, recognizeDates); } } else if (utils.isObject(data[key])) { if (data[key] !== null) { //don't step into geo.. objects type = data[key+'@odata.type']; if (!type) { //type unknown addFullMetadataToJsonPayload(data[key], model, recognizeDates); } else { type = type.substring(1); if (isGeographyEdmType(type) || isGeometryEdmType(type)) { // don't add type info for geo* types } else { addFullMetadataToJsonPayload(data[key], model, recognizeDates); } } } } else { type = data[key + '@odata.type']; // On .Net OData library, some basic EDM type is omitted, e.g. Edm.String, Edm.Int, and etc. // For the full metadata payload, we need to full fill the @data.type for each property if it is missing. // We do this is to help the OlingoJS consumers to easily get the type of each property. if (!assigned(type)) { // Guessing the "type" from the type of the value is not the right way here. // To do: we need to get the type from metadata instead of guessing. var typeFromObject = typeof data[key]; if (typeFromObject === 'string') { addType(data, key, 'String'); } else if (typeFromObject === 'boolean') { addType(data, key, 'Boolean'); } else if (typeFromObject === 'number') { if (data[key] % 1 === 0) { // has fraction addType(data, key, 'Int32'); // the biggst integer } else { addType(data, key, 'Decimal'); // the biggst float single,doulbe,decimal } } } else { if (recognizeDates) { convertDatesNoEdm(data, key, type.substring(1)); } } } } } } } return data; } /** Loop through the properties of an JSON payload object, look up the type info of the property and call * the appropriate add*MetadataToJsonPayloadObject function * @param {Object} data - Data structure to be extended * @param {String} objectInfoType - Information about the data (name,type,typename,...) * @param {String} baseURI - Base Url * @param {Object} model - Metadata model * @param {Boolean} recognizeDates - Flag indicating whether datetime literal strings should be converted to JavaScript Date objects. */ function checkProperties(data, objectInfoType, baseURI, model, 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; if (isCollectionType(property.type)) { addTypeColNoEdm(data,name,property.type.substring(11,property.type.length-1)); } else { addTypeNoEdm(data,name,property.type); } for ( var i = 0; i < propertyValue.length; i++) { addMetadataToJsonMinimalPayloadComplex(propertyValue[i], property, baseURI, model, recognizeDates); } } else if (isObject(propertyValue) && (propertyValue !== null)) { addMetadataToJsonMinimalPayloadComplex(propertyValue, property, baseURI, model, recognizeDates); } else { //data[name+'@odata.type'] = '#' + property.type; addTypeNoEdm(data,name,property.type); if (recognizeDates) { convertDates(data, name, property.type); } } } } } /** Add metadata to an JSON payload object containing minimal metadata * @param {Object} data - Json response payload object * @param {Object} model - Object describing an OData conceptual schema * @param {Boolean} recognizeDates - Flag indicating whether datetime literal strings should be converted to JavaScript Date objects. * @returns {Object} Object in the library's representation. */ function addMinimalMetadataToJsonPayload(data, model, recognizeDates) { if (!assigned(model) || isArray(model)) { return data; } var baseURI = data[contextUrlAnnotation]; var payloadInfo = createPayloadInfo(data, model); switch (payloadInfo.detectedPayloadKind) { case PAYLOADTYPE_VALUE: if (payloadInfo.type !== null) { return addMetadataToJsonMinimalPayloadEntity(data, payloadInfo, baseURI, model, recognizeDates); } else { return addTypeNoEdm(data,'value', payloadInfo.typeName); } case PAYLOADTYPE_FEED: return addMetadataToJsonMinimalPayloadFeed(data, model, payloadInfo, baseURI, recognizeDates); case PAYLOADTYPE_ENTRY: return addMetadataToJsonMinimalPayloadEntity(data, payloadInfo, baseURI, model, recognizeDates); case PAYLOADTYPE_COLLECTION: return addMetadataToJsonMinimalPayloadCollection(data, model, payloadInfo, baseURI, recognizeDates); case PAYLOADTYPE_PROPERTY: if (payloadInfo.type !== null) { return addMetadataToJsonMinimalPayloadEntity(data, payloadInfo, baseURI, model, recognizeDates); } else { return addTypeNoEdm(data,'value', payloadInfo.typeName); } case PAYLOADTYPE_SVCDOC: return data; case PAYLOADTYPE_LINKS: return data; } return data; } /** Add metadata to an JSON payload feed object containing minimal metadata * @param {Object} data - Data structure to be extended * @param {Object} model - Metadata model * @param {String} feedInfo - Information about the data (name,type,typename,...) * @param {String} baseURI - Base Url * @param {Boolean} recognizeDates - Flag indicating whether datetime literal strings should be converted to JavaScript Date objects. */ function addMetadataToJsonMinimalPayloadFeed(data, model, feedInfo, baseURI, recognizeDates) { var entries = []; var items = data.value; var i,len; var entry; for (i = 0, len = items.length; i < len; i++) { var item = items[i]; if ( defined(item['@odata.type'])) { // in case of mixed feeds var typeName = item['@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 = addMetadataToJsonMinimalPayloadEntity(item, entryInfo, baseURI, model, recognizeDates); } else { entry = addMetadataToJsonMinimalPayloadEntity(item, feedInfo, baseURI, model, recognizeDates); } entries.push(entry); } data.value = entries; return data; } /** Add metadata to an JSON payload entity object containing minimal metadata * @param {Object} data - Data structure to be extended * @param {String} objectInfo - Information about the data (name,type,typename,...) * @param {String} baseURI - Base Url * @param {Object} model - Metadata model * @param {Boolean} recognizeDates - Flag indicating whether datetime literal strings should be converted to JavaScript Date objects. */ function addMetadataToJsonMinimalPayloadEntity(data, objectInfo, baseURI, model, recognizeDates) { addType(data,'',objectInfo.typeName); var keyType = objectInfo.type; while ((defined(keyType)) && ( keyType.key === undefined) && (keyType.baseType !== undefined)) { keyType = lookupEntityType(keyType.baseType, model); } if (keyType.key !== undefined) { var lastIdSegment = objectInfo.name + jsonGetEntryKey(data, keyType); data['@odata.id'] = baseURI.substring(0, baseURI.lastIndexOf("$metadata")) + lastIdSegment; data['@odata.editLink'] = lastIdSegment; } //var serviceURI = baseURI.substring(0, baseURI.lastIndexOf("$metadata")); checkProperties(data, objectInfo.type, baseURI, model, recognizeDates); return data; } /** Add metadata to an JSON payload complex object containing minimal metadata * @param {Object} data - Data structure to be extended * @param {String} property - Information about the data (name,type,typename,...) * @param {String} baseURI - Base Url * @param {Object} model - Metadata model * @param {Boolean} recognizeDates - Flag indicating whether datetime literal strings should be converted to JavaScript Date objects. */ function addMetadataToJsonMinimalPayloadComplex(data, property, baseURI, model, recognizeDates) { var type = property.type; if (isCollectionType(property.type)) { type =property.type.substring(11,property.type.length-1); } addType(data,'',property.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, recognizeDates); } /** Add metadata to an JSON payload collection object containing minimal metadata * @param {Object} data - Data structure to be extended * @param {Object} model - Metadata model * @param {String} collectionInfo - Information about the data (name,type,typename,...) * @param {String} baseURI - Base Url * @param {Boolean} recognizeDates - Flag indicating whether datetime literal strings should be converted to JavaScript Date objects. */ function addMetadataToJsonMinimalPayloadCollection(data, model, collectionInfo, baseURI, recognizeDates) { addTypeColNoEdm(data,'', collectionInfo.typeName); if (collectionInfo.type !== null) { var entries = []; var items = data.value; var i,len; var entry; for (i = 0, len = items.length; i < len; i++) { var item = items[i]; if ( defined(item['@odata.type'])) { // in case of mixed collections var typeName = item['@odata.type'].substring(1); var type = lookupEntityType( typeName, model); var entryInfo = { contentTypeOdata : collectionInfo.contentTypeOdata, detectedPayloadKind : collectionInfo.detectedPayloadKind, name : collectionInfo.name, type : type, typeName : typeName }; entry = addMetadataToJsonMinimalPayloadEntity(item, entryInfo, baseURI, model, recognizeDates); } else { entry = addMetadataToJsonMinimalPayloadEntity(item, collectionInfo, baseURI, model, recognizeDates); } entries.push(entry); } data.value = entries; } return data; } /** Add an OData type tag to an JSON payload object * @param {Object} data - Data structure to be extended * @param {String} name - Name of the property whose type is set * @param {String} value - Type name */ function addType(data, name, value ) { var fullName = name + '@odata.type'; if ( data[fullName] === undefined) { data[fullName] = '#' + value; } } /** Add an OData type tag to an JSON payload object collection (without "Edm." namespace) * @param {Object} data - Data structure to be extended * @param {String} name - Name of the property whose type is set * @param {String} typeName - Type name */ function addTypeColNoEdm(data, name, typeName ) { var fullName = name + '@odata.type'; if ( data[fullName] === undefined) { if ( typeName.substring(0,4)==='Edm.') { data[fullName] = '#Collection('+typeName.substring(4)+ ')'; } else { data[fullName] = '#Collection('+typeName+ ')'; } } } /** Add an OData type tag to an JSON payload object (without "Edm." namespace) * @param {Object} data - Data structure to be extended * @param {String} name - Name of the property whose type is set * @param {String} value - Type name */ function addTypeNoEdm(data, name, value ) { var fullName = name + '@odata.type'; if ( data[fullName] === undefined) { if ( value.substring(0,4)==='Edm.') { data[fullName] = '#' + value.substring(4); } else { data[fullName] = '#' + value; } } return data; } /** Convert the date/time format of an property from the JSON payload object (without "Edm." namespace) * @param {Object} data - Data structure to be extended * @param propertyName - Name of the property to be changed * @param type - Type */ function convertDates(data, propertyName,type) { if (type === 'Edm.Date') { data[propertyName] = oDataUtils.parseDate(data[propertyName], true); } else if (type === 'Edm.DateTimeOffset') { data[propertyName] = oDataUtils.parseDateTimeOffset(data[propertyName], true); } else if (type === 'Edm.Duration') { data[propertyName] = oDataUtils.parseDuration(data[propertyName], true); } else if (type === 'Edm.Time') { data[propertyName] = oDataUtils.parseTime(data[propertyName], true); } } /** Convert the date/time format of an property from the JSON payload object * @param {Object} data - Data structure to be extended * @param propertyName - Name of the property to be changed * @param type - Type */ function convertDatesNoEdm(data, propertyName,type) { if (type === 'Date') { data[propertyName] = oDataUtils.parseDate(data[propertyName], true); } else if (type === 'DateTimeOffset') { data[propertyName] = oDataUtils.parseDateTimeOffset(data[propertyName], true); } else if (type === 'Duration') { data[propertyName] = oDataUtils.parseDuration(data[propertyName], true); } else if (type === 'Time') { data[propertyName] = oDataUtils.parseTime(data[propertyName], true); } } /** Formats a value according to Uri literal format * @param value - Value to be formatted. * @param type - Edm type of the value * @returns {string} Value after formatting */ function formatLiteral(value, type) { value = "" + formatRawLiteral(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; } } /** convert raw byteArray to hexString if the property is an binary property * @param value - Value to be formatted. * @param type - Edm type of the value * @returns {string} Value after formatting */ function formatRawLiteral(value, type) { switch (type) { case "Edm.Binary": return convertByteArrayToHexString(value); default: return value; } } /** Formats the given minutes into (+/-)hh:mm format. * @param {Number} minutes - Number of minutes to format. * @returns {String} The minutes in (+/-)hh:mm format. */ function minutesToOffset(minutes) { 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); } /** Parses the JSON Date representation into a Date object. * @param {String} value - String value. * @returns {Date} A Date object if the value matches one; falsy otherwise. */ function parseJsonDateString(value) { 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. } /** Creates an object containing information for the context * @param {String} fragments - Uri fragment * @param {Object} model - Object describing an OData conceptual schema * @returns {Object} type(optional) object containing type information for entity- and complex-types ( null if a typeName is a primitive) */ function parseContextUriFragment( 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("/"); var type; 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; 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; } } if (jsonIsPrimitiveType(fragment)) { ret.typeName = fragment; ret.type = null; ret.detectedPayloadKind = PAYLOADTYPE_VALUE; continue; } 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; } //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; 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) { //PAYLOADTYPE_COLLECTION ret.typeName = property.type; if (utils.startsWith(property.type, 'Collection')) { ret.detectedPayloadKind = PAYLOADTYPE_COLLECTION; var tmp12 = property.type.substring(10+1,property.type.length - 1); ret.typeName = tmp12; ret.type = lookupComplexType(tmp12, model); ret.detectedPayloadKind = PAYLOADTYPE_COLLECTION; } else { ret.type = lookupComplexType(property.type, model); ret.detectedPayloadKind = PAYLOADTYPE_PROPERTY; } ret.name = fragment; // 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; } /** Infers the information describing the JSON payload from its metadata annotation, structure, and data model. * @param {Object} data - Json response payload object. * @param {Object} model - Object describing an OData conceptual schema. * 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 payload. * @return Object with kind and type fields. Null if there is no metadata annotation or the payload info cannot be obtained.. */ function createPayloadInfo(data, model) { var metadataUri = data[contextUrlAnnotation]; if (!metadataUri || typeof metadataUri !== "string") { return null; } var fragmentStart = metadataUri.lastIndexOf("#"); if (fragmentStart === -1) { return jsonMakePayloadInfo(PAYLOADTYPE_SVCDOC); } var fragment = metadataUri.substring(fragmentStart + 1); return parseContextUriFragment(fragment,model); } /** Gets the key of an entry. * @param {Object} data - JSON entry. * @param {Object} data - EDM entity model for key loockup. * @returns {string} Entry instance key. */ function jsonGetEntryKey(data, entityModel) { 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; } /** Determines whether a type name is a primitive type in a JSON payload. * @param {String} typeName - Type name to test. * @returns {Boolean} True if the type name an EDM primitive type or an OData spatial type; false otherwise. */ function jsonIsPrimitiveType(typeName) { return isPrimitiveEdmType(typeName) || isGeographyEdmType(typeName) || isGeometryEdmType(typeName); } var jsonHandler = oDataHandler.handler(jsonParser, jsonSerializer, jsonMediaType, MAX_DATA_SERVICE_VERSION); jsonHandler.recognizeDates = false; exports.createPayloadInfo = createPayloadInfo; exports.jsonHandler = jsonHandler; exports.jsonParser = jsonParser; exports.jsonSerializer = jsonSerializer; exports.parseJsonDateString = parseJsonDateString;