* JustTest.cs: chore: 优化 AVObject 编解码

* AVClient.cs:
* AVObject.cs:
* AVObject2.cs:
* ObjectControllerTests.cs:
* AVDecoder.cs:
* AVEncoder.cs:
* MutableObjectState.cs:
oneRain 2019-09-18 17:23:49 +08:00
parent 1823dd974b
commit e5aa736805
8 changed files with 187 additions and 229 deletions

View File

@ -1,28 +1,41 @@
using System;
using NUnit.Framework;
using System;
using System.Linq;
using System.Collections.Generic;
namespace LeanCloud.Test {
public class JustTest {
public class Animal {
[Test]
public void Concat() {
Dictionary<string, string> d1 = new Dictionary<string, string> {
{ "aaa", "111" }
};
Dictionary<string, string> d2 = new Dictionary<string, string> {
{ "aaa", "222" },
{ "ccc", "333" }
};
IEnumerable<KeyValuePair<string, string>> d = d1.Concat(d2);
foreach (var e in d) {
TestContext.Out.WriteLine($"{e.Key} : {e.Value}");
}
}
public class Dog : Animal {
}
public class Walk<T> where T : Animal {
public virtual T Do() {
return default;
List<string> l1 = new List<string> { "aaa" };
List<string> l2 = new List<string> { "aaa", "bbb" };
IEnumerable<string> l = l1.Concat(l2);
foreach (var e in l) {
TestContext.Out.WriteLine($"{e}");
}
}
public class Run : Walk<Dog> {
public override Dog Do() {
return base.Do();
}
}
public JustTest() {
[Test]
public void GenericType() {
List<int> list = new List<int> { 1, 1, 2, 3, 5, 8 };
Type type = list.GetType();
TestContext.Out.WriteLine(type);
Type genericType = type.GetGenericTypeDefinition();
TestContext.Out.WriteLine(genericType);
TestContext.Out.WriteLine(typeof(IList<>));
TestContext.Out.WriteLine(typeof(List<>));
}
}
}

View File

@ -15,6 +15,11 @@ namespace LeanCloud.Test {
public async Task Save() {
AVObject obj = AVObject.Create("Foo");
obj["content"] = "hello, world";
obj["list"] = new List<int> { 1, 1, 2, 3, 5, 8 };
obj["dict"] = new Dictionary<string, int> {
{ "hello", 1 },
{ "world", 2 }
};
await obj.SaveAsync();
Assert.NotNull(obj.ObjectId);
Assert.NotNull(obj.CreatedAt);

View File

@ -4,17 +4,13 @@ using System.Collections.Generic;
using System.Globalization;
using LeanCloud.Utilities;
namespace LeanCloud.Storage.Internal
{
public class AVDecoder
{
namespace LeanCloud.Storage.Internal {
public class AVDecoder {
// This class isn't really a Singleton, but since it has no state, it's more efficient to get
// the default instance.
private static readonly AVDecoder instance = new AVDecoder();
public static AVDecoder Instance
{
get
{
public static AVDecoder Instance {
get {
return instance;
}
}
@ -22,143 +18,78 @@ namespace LeanCloud.Storage.Internal
// Prevent default constructor.
private AVDecoder() { }
public object Decode(object data)
{
if (data == null)
{
return null;
}
var dict = data as IDictionary<string, object>;
if (dict != null)
{
if (dict.ContainsKey("__op"))
{
public object Decode(object data) {
// 如果是字典类型
if (data is IDictionary<string, object> dict) {
if (dict.ContainsKey("__op")) {
return AVFieldOperations.Decode(dict);
}
object type;
dict.TryGetValue("__type", out type);
var typeString = type as string;
if (typeString == null)
{
var newDict = new Dictionary<string, object>();
foreach (var pair in dict)
{
newDict[pair.Key] = Decode(pair.Value);
if (dict.TryGetValue("__type", out object type)) {
string typeString = type as string;
switch (typeString) {
case "Date":
return ParseDate(dict["iso"] as string);
case "Bytes":
return Convert.FromBase64String(dict["base64"] as string);
case "Pointer": {
if (dict.Keys.Count > 3) {
return DecodeAVObject(dict);
}
return DecodePointer(dict["className"] as string, dict["objectId"] as string);
}
case "GeoPoint":
return new AVGeoPoint(Conversion.To<double>(dict["latitude"]),
Conversion.To<double>(dict["longitude"]));
case "Object":
return DecodeAVObject(dict);
case "Relation":
return AVRelationBase.CreateRelation(null, null, dict["className"] as string);
default:
break;
}
return newDict;
}
if (typeString == "Date")
{
return ParseDate(dict["iso"] as string);
}
if (typeString == "Bytes")
{
return Convert.FromBase64String(dict["base64"] as string);
}
if (typeString == "Pointer")
{
//set a include key to fetch or query.
if (dict.Keys.Count > 3)
{
return DecodeAVObject(dict);
}
return DecodePointer(dict["className"] as string, dict["objectId"] as string);
}
if (typeString == "File")
{
return DecodeAVFile(dict);
}
if (typeString == "GeoPoint")
{
return new AVGeoPoint(Conversion.To<double>(dict["latitude"]),
Conversion.To<double>(dict["longitude"]));
}
if (typeString == "Object")
{
return DecodeAVObject(dict);
}
if (typeString == "Relation")
{
return AVRelationBase.CreateRelation(null, null, dict["className"] as string);
}
var converted = new Dictionary<string, object>();
foreach (var pair in dict)
{
foreach (var pair in dict) {
converted[pair.Key] = Decode(pair.Value);
}
return converted;
}
var list = data as IList<object>;
if (list != null)
{
// 如果是数组类型
if (data is IList<object> list) {
return (from item in list
select Decode(item)).ToList();
}
// 原样返回
return data;
}
protected virtual object DecodePointer(string className, string objectId)
{
if (className == "_File")
{
return AVFile.CreateWithoutData("_File", objectId);
}
protected virtual object DecodePointer(string className, string objectId) {
return AVObject.CreateWithoutData(className, objectId);
}
protected virtual object DecodeAVObject(IDictionary<string, object> dict)
{
protected virtual object DecodeAVObject(IDictionary<string, object> dict) {
var className = dict["className"] as string;
if (className == "_File")
{
return DecodeAVFile(dict);
}
var state = AVObjectCoder.Instance.Decode(dict, this);
return AVObject.FromState<AVObject>(state, dict["className"] as string);
}
protected virtual object DecodeAVFile(IDictionary<string, object> dict)
{
var objectId = dict["objectId"] as string;
var file = AVFile.CreateWithoutData("_File", objectId);
//file.MergeFromJSON(dict);
return file;
return AVObject.FromState<AVObject>(state, className);
}
public virtual IList<T> DecodeList<T>(object data)
{
public virtual IList<T> DecodeList<T>(object data) {
IList<T> rtn = null;
var list = (IList<object>)data;
if (list != null)
{
if (list != null) {
rtn = new List<T>();
foreach (var item in list)
{
foreach (var item in list) {
rtn.Add((T)item);
}
}
return rtn;
}
public static DateTime ParseDate(string input)
{
var rtn = DateTime.ParseExact(input,
public static DateTime ParseDate(string input) {
return DateTime.ParseExact(input,
AVClient.DateFormatStrings,
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal);
return rtn;
}
}
}

View File

@ -5,29 +5,19 @@ using System.Linq;
using LeanCloud.Utilities;
using LeanCloud.Storage.Internal;
namespace LeanCloud.Storage.Internal
{
namespace LeanCloud.Storage.Internal {
/// <summary>
/// A <c>AVEncoder</c> can be used to transform objects such as <see cref="AVObject"/> into JSON
/// data structures.
/// </summary>
/// <seealso cref="AVDecoder"/>
public abstract class AVEncoder
{
#if UNITY
private static readonly bool isCompiledByIL2CPP = AppDomain.CurrentDomain.FriendlyName.Equals("IL2CPP Root Domain");
#else
private static readonly bool isCompiledByIL2CPP = false;
#endif
public static bool IsValidType(object value)
{
public abstract class AVEncoder {
public static bool IsValidType(object value) {
return value == null ||
ReflectionHelpers.IsPrimitive(value.GetType()) ||
value is string ||
value is AVObject ||
value is AVACL ||
value is AVFile ||
value is AVGeoPoint ||
value is AVRelationBase ||
value is DateTime ||
@ -36,77 +26,46 @@ namespace LeanCloud.Storage.Internal
Conversion.As<IList<object>>(value) != null;
}
public object Encode(object value)
{
public object Encode(object value) {
// If this object has a special encoding, encode it and return the
// encoded object. Otherwise, just return the original object.
if (value is DateTime)
{
return new Dictionary<string, object>
{
{
"iso", ((DateTime)value).ToUniversalTime().ToString(AVClient.DateFormatStrings.First(), CultureInfo.InvariantCulture)
},
{
"__type", "Date"
}
if (value is DateTime) {
return new Dictionary<string, object> {
{ "__type", "Date" },
{ "iso", ((DateTime)value).ToUniversalTime().ToString(AVClient.DateFormatStrings.First(), CultureInfo.InvariantCulture) }
};
}
if (value is AVFile)
{
var file = value as AVFile;
return new Dictionary<string, object>
{
{"__type", "Pointer"},
{ "className", "_File"},
{ "objectId", file.ObjectId}
if (value is byte[] bytes) {
return new Dictionary<string, object> {
{ "__type", "Bytes" },
{ "base64", Convert.ToBase64String(bytes) }
};
}
var bytes = value as byte[];
if (bytes != null)
{
return new Dictionary<string, object>
{
{ "__type", "Bytes"},
{ "base64", Convert.ToBase64String(bytes)}
};
}
var obj = value as AVObject;
if (obj != null)
{
if (value is AVObject obj) {
return EncodeAVObject(obj);
}
var jsonConvertible = value as IJsonConvertible;
if (jsonConvertible != null)
{
if (value is IJsonConvertible jsonConvertible) {
return jsonConvertible.ToJSON();
}
var dict = Conversion.As<IDictionary<string, object>>(value);
if (dict != null)
{
var json = new Dictionary<string, object>();
foreach (var pair in dict)
{
json[pair.Key] = Encode(pair.Value);
}
return json;
}
//var dict = Conversion.As<IDictionary<string, object>>(value);
//if (dict != null) {
// var json = new Dictionary<string, object>();
// foreach (var pair in dict) {
// json[pair.Key] = Encode(pair.Value);
// }
// return json;
//}
var list = Conversion.As<IList<object>>(value);
if (list != null)
{
return EncodeList(list);
}
//var list = Conversion.As<IList<object>>(value);
//if (list != null) {
// return EncodeList(list);
//}
// TODO (hallucinogen): convert IAVFieldOperation to IJsonConvertible
var operation = value as IAVFieldOperation;
if (operation != null)
{
if (value is IAVFieldOperation operation) {
return operation.Encode();
}
@ -115,19 +74,10 @@ namespace LeanCloud.Storage.Internal
protected abstract IDictionary<string, object> EncodeAVObject(AVObject value);
private object EncodeList(IList<object> list)
{
var newArray = new List<object>();
// We need to explicitly cast `list` to `List<object>` rather than
// `IList<object>` because IL2CPP is stricter than the usual Unity AOT compiler pipeline.
if (isCompiledByIL2CPP && list.GetType().IsArray)
{
list = new List<object>(list);
}
foreach (var item in list)
{
if (!IsValidType(item))
{
private object EncodeList(IList<object> list) {
List<object> newArray = new List<object>();
foreach (object item in list) {
if (!IsValidType(item)) {
throw new ArgumentException("Invalid type for value in an array");
}
newArray.Add(Encode(item));

View File

@ -10,17 +10,9 @@ namespace LeanCloud.Storage.Internal {
public DateTime? UpdatedAt { get; set; }
public DateTime? CreatedAt { get; set; }
// Initialize serverData to avoid further null checking.
private IDictionary<string, object> serverData = new Dictionary<string, object>();
public IDictionary<string, object> ServerData {
get {
return serverData;
}
set {
serverData = value;
}
}
get; set;
} = new Dictionary<string, object>();
public object this[string key] {
get {
@ -35,8 +27,7 @@ namespace LeanCloud.Storage.Internal {
public void Apply(IDictionary<string, IAVFieldOperation> operationSet) {
// Apply operationSet
foreach (var pair in operationSet) {
object oldValue;
ServerData.TryGetValue(pair.Key, out oldValue);
ServerData.TryGetValue(pair.Key, out object oldValue);
var newValue = pair.Value.Apply(oldValue, pair.Key);
if (newValue != AVDeleteOperation.DeleteToken) {
ServerData[pair.Key] = newValue;

View File

@ -118,10 +118,14 @@ namespace LeanCloud {
});
}
public static void SetServerURL(string serverUrl) {
CurrentConfiguration.ApiServer = serverUrl;
CurrentConfiguration.EngineServer = serverUrl;
CurrentConfiguration.RTMServer = serverUrl;
public static void Initialize(string applicationId, string applicationKey, string serverUrl) {
Initialize(new Configuration {
ApplicationId = applicationId,
ApplicationKey = applicationKey,
ApiServer = serverUrl,
EngineServer = serverUrl,
RTMServer = serverUrl
});
}
internal static Action<string> LogTracker { get; private set; }

View File

@ -8,6 +8,7 @@ using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Collections;
using System.Collections.Concurrent;
namespace LeanCloud {
/// <summary>
@ -971,8 +972,7 @@ string propertyName
List<string> appliedKeys = new List<string>();
lock (mutex) {
foreach (var pair in operations) {
object oldValue;
map.TryGetValue(pair.Key, out oldValue);
map.TryGetValue(pair.Key, out object oldValue);
var newValue = pair.Value.Apply(oldValue, pair.Key);
if (newValue != AVDeleteOperation.DeleteToken) {
map[pair.Key] = newValue;

View File

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Collections;
namespace Storage.Public {
public class AVObject2 : IDictionary<string, object> {
ConcurrentDictionary<string, object> data;
public object this[string key] { get => ((IDictionary<string, object>)data)[key]; set => ((IDictionary<string, object>)data)[key] = value; }
public ICollection<string> Keys => ((IDictionary<string, object>)data).Keys;
public ICollection<object> Values => ((IDictionary<string, object>)data).Values;
public int Count => ((IDictionary<string, object>)data).Count;
public bool IsReadOnly => ((IDictionary<string, object>)data).IsReadOnly;
public void Add(string key, object value) {
((IDictionary<string, object>)data).Add(key, value);
}
public void Add(KeyValuePair<string, object> item) {
((IDictionary<string, object>)data).Add(item);
}
public void Clear() {
((IDictionary<string, object>)data).Clear();
}
public bool Contains(KeyValuePair<string, object> item) {
return ((IDictionary<string, object>)data).Contains(item);
}
public bool ContainsKey(string key) {
return ((IDictionary<string, object>)data).ContainsKey(key);
}
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex) {
((IDictionary<string, object>)data).CopyTo(array, arrayIndex);
}
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() {
return ((IDictionary<string, object>)data).GetEnumerator();
}
public bool Remove(string key) {
return ((IDictionary<string, object>)data).Remove(key);
}
public bool Remove(KeyValuePair<string, object> item) {
return ((IDictionary<string, object>)data).Remove(item);
}
public bool TryGetValue(string key, out object value) {
return ((IDictionary<string, object>)data).TryGetValue(key, out value);
}
IEnumerator IEnumerable.GetEnumerator() {
return ((IDictionary<string, object>)data).GetEnumerator();
}
}
}