/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file 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. */ using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using ThirdParty.Json.LitJson; namespace Amazon.Runtime.Documents { /// /// A specialized type that is used to carry open content. A can represent primitives like /// , , and , complex objects /// (represented as a Dictionary<string,Document>) and lists of . /// /// When working with a it's necessary to first convert it to its backing type via one of the provided AsX() methods. /// Type checking can be done via IsX() method. /// /// /// /// Document Types specification specifies support for arbitrary precision integers. However, the dotnet implementation /// is limited to representing numbers as either , or . /// public partial struct Document : IEquatable, IEnumerable, IEnumerable> { private readonly bool _dataBool; private readonly double _dataDouble; private readonly int _dataInt; private readonly long _dataLong; private readonly string _dataString; private List _dataList; private Dictionary _dataDictionary; public DocumentType Type { get; private set; } #region Constructors public Document(bool value) : this() { Type = DocumentType.Bool; _dataBool = value; } public Document(double value) : this() { Type = DocumentType.Double; _dataDouble = value; } public Document(int value) : this() { Type = DocumentType.Int; _dataInt = value; } public Document(long value) : this() { Type = DocumentType.Long; _dataLong = value; } public Document(string value) : this() { Type = DocumentType.String; _dataString = value; } public Document(List values) : this() { Type = DocumentType.List; _dataList = values; } public Document(params Document[] values) : this(values.ToList()) { } public Document(Dictionary values) : this() { Type = DocumentType.Dictionary; _dataDictionary = values; } #endregion #region Implicit Conversion Operators public static implicit operator Document(bool value) => new Document(value); public static implicit operator Document(double value) => new Document(value); public static implicit operator Document(int value) => new Document(value); public static implicit operator Document(long value) => new Document(value); public static implicit operator Document(string value) => new Document(value); public static implicit operator Document(Document[] values) => new Document(values); public static implicit operator Document(Dictionary values) => new Document(values); #endregion #region Type Checking/Conversion /// /// Returns true if is /// public bool IsBool() => Type == DocumentType.Bool; /// /// Returns the Document's backing value as a . /// /// Thrown if is not public bool AsBool() { AssertIsType(DocumentType.Bool); return _dataBool; } /// /// Returns true if is /// public bool IsDictionary() => Type == DocumentType.Dictionary; /// /// Returns the Document's backing value as a . /// /// Thrown if is not public Dictionary AsDictionary() { AssertIsType(DocumentType.Dictionary); return _dataDictionary; } /// /// Returns true if is /// public bool IsDouble() => Type == DocumentType.Double; /// /// Returns the Document's backing value as a . /// /// Thrown if is not public double AsDouble() { AssertIsType(DocumentType.Double); return _dataDouble; } /// /// Returns true if is /// public bool IsInt() => Type == DocumentType.Int; /// /// Returns the Document's backing value as a . /// /// Thrown if is not public int AsInt() { AssertIsType(DocumentType.Int); return _dataInt; } /// /// Returns true if is /// public bool IsList() => Type == DocumentType.List; /// /// Returns the Document's backing value as a . /// /// Thrown if is not public List AsList() { AssertIsType(DocumentType.List); return _dataList; } /// /// Returns true if is /// public bool IsLong() => Type == DocumentType.Long; /// /// Returns the Document's backing value as a . /// /// Thrown if is not public long AsLong() { AssertIsType(DocumentType.Long); return _dataLong; } /// /// Returns true if is /// public bool IsNull() => Type == DocumentType.Null; /// /// Returns true if is /// public bool IsString() => Type == DocumentType.String; /// /// Returns the Document's backing value as a . /// /// Thrown if is not public string AsString() { AssertIsType(DocumentType.String); return _dataString; } private void AssertIsType(DocumentType type) { if (Type != type) throw new InvalidDocumentTypeConversionException(type, Type); } #endregion #region Equality public bool Equals(Document other) { if (Type != other.Type) return false; switch (Type) { case DocumentType.Null: return true; case DocumentType.Bool: return _dataBool == other.AsBool(); case DocumentType.Double: return _dataDouble.Equals(other.AsDouble()); case DocumentType.Int: return _dataInt == other.AsInt(); case DocumentType.Long: return _dataLong == other.AsLong(); case DocumentType.String: return _dataString.Equals(other.AsString()); // use reference equality case DocumentType.List: return _dataList.Equals(other.AsList()); case DocumentType.Dictionary: return _dataDictionary.Equals(other.AsDictionary()); default: return false; } } public bool Equals(Document? other) { if (!other.HasValue) return false; return Equals(other.Value); } public override bool Equals(object obj) { return Equals(obj as Document?); } public override int GetHashCode() => base.GetHashCode(); public static bool operator ==(Document left, Document right) => left.Equals(right); public static bool operator !=(Document left, Document right) => !left.Equals(right); #endregion #region Collection Initializers IEnumerator IEnumerable.GetEnumerator() { return AsList().GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { if (Type == DocumentType.List) return AsList().GetEnumerator(); if (Type == DocumentType.Dictionary) return AsDictionary().GetEnumerator(); return new[]{this}.GetEnumerator(); } /// /// This method is meant to support Collection Initializers and should not be used directly. /// Use .Add() instead. /// /// Supports: /// /// /// Thrown if is not public void Add(Document document) { // special type case to support collection initializer if (Type == DocumentType.Null) { Type = DocumentType.List; _dataList = new List(); } AssertIsType(DocumentType.List); _dataList.Add(document); } IEnumerator> IEnumerable>.GetEnumerator() { return AsDictionary().GetEnumerator(); } /// /// This method is meant to support Collection Initializers and should not be used directly. /// Use .Add() instead. /// /// Supports: /// /// /// Thrown if is not public void Add(string key, Document value) { // special type case to support collection initializer if (Type == DocumentType.Null) { _dataDictionary = new Dictionary(); Type = DocumentType.Dictionary; } AssertIsType(DocumentType.Dictionary); _dataDictionary.Add(key, value); } #endregion #region ToString public override string ToString() { switch (Type) { case DocumentType.Bool: return _dataBool.ToString(); case DocumentType.Dictionary: return "Document dictionary"; case DocumentType.Double: return _dataDouble.ToString(CultureInfo.CurrentCulture); case DocumentType.Int: return _dataInt.ToString(); case DocumentType.List: return "Document list"; case DocumentType.Long: return _dataLong.ToString(); case DocumentType.Null: return "Document null value"; case DocumentType.String: return _dataString; } return base.ToString(); } #endregion #region FromObject /// /// Convenience method for generating objects from a strongly typed /// or anonymous object. /// /// This method uses reflection to analyze and is therefor not intended /// for performance critical work. Additionally, if is a known primitive (ie ), /// using a constructor directly will be more performant. /// public static Document FromObject(object o) { IJsonWrapper jsonData = JsonMapper.ToObject(JsonMapper.ToJson(o)); return FromObject(jsonData); } private static Document FromObject(IJsonWrapper jsonData) { switch (jsonData.GetJsonType()) { case JsonType.None: return new Document(); case JsonType.Boolean: return new Document(jsonData.GetBoolean()); case JsonType.Double: return new Document(jsonData.GetDouble()); case JsonType.Int: return new Document(jsonData.GetInt()); case JsonType.Long: return new Document(jsonData.GetLong()); case JsonType.String: return new Document(jsonData.GetString()); case JsonType.Array: return new Document(jsonData.Values.OfType().Select(FromObject).ToArray()); case JsonType.Object: var dictionary = new Dictionary(); Copy(jsonData, dictionary); return new Document(dictionary); } throw new NotSupportedException($"Couldn't convert {jsonData.GetJsonType()}"); } private static void Copy(IDictionary source, Dictionary target) { foreach (var key in source.Keys) { target[key.ToString()] = FromObject((IJsonWrapper)source[key]); } } #endregion } }