* 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 { namespace LeanCloud.Test {
public class JustTest { 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}");
}
} List<string> l1 = new List<string> { "aaa" };
List<string> l2 = new List<string> { "aaa", "bbb" };
public class Dog : Animal { IEnumerable<string> l = l1.Concat(l2);
foreach (var e in l) {
} TestContext.Out.WriteLine($"{e}");
public class Walk<T> where T : Animal {
public virtual T Do() {
return default;
} }
} }
public class Run : Walk<Dog> { [Test]
public override Dog Do() { public void GenericType() {
return base.Do(); List<int> list = new List<int> { 1, 1, 2, 3, 5, 8 };
} Type type = list.GetType();
} TestContext.Out.WriteLine(type);
Type genericType = type.GetGenericTypeDefinition();
public JustTest() { 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() { public async Task Save() {
AVObject obj = AVObject.Create("Foo"); AVObject obj = AVObject.Create("Foo");
obj["content"] = "hello, world"; 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(); await obj.SaveAsync();
Assert.NotNull(obj.ObjectId); Assert.NotNull(obj.ObjectId);
Assert.NotNull(obj.CreatedAt); Assert.NotNull(obj.CreatedAt);

View File

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

View File

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

View File

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

View File

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

View File

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