blob: 933f980319919804081dacaee942e6770d8a5320 [file] [log] [blame]
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// odata-json.js
(function (window, undefined) {
var datajs = window.datajs || {};
var odata = window.OData || {};
// Imports
var defined = datajs.defined;
var extend = datajs.extend;
var isArray = datajs.isArray;
var isDate = datajs.isDate;
var normalizeURI = datajs.normalizeURI;
var parseInt10 = datajs.parseInt10;
var contentType = odata.contentType;
var jsonLightReadPayload = odata.jsonLightReadPayload;
var formatDateTimeOffset = odata.formatDateTimeOffset;
var formatDuration = odata.formatDuration;
var formatJsonLight = odata.formatJsonLight;
var formatNumberWidth = odata.formatNumberWidth;
var getCanonicalTimezone = odata.getCanonicalTimezone;
var handler = odata.handler;
var isComplex = odata.isComplex;
var lookupComplexType = odata.lookupComplexType;
var lookupEntityType = odata.lookupEntityType;
var MAX_DATA_SERVICE_VERSION = odata.MAX_DATA_SERVICE_VERSION;
var maxVersion = odata.maxVersion;
var parseDateTime = odata.parseDateTime;
var parseDuration = odata.parseDuration;
var parseTimezone = odata.parseTimezone;
var payloadTypeOf = odata.payloadTypeOf;
var traverse = odata.traverse;
var formatJsonLightRequestPayload = odata.formatJsonLightRequestPayload;
// CONTENT START
var jsonMediaType = "application/json";
var jsonContentType = contentType(jsonMediaType);
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") {
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] };
};
// 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 dataServiceVersion = context.dataServiceVersion;
var json = (typeof text === "string") ? window.JSON.parse(text) : text;
if ((maxVersion("4.0", dataServiceVersion) === dataServiceVersion)) {
return json;
}
return undefined;
};
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 formatDateTimeOffset(this);
};
result = window.JSON.stringify(data, jsonReplacer);
} 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 window.JSON.stringify(newdata);
}
}
return undefined;
};
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 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 jsonHandler = handler(jsonParser, jsonSerializer, jsonMediaType, MAX_DATA_SERVICE_VERSION);
jsonHandler.recognizeDates = false;
odata.jsonHandler = jsonHandler;
// DATAJS INTERNAL START
odata.jsonParser = jsonParser;
odata.jsonSerializer = jsonSerializer;
odata.jsonNormalizeData = jsonNormalizeData;
odata.normalizeServiceDocument = normalizeServiceDocument;
odata.parseJsonDateString = parseJsonDateString;
// DATAJS INTERNAL END
// CONTENT END
})(this);