diff --git a/backend/Squidex.ruleset b/backend/Squidex.ruleset
index 5aae5da01..1f62259db 100644
--- a/backend/Squidex.ruleset
+++ b/backend/Squidex.ruleset
@@ -86,6 +86,7 @@
+
diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs
index 680cac80e..4f7d0cf70 100644
--- a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs
+++ b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs
@@ -25,6 +25,11 @@ namespace Squidex.Domain.Apps.Core.Contents
{
}
+ protected ContentData(ContentData source, IEqualityComparer comparer)
+ : base(source, comparer)
+ {
+ }
+
protected ContentData(int capacity, IEqualityComparer comparer)
: base(capacity, comparer)
{
diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs
index d00c21552..35859d863 100644
--- a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs
+++ b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs
@@ -19,6 +19,16 @@ namespace Squidex.Domain.Apps.Core.Contents
{
}
+ public ContentFieldData(ContentFieldData source)
+ : base(source, StringComparer.OrdinalIgnoreCase)
+ {
+ }
+
+ public ContentFieldData(int capacity)
+ : base(capacity, StringComparer.OrdinalIgnoreCase)
+ {
+ }
+
public ContentFieldData AddValue(object? value)
{
return AddJsonValue(JsonValue.Create(value));
@@ -52,6 +62,18 @@ namespace Squidex.Domain.Apps.Core.Contents
return this;
}
+ public ContentFieldData Clone()
+ {
+ var clone = new ContentFieldData(Count);
+
+ foreach (var (key, value) in this)
+ {
+ clone[key] = value?.Clone()!;
+ }
+
+ return clone;
+ }
+
public override bool Equals(object? obj)
{
return Equals(obj as ContentFieldData);
diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/NamedContentData.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/NamedContentData.cs
index aea3e31e7..6e55de3dc 100644
--- a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/NamedContentData.cs
+++ b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/NamedContentData.cs
@@ -17,6 +17,11 @@ namespace Squidex.Domain.Apps.Core.Contents
{
}
+ public NamedContentData(NamedContentData source)
+ : base(source, StringComparer.Ordinal)
+ {
+ }
+
public NamedContentData(int capacity)
: base(capacity, StringComparer.Ordinal)
{
@@ -46,6 +51,18 @@ namespace Squidex.Domain.Apps.Core.Contents
return this;
}
+ public NamedContentData Clone()
+ {
+ var clone = new NamedContentData(Count);
+
+ foreach (var (key, value) in this)
+ {
+ clone[key] = value?.Clone()!;
+ }
+
+ return clone;
+ }
+
public bool Equals(NamedContentData other)
{
return base.Equals(other);
diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs
index 9af19f67f..aa7ced7d9 100644
--- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs
+++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs
@@ -11,7 +11,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
{
public static class Fields
{
- public static RootField Array(long id, string name, Partitioning partitioning, params NestedField[] fields)
+ public static ArrayField Array(long id, string name, Partitioning partitioning, params NestedField[] fields)
{
return new ArrayField(id, name, partitioning, fields);
}
diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs
index ebfd7ec2e..4575de073 100644
--- a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs
+++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs
@@ -5,35 +5,45 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
-using System;
-using System.Collections.Generic;
+using System.Linq;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
+using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ConvertContent
{
public static class ContentConverter
{
- private static readonly Func KeyNameResolver = f => f.Name;
- private static readonly Func KeyIdResolver = f => f.Id;
-
public static NamedContentData ConvertId2Name(this IdContentData content, Schema schema, params FieldConverter[] converters)
{
Guard.NotNull(schema);
var result = new NamedContentData(content.Count);
- return ConvertInternal(content, result, schema.FieldsById, KeyNameResolver, converters);
- }
+ foreach (var (fieldId, data) in content)
+ {
+ if (data == null || !schema.FieldsById.TryGetValue(fieldId, out var field))
+ {
+ continue;
+ }
- public static IdContentData ConvertId2Id(this IdContentData content, Schema schema, params FieldConverter[] converters)
- {
- Guard.NotNull(schema);
+ ContentFieldData? newData = data;
- var result = new IdContentData(content.Count);
+ ConvertArray(newData, field, FieldIdentifier.ById, FieldIdentifier.ByName);
+
+ if (newData != null)
+ {
+ newData = ConvertData(converters, field, newData);
+ }
+
+ if (newData != null)
+ {
+ result.Add(field.Name, newData);
+ }
+ }
- return ConvertInternal(content, result, schema.FieldsById, KeyIdResolver, converters);
+ return result;
}
public static NamedContentData ConvertName2Name(this NamedContentData content, Schema schema, params FieldConverter[] converters)
@@ -42,60 +52,117 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
var result = new NamedContentData(content.Count);
- return ConvertInternal(content, result, schema.FieldsByName, KeyNameResolver, converters);
+ foreach (var (fieldName, data) in content)
+ {
+ if (data == null || !schema.FieldsByName.TryGetValue(fieldName, out var field))
+ {
+ continue;
+ }
+
+ ContentFieldData? newData = data;
+
+ if (newData != null)
+ {
+ newData = ConvertData(converters, field, newData);
+ }
+
+ if (newData != null)
+ {
+ result.Add(field.Name, newData);
+ }
+ }
+
+ return result;
}
- public static IdContentData ConvertName2Id(this NamedContentData content, Schema schema, params FieldConverter[] converters)
+ public static IdContentData ConvertName2IdCloned(this NamedContentData content, Schema schema, params FieldConverter[] converters)
{
Guard.NotNull(schema);
var result = new IdContentData(content.Count);
- return ConvertInternal(content, result, schema.FieldsByName, KeyIdResolver, converters);
+ foreach (var (fieldName, data) in content)
+ {
+ if (data == null || !schema.FieldsByName.TryGetValue(fieldName, out var field))
+ {
+ continue;
+ }
+
+ ContentFieldData? newData = data.Clone();
+
+ if (newData != null)
+ {
+ newData = ConvertData(converters, field, newData);
+ }
+
+ if (newData != null)
+ {
+ ConvertArray(newData, field, FieldIdentifier.ByName, FieldIdentifier.ById);
+ }
+
+ if (newData != null)
+ {
+ result.Add(field.Id, newData);
+ }
+ }
+
+ return result;
}
- private static TDict2 ConvertInternal(
- TDict1 source,
- TDict2 target,
- IReadOnlyDictionary fields,
- Func targetKey, params FieldConverter[] converters)
- where TDict1 : IDictionary
- where TDict2 : IDictionary
- where TKey1 : notnull
- where TKey2 : notnull
+ private static ContentFieldData? ConvertData(FieldConverter[] converters, IRootField field, ContentFieldData data)
{
- foreach (var (fieldName, value) in source)
+ if (converters != null)
{
- if (!fields.TryGetValue(fieldName, out var field))
+ for (var i = 0; i < converters.Length; i++)
{
- continue;
+ data = converters[i](data!, field)!;
+
+ if (data == null)
+ {
+ break;
+ }
}
+ }
- var newValue = value;
+ return data;
+ }
- if (newValue != null)
+ private static void ConvertArray(ContentFieldData data, IRootField? field, FieldIdentifier sourceIdentifier, FieldIdentifier targetIdentifier)
+ {
+ if (field is IArrayField arrayField)
+ {
+ foreach (var (key, value) in data)
{
- if (converters != null)
+ if (value is JsonArray array)
{
- foreach (var converter in converters)
+ foreach (var nested in array.OfType())
{
- newValue = converter(newValue, field);
+ var properties = nested.ToList();
- if (newValue == null)
+ nested.Clear();
+
+ foreach (var (nestedKey, nestedValue) in properties)
{
- break;
+ if (nestedValue == null)
+ {
+ continue;
+ }
+
+ var nestedField = sourceIdentifier.GetField(arrayField, nestedKey);
+
+ if (nestedField == null)
+ {
+ continue;
+ }
+
+ var targetKey = targetIdentifier.GetStringKey(nestedField);
+
+ nested[targetKey] = nestedValue;
}
}
}
}
-
- if (newValue != null)
- {
- target.Add(targetKey(field), newValue);
- }
}
-
- return target;
}
}
-}
+}
\ No newline at end of file
diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs
index 9fe0a8061..d0a42eda3 100644
--- a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs
+++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs
@@ -23,162 +23,51 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
public static class FieldConverters
{
- private delegate string FieldKeyResolver(IField field);
+ public static readonly FieldConverter Noop = (data, field) => data;
- private static readonly FieldKeyResolver KeyNameResolver = f => f.Name;
- private static readonly FieldKeyResolver KeyIdResolver = f => f.Id.ToString();
-
- private delegate IField? FieldResolver(IArrayField field, string key);
-
- private static IField? FieldByIdResolver(IArrayField array, string key)
+ public static readonly FieldConverter ExcludeHidden = (data, field) =>
{
- if (key != null && long.TryParse(key, out var id))
- {
- return array.FieldsById.GetOrDefault(id);
- }
-
- return null;
- }
+ return field.IsForApi() ? data : null;
+ };
- private static IField? FieldByNameResolver(IArrayField array, string key)
+ public static readonly FieldConverter ExcludeChangedTypes = (data, field) =>
{
- if (key != null)
+ foreach (var value in data.Values)
{
- return array.FieldsByName.GetOrDefault(key);
- }
-
- return null;
- }
-
- public static FieldConverter ExcludeHidden()
- {
- return (data, field) => !field.IsForApi() ? null : data;
- }
-
- public static FieldConverter ExcludeChangedTypes()
- {
- return (data, field) =>
- {
- foreach (var value in data.Values)
+ if (value.Type == JsonValueType.Null)
{
- if (value.Type == JsonValueType.Null)
- {
- continue;
- }
-
- try
- {
- var (_, error) = JsonValueConverter.ConvertValue(field, value);
-
- if (error != null)
- {
- return null;
- }
- }
- catch
- {
- return null;
- }
+ continue;
}
- return data;
- };
- }
-
- public static FieldConverter ResolveAssetUrls(IReadOnlyCollection? fields, IUrlGenerator urlGenerator)
- {
- if (fields?.Any() != true)
- {
- return (data, field) => data;
- }
-
- bool ShouldHandle(IField field, IField? parent = null)
- {
- if (field is IField)
+ try
{
- if (fields.Contains("*"))
- {
- return true;
- }
+ var (_, error) = JsonValueConverter.ConvertValue(field, value);
- if (parent == null)
- {
- return fields.Contains(field.Name);
- }
- else
+ if (error != null)
{
- return fields.Contains($"{parent.Name}.{field.Name}");
+ return null;
}
}
-
- return false;
- }
-
- void Resolve(IJsonValue value)
- {
- if (value is JsonArray array)
+ catch
{
- for (var i = 0; i < array.Count; i++)
- {
- var id = array[i].ToString();
-
- array[i] = JsonValue.Create(urlGenerator.AssetContent(Guid.Parse(id)));
- }
+ return null;
}
}
- return (data, field) =>
- {
- if (ShouldHandle(field))
- {
- foreach (var partition in data)
- {
- Resolve(partition.Value);
- }
- }
- else if (field is IArrayField arrayField)
- {
- foreach (var partition in data)
- {
- if (partition.Value is JsonArray array)
- {
- for (var i = 0; i < array.Count; i++)
- {
- if (array[i] is JsonObject arrayItem)
- {
- foreach (var (key, value) in arrayItem)
- {
- if (arrayField.FieldsByName.TryGetValue(key, out var nestedField) && ShouldHandle(nestedField, field))
- {
- Resolve(value);
- }
- }
- }
- }
- }
- }
- }
-
- return data;
- };
- }
+ return data;
+ };
public static FieldConverter ResolveInvariant(LanguagesConfig languages)
{
var codeForInvariant = InvariantPartitioning.Key;
- var codeForMasterLanguage = languages.Master;
return (data, field) =>
{
- if (field.Partitioning.Equals(Partitioning.Invariant))
+ if (field.Partitioning.Equals(Partitioning.Invariant) && !data.ContainsKey(codeForInvariant))
{
- var result = new ContentFieldData();
+ var result = new ContentFieldData(1);
- if (data.TryGetValue(codeForInvariant, out var value))
- {
- result[codeForInvariant] = value;
- }
- else if (data.TryGetValue(codeForMasterLanguage, out value))
+ if (data.TryGetValue(languages.Master, out var value))
{
result[codeForInvariant] = value;
}
@@ -202,21 +91,20 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{
if (field.Partitioning.Equals(Partitioning.Language))
{
- var result = new ContentFieldData();
-
- foreach (var languageCode in languages.AllKeys)
+ if (data.TryGetValue(codeForInvariant, out var value))
{
- if (data.TryGetValue(languageCode, out var value))
- {
- result[languageCode] = value;
- }
- else if (languages.IsMaster(languageCode) && data.TryGetValue(codeForInvariant, out value))
+ var result = new ContentFieldData
{
- result[languageCode] = value;
- }
+ [languages.Master] = value
+ };
+
+ return result;
}
- return result;
+ foreach (var key in data.Keys.Where(x => !languages.AllKeys.Contains(x)).ToList())
+ {
+ data.Remove(key);
+ }
}
return data;
@@ -231,11 +119,11 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{
foreach (var languageCode in languages.AllKeys)
{
- if (!data.TryGetValue(languageCode, out var value))
+ if (!data.ContainsKey(languageCode))
{
foreach (var fallback in languages.GetPriorities(languageCode))
{
- if (data.TryGetValue(fallback, out value))
+ if (data.TryGetValue(fallback, out var value))
{
data[languageCode] = value;
break;
@@ -253,7 +141,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{
if (languages?.Any() != true)
{
- return (data, field) => data;
+ return Noop;
}
var languageSet = new HashSet(StringComparer.OrdinalIgnoreCase);
@@ -275,145 +163,45 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{
if (field.Partitioning.Equals(Partitioning.Language))
{
- var result = new ContentFieldData();
-
- foreach (var languageCode in languageSet)
+ foreach (var (key, _) in data.ToList())
{
- if (data.TryGetValue(languageCode, out var value))
+ if (!languageSet.Contains(key))
{
- result[languageCode] = value;
+ data.Remove(key);
}
}
-
- return result;
}
return data;
};
}
- public static FieldConverter ForNestedName2Name(params ValueConverter[] converters)
- {
- return ForNested(FieldByNameResolver, KeyNameResolver, converters);
- }
-
- public static FieldConverter ForNestedName2Id(params ValueConverter[] converters)
- {
- return ForNested(FieldByNameResolver, KeyIdResolver, converters);
- }
-
- public static FieldConverter ForNestedId2Name(params ValueConverter[] converters)
- {
- return ForNested(FieldByIdResolver, KeyNameResolver, converters);
- }
-
- public static FieldConverter ForNestedId2Id(params ValueConverter[] converters)
- {
- return ForNested(FieldByIdResolver, KeyIdResolver, converters);
- }
-
- private static FieldConverter ForNested(FieldResolver fieldResolver, FieldKeyResolver keyResolver, params ValueConverter[] converters)
+ public static FieldConverter ForValues(params ValueConverter[] converters)
{
return (data, field) =>
{
- if (field is IArrayField arrayField)
+ foreach (var (key, value) in data.ToList())
{
- var result = new ContentFieldData();
+ IJsonValue? newValue = value;
- foreach (var (partitionKey, partitionValue) in data)
+ for (var i = 0; i < converters.Length; i++)
{
- if (!(partitionValue is JsonArray array))
- {
- continue;
- }
+ newValue = converters[i](newValue!, field, null);
- var newArray = JsonValue.Array();
-
- foreach (var item in array.OfType())
+ if (newValue == null)
{
- var newItem = JsonValue.Object();
-
- foreach (var (key, value) in item)
- {
- var nestedField = fieldResolver(arrayField, key);
-
- if (nestedField == null)
- {
- continue;
- }
-
- var newValue = value;
-
- var isUnset = false;
-
- if (converters != null)
- {
- foreach (var converter in converters)
- {
- newValue = converter(newValue, nestedField);
-
- if (ReferenceEquals(newValue, Value.Unset))
- {
- isUnset = true;
- break;
- }
- }
- }
-
- if (!isUnset)
- {
- newItem.Add(keyResolver(nestedField), newValue);
- }
- }
-
- newArray.Add(newItem);
+ break;
}
-
- result.Add(partitionKey, newArray);
}
- return result;
- }
-
- return data;
- };
- }
-
- public static FieldConverter ForValues(params ValueConverter[] converters)
- {
- return (data, field) =>
- {
- if (!(field is IArrayField))
- {
- var result = new ContentFieldData();
-
- foreach (var (key, value) in data)
+ if (newValue == null)
{
- var newValue = value;
-
- var isUnset = false;
-
- if (converters != null)
- {
- foreach (var converter in converters)
- {
- newValue = converter(newValue, field);
-
- if (ReferenceEquals(newValue, Value.Unset))
- {
- isUnset = true;
- break;
- }
- }
- }
-
- if (!isUnset)
- {
- result.Add(key, newValue);
- }
+ data.Remove(key);
+ }
+ else if (!ReferenceEquals(newValue, value))
+ {
+ data[key] = newValue;
}
-
- return result;
}
return data;
diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldIdentifier.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldIdentifier.cs
new file mode 100644
index 000000000..b94619db9
--- /dev/null
+++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldIdentifier.cs
@@ -0,0 +1,53 @@
+// ==========================================================================
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex UG (haftungsbeschraenkt)
+// All rights reserved. Licensed under the MIT license.
+// ==========================================================================
+
+using System.Collections.Generic;
+using Squidex.Domain.Apps.Core.Schemas;
+
+namespace Squidex.Domain.Apps.Core.ConvertContent
+{
+ public abstract class FieldIdentifier
+ {
+ public static readonly FieldIdentifier ByName = new FieldByName();
+ public static readonly FieldIdentifier ById = new FieldById();
+
+ public abstract IField? GetField(IArrayField arrayField, string key);
+
+ public abstract string GetStringKey(IField field);
+
+ private sealed class FieldByName : FieldIdentifier
+ {
+ public override IField? GetField(IArrayField arrayField, string key)
+ {
+ return arrayField.FieldsByName.GetValueOrDefault(key);
+ }
+
+ public override string GetStringKey(IField field)
+ {
+ return field.Name;
+ }
+ }
+
+ private sealed class FieldById : FieldIdentifier
+ {
+ public override IField? GetField(IArrayField arrayField, string key)
+ {
+ if (long.TryParse(key, out var id))
+ {
+ return arrayField.FieldsById.GetValueOrDefault(id);
+ }
+
+ return null;
+ }
+
+ public override string GetStringKey(IField field)
+ {
+ return field.Id.ToString();
+ }
+ }
+ }
+}
diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/Value.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/Value.cs
deleted file mode 100644
index a83740e60..000000000
--- a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/Value.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-// ==========================================================================
-// Squidex Headless CMS
-// ==========================================================================
-// Copyright (c) Squidex UG (haftungsbeschraenkt)
-// All rights reserved. Licensed under the MIT license.
-// ==========================================================================
-
-using Squidex.Infrastructure.Json.Objects;
-
-namespace Squidex.Domain.Apps.Core.ConvertContent
-{
- public static class Value
- {
- public static readonly IJsonValue Unset = JsonValue.Create("UNSET");
- }
-}
diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs
index b7973b677..2109a0a97 100644
--- a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs
+++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs
@@ -6,6 +6,8 @@
// ==========================================================================
using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Text;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent;
@@ -14,13 +16,44 @@ using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ConvertContent
{
- public delegate IJsonValue ValueConverter(IJsonValue value, IField field);
+ public delegate IJsonValue? ValueConverter(IJsonValue value, IField field, IArrayField? parent = null);
public static class ValueConverters
{
+ public static readonly ValueConverter Noop = (value, field, parent) => value;
+
+ public static readonly ValueConverter ExcludeHidden = (value, field, parent) =>
+ {
+ return field.IsForApi() ? value : null;
+ };
+
+ public static readonly ValueConverter ExcludeChangedTypes = (value, field, parent) =>
+ {
+ if (value.Type == JsonValueType.Null)
+ {
+ return value;
+ }
+
+ try
+ {
+ var (_, error) = JsonValueConverter.ConvertValue(field, value);
+
+ if (error != null)
+ {
+ return null;
+ }
+ }
+ catch
+ {
+ return null;
+ }
+
+ return value;
+ };
+
public static ValueConverter DecodeJson(IJsonSerializer jsonSerializer)
{
- return (value, field) =>
+ return (value, field, parent) =>
{
if (field is IField && value is JsonScalar s)
{
@@ -35,7 +68,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
public static ValueConverter EncodeJson(IJsonSerializer jsonSerializer)
{
- return (value, field) =>
+ return (value, field, parent) =>
{
if (value.Type != JsonValueType.Null && field is IField)
{
@@ -48,32 +81,103 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
};
}
- public static ValueConverter ExcludeHidden()
+ public static ValueConverter ResolveAssetUrls(IReadOnlyCollection? fields, IUrlGenerator urlGenerator)
{
- return (value, field) => !field.IsForApi() ? Value.Unset : value;
- }
+ if (fields?.Any() != true)
+ {
+ return Noop;
+ }
- public static ValueConverter ExcludeChangedTypes()
- {
- return (value, field) =>
+ Func shouldHandle;
+
+ if (fields.Contains("*"))
{
- if (value.Type == JsonValueType.Null)
- {
- return value;
- }
+ shouldHandle = (field, parent) => true;
+ }
+ else
+ {
+ var paths = fields.Select(x => x.Split('.')).ToList();
- try
+ shouldHandle = (field, parent) =>
{
- var (_, error) = JsonValueConverter.ConvertValue(field, value);
+ for (var i = 0; i < paths.Count; i++)
+ {
+ var path = paths[i];
+
+ if (parent != null)
+ {
+ return path.Length == 2 && path[0] == parent.Name && path[1] == field.Name;
+ }
+ else
+ {
+ return path.Length == 1 && path[0] == field.Name;
+ }
+ }
- if (error != null)
+ return false;
+ };
+ }
+
+ return (value, field, parent) =>
+ {
+ if (value is JsonArray array && shouldHandle(field, parent))
+ {
+ for (var i = 0; i < array.Count; i++)
{
- return Value.Unset;
+ var id = array[i].ToString();
+
+ array[i] = JsonValue.Create(urlGenerator.AssetContent(Guid.Parse(id)));
}
}
- catch
+
+ return value;
+ };
+ }
+
+ public static ValueConverter ForNested(params ValueConverter[] converters)
+ {
+ if (converters?.Any() != true)
+ {
+ return Noop;
+ }
+
+ return (value, field, parent) =>
+ {
+ if (value is JsonArray array && field is IArrayField arrayField)
{
- return Value.Unset;
+ foreach (var nested in array.OfType())
+ {
+ foreach (var (fieldName, nestedValue) in nested.ToList())
+ {
+ IJsonValue? newValue = nestedValue;
+
+ if (arrayField.FieldsByName.TryGetValue(fieldName, out var nestedField))
+ {
+ for (var i = 0; i < converters.Length; i++)
+ {
+ newValue = converters[i](newValue!, nestedField, arrayField);
+
+ if (newValue == null)
+ {
+ break;
+ }
+ }
+ }
+ else
+ {
+ newValue = null;
+ }
+
+ if (newValue == null)
+ {
+ nested.Remove(fieldName);
+ }
+ else if (!ReferenceEquals(nestedValue, newValue))
+ {
+ nested[fieldName] = newValue;
+ }
+ }
+ }
}
return value;
diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs
index 376bcbde8..3cefb32ac 100644
--- a/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs
+++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs
@@ -89,12 +89,17 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
if (value is JsonArray array)
{
- var result = new JsonArray(array);
+ var result = array;
for (var i = 0; i < result.Count; i++)
{
if (!IsValidReference(result[i]))
{
+ if (ReferenceEquals(result, array))
+ {
+ result = new JsonArray(array);
+ }
+
result.RemoveAt(i);
i--;
}
diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ValueReferencesConverter.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ValueReferencesConverter.cs
index eeee1a1a5..b0dec3778 100644
--- a/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ValueReferencesConverter.cs
+++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ValueReferencesConverter.cs
@@ -18,16 +18,16 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
if (validIds == null)
{
- return (value, field) => value;
+ return ValueConverters.Noop;
}
var cleaner = new ReferencesCleaner(validIds);
- return (value, field) =>
+ return (value, field, parent) =>
{
if (value.Type == JsonValueType.Null)
{
- return value!;
+ return value;
}
cleaner.SetValue(value);
diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/DataConverter.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/DataConverter.cs
index 96dc61e2a..e4cb65582 100644
--- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/DataConverter.cs
+++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/DataConverter.cs
@@ -19,20 +19,18 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
public DataConverter(IJsonSerializer serializer)
{
+ var decoder = ValueConverters.DecodeJson(serializer);
+
decodeJsonConverters = new[]
{
- FieldConverters.ForValues(
- ValueConverters.DecodeJson(serializer)),
- FieldConverters.ForNestedId2Name(
- ValueConverters.DecodeJson(serializer))
+ FieldConverters.ForValues(decoder, ValueConverters.ForNested(decoder))
};
+ var encoder = ValueConverters.EncodeJson(serializer);
+
encodeJsonConverters = new[]
{
- FieldConverters.ForValues(
- ValueConverters.EncodeJson(serializer)),
- FieldConverters.ForNestedName2Id(
- ValueConverters.EncodeJson(serializer))
+ FieldConverters.ForValues(encoder, ValueConverters.ForNested(encoder))
};
}
@@ -43,7 +41,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
public IdContentData ToMongoModel(NamedContentData result, Schema schema)
{
- return result.ConvertName2Id(schema, encodeJsonConverters);
+ return result.ConvertName2IdCloned(schema, encodeJsonConverters);
}
}
}
diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs
index 1a9f2bca8..736b3ebcc 100644
--- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs
+++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs
@@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
if (context.PlainResult is IContentEntity content && NotEnriched(context))
{
- var enriched = await contentEnricher.EnrichAsync(content, contextProvider.Context);
+ var enriched = await contentEnricher.EnrichAsync(content, true, contextProvider.Context);
context.Complete(enriched);
}
diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs
index 860abb2bb..5b09a992b 100644
--- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs
+++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs
@@ -36,20 +36,25 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
this.contentQuery = contentQuery;
}
- public async Task EnrichAsync(IContentEntity content, Context context)
+ public async Task EnrichAsync(IContentEntity content, bool cloneData, Context context)
{
Guard.NotNull(content);
- var enriched = await EnrichAsync(Enumerable.Repeat(content, 1), context);
+ var enriched = await EnrichInternalAsync(Enumerable.Repeat(content, 1), cloneData, context);
return enriched[0];
}
- public async Task> EnrichAsync(IEnumerable contents, Context context)
+ public Task> EnrichAsync(IEnumerable contents, Context context)
{
Guard.NotNull(contents);
Guard.NotNull(context);
+ return EnrichInternalAsync(contents, false, context);
+ }
+
+ private async Task> EnrichInternalAsync(IEnumerable contents, bool cloneData, Context context)
+ {
using (Profiler.TraceMethod())
{
var results = new List();
@@ -65,6 +70,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
var result = SimpleMapper.Map(content, new ContentEntity());
+ if (cloneData)
+ {
+ result.Data = result.Data.Clone();
+ }
+
results.Add(result);
}
diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricher.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricher.cs
index 7a2a5a741..63ab1198b 100644
--- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricher.cs
+++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricher.cs
@@ -12,7 +12,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
public interface IContentEnricher
{
- Task EnrichAsync(IContentEntity content, Context context);
+ Task EnrichAsync(IContentEntity content, bool cloneData, Context context);
Task> EnrichAsync(IEnumerable contents, Context context);
}
diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs
index 9abdd5fcc..5a9192fc7 100644
--- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs
+++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs
@@ -104,17 +104,17 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
{
if (!context.IsFrontendClient)
{
- yield return FieldConverters.ExcludeHidden();
- yield return FieldConverters.ForNestedName2Name(ValueConverters.ExcludeHidden());
+ yield return FieldConverters.ExcludeHidden;
+ yield return FieldConverters.ForValues(ValueConverters.ForNested(ValueConverters.ExcludeHidden));
}
- yield return FieldConverters.ExcludeChangedTypes();
- yield return FieldConverters.ForNestedName2Name(ValueConverters.ExcludeChangedTypes());
+ yield return FieldConverters.ExcludeChangedTypes;
+ yield return FieldConverters.ForValues(ValueConverters.ForNested(ValueConverters.ExcludeChangedTypes));
if (cleanReferences != null)
{
yield return FieldConverters.ForValues(cleanReferences);
- yield return FieldConverters.ForNestedName2Name(cleanReferences);
+ yield return FieldConverters.ForValues(ValueConverters.ForNested(cleanReferences));
}
yield return FieldConverters.ResolveInvariant(context.App.LanguagesConfig);
@@ -134,11 +134,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
yield return FieldConverters.FilterLanguages(context.App.LanguagesConfig, languages);
}
- var assetUrls = context.AssetUrls();
+ var assetUrls = context.AssetUrls().ToList();
- if (assetUrls.Any())
+ if (assetUrls.Count > 0)
{
- yield return FieldConverters.ResolveAssetUrls(assetUrls.ToList(), urlGenerator);
+ var resolveAssetUrls = ValueConverters.ResolveAssetUrls(assetUrls, urlGenerator);
+
+ yield return FieldConverters.ForValues(resolveAssetUrls);
+ yield return FieldConverters.ForValues(ValueConverters.ForNested(resolveAssetUrls));
}
}
}
diff --git a/backend/src/Squidex.Infrastructure/Json/Objects/IJsonValue.cs b/backend/src/Squidex.Infrastructure/Json/Objects/IJsonValue.cs
index e2180b7f5..deb5fdb4d 100644
--- a/backend/src/Squidex.Infrastructure/Json/Objects/IJsonValue.cs
+++ b/backend/src/Squidex.Infrastructure/Json/Objects/IJsonValue.cs
@@ -16,6 +16,8 @@ namespace Squidex.Infrastructure.Json.Objects
bool TryGet(string pathSegment, [MaybeNullWhen(false)] out IJsonValue result);
+ IJsonValue Clone();
+
string ToJsonString();
string ToString();
diff --git a/backend/src/Squidex.Infrastructure/Json/Objects/JsonArray.cs b/backend/src/Squidex.Infrastructure/Json/Objects/JsonArray.cs
index f91d3a048..33cc19b1c 100644
--- a/backend/src/Squidex.Infrastructure/Json/Objects/JsonArray.cs
+++ b/backend/src/Squidex.Infrastructure/Json/Objects/JsonArray.cs
@@ -26,6 +26,11 @@ namespace Squidex.Infrastructure.Json.Objects
}
public JsonArray(JsonArray source)
+ : base(source.ToList())
+ {
+ }
+
+ private JsonArray(List source)
: base(source)
{
}
@@ -90,6 +95,11 @@ namespace Squidex.Infrastructure.Json.Objects
return hashCode;
}
+ public IJsonValue Clone()
+ {
+ return new JsonArray(this.Select(x => x.Clone()).ToList());
+ }
+
public string ToJsonString()
{
return ToString();
diff --git a/backend/src/Squidex.Infrastructure/Json/Objects/JsonNull.cs b/backend/src/Squidex.Infrastructure/Json/Objects/JsonNull.cs
index 18ea75059..04b4f18c2 100644
--- a/backend/src/Squidex.Infrastructure/Json/Objects/JsonNull.cs
+++ b/backend/src/Squidex.Infrastructure/Json/Objects/JsonNull.cs
@@ -43,6 +43,11 @@ namespace Squidex.Infrastructure.Json.Objects
return 0;
}
+ public IJsonValue Clone()
+ {
+ return this;
+ }
+
public string ToJsonString()
{
return ToString();
diff --git a/backend/src/Squidex.Infrastructure/Json/Objects/JsonObject.cs b/backend/src/Squidex.Infrastructure/Json/Objects/JsonObject.cs
index 8f523809f..bc0d045c8 100644
--- a/backend/src/Squidex.Infrastructure/Json/Objects/JsonObject.cs
+++ b/backend/src/Squidex.Infrastructure/Json/Objects/JsonObject.cs
@@ -61,6 +61,11 @@ namespace Squidex.Infrastructure.Json.Objects
inner = new Dictionary(obj.inner);
}
+ private JsonObject(Dictionary source)
+ {
+ inner = source;
+ }
+
public JsonObject Add(string key, object? value)
{
return Add(key, JsonValue.Create(value));
@@ -123,6 +128,11 @@ namespace Squidex.Infrastructure.Json.Objects
return inner.DictionaryHashCode();
}
+ public IJsonValue Clone()
+ {
+ return new JsonObject(this.ToDictionary(x => x.Key, x => x.Value.Clone()));
+ }
+
public string ToJsonString()
{
return ToString();
diff --git a/backend/src/Squidex.Infrastructure/Json/Objects/JsonScalar.cs b/backend/src/Squidex.Infrastructure/Json/Objects/JsonScalar.cs
index e2c76751e..0044ab666 100644
--- a/backend/src/Squidex.Infrastructure/Json/Objects/JsonScalar.cs
+++ b/backend/src/Squidex.Infrastructure/Json/Objects/JsonScalar.cs
@@ -36,6 +36,11 @@ namespace Squidex.Infrastructure.Json.Objects
return other != null && other.Type == Type && Equals(other.Value, Value);
}
+ public IJsonValue Clone()
+ {
+ return this;
+ }
+
public override int GetHashCode()
{
return Value.GetHashCode();
diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentDataTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentDataTests.cs
index fe4b91dc5..d7b0295d5 100644
--- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentDataTests.cs
+++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentDataTests.cs
@@ -231,5 +231,24 @@ namespace Squidex.Domain.Apps.Core.Model.Contents
Assert.True(lhs.Equals((object)rhs));
Assert.Equal(lhs.GetHashCode(), rhs.GetHashCode());
}
+
+ [Fact]
+ public void Should_clone_named_value_and_also_children()
+ {
+ var source = new NamedContentData
+ {
+ ["field1"] = new ContentFieldData(),
+ ["field2"] = new ContentFieldData()
+ };
+
+ var clone = source.Clone();
+
+ Assert.NotSame(source, clone);
+
+ foreach (var (key, value) in clone)
+ {
+ Assert.NotSame(value, source[key]);
+ }
+ }
}
}
diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentFieldDataTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentFieldDataTests.cs
index 010007544..185789b0b 100644
--- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentFieldDataTests.cs
+++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentFieldDataTests.cs
@@ -9,6 +9,7 @@ using System;
using System.Linq;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Contents;
+using Squidex.Infrastructure.Json.Objects;
using Xunit;
namespace Squidex.Domain.Apps.Core.Model.Contents
@@ -62,5 +63,24 @@ namespace Squidex.Domain.Apps.Core.Model.Contents
Assert.Null(string.IsInterned(serialized.Keys.First()));
}
+
+ [Fact]
+ public void Should_clone_value_and_also_children()
+ {
+ var source = new ContentFieldData
+ {
+ ["en"] = JsonValue.Array(),
+ ["de"] = JsonValue.Array()
+ };
+
+ var clone = source.Clone();
+
+ Assert.NotSame(source, clone);
+
+ foreach (var (key, value) in clone)
+ {
+ Assert.NotSame(value, source[key]);
+ }
+ }
}
}
diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs
index 7081b61bc..d253f2a8d 100644
--- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs
+++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs
@@ -8,6 +8,7 @@
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.Schemas;
+using Squidex.Infrastructure.Json.Objects;
using Xunit;
namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
@@ -25,7 +26,12 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
.AddNumber(3, "field3", Partitioning.Invariant)
.AddAssets(5, "assets1", Partitioning.Invariant)
.AddAssets(6, "assets2", Partitioning.Invariant)
+ .AddArray(7, "array", Partitioning.Invariant, h => h
+ .AddNumber(71, "nested1")
+ .AddNumber(72, "nested2"))
.AddJson(4, "json", Partitioning.Language)
+ .HideField(2)
+ .HideField(71, 7)
.UpdateField(3, f => f.Hide());
}
@@ -40,17 +46,34 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
.AddField("field2",
new ContentFieldData()
.AddValue("iv", 1))
+ .AddField("array",
+ new ContentFieldData()
+ .AddValue("iv",
+ JsonValue.Array(
+ JsonValue.Object()
+ .Add("nested1", 100)
+ .Add("nested2", 200)
+ .Add("invalid", 300))))
.AddField("invalid",
new ContentFieldData()
.AddValue("iv", 2));
- var actual = input.ConvertName2Id(schema, (data, field) => field.Name == "field2" ? null : data);
+ var hideRoot = FieldConverters.ExcludeHidden;
+ var hideNested = FieldConverters.ForValues(ValueConverters.ForNested(ValueConverters.ExcludeHidden));
+
+ var actual = input.ConvertName2IdCloned(schema, hideRoot, hideNested);
var expected =
new IdContentData()
.AddField(1,
new ContentFieldData()
- .AddValue("en", "EN"));
+ .AddValue("en", "EN"))
+ .AddField(7,
+ new ContentFieldData()
+ .AddValue("iv",
+ JsonValue.Array(
+ JsonValue.Object()
+ .Add("72", 200))));
Assert.Equal(expected, actual);
}
@@ -81,32 +104,6 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
Assert.Equal(expected, actual);
}
- [Fact]
- public void Should_convert_id_to_id()
- {
- var input =
- new IdContentData()
- .AddField(1,
- new ContentFieldData()
- .AddValue("en", "EN"))
- .AddField(2,
- new ContentFieldData()
- .AddValue("iv", 1))
- .AddField(99,
- new ContentFieldData()
- .AddValue("iv", 2));
-
- var actual = input.ConvertId2Id(schema, (data, field) => field.Name == "field2" ? null : data);
-
- var expected =
- new IdContentData()
- .AddField(1,
- new ContentFieldData()
- .AddValue("en", "EN"));
-
- Assert.Equal(expected, actual);
- }
-
[Fact]
public void Should_convert_id_to_name()
{
@@ -118,17 +115,34 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
.AddField(2,
new ContentFieldData()
.AddValue("iv", 1))
+ .AddField(7,
+ new ContentFieldData()
+ .AddValue("iv",
+ JsonValue.Array(
+ JsonValue.Object()
+ .Add("71", 100)
+ .Add("72", 200)
+ .Add("799", 300))))
.AddField(99,
new ContentFieldData()
.AddValue("iv", 2));
- var actual = input.ConvertId2Name(schema, (data, field) => field.Name == "field2" ? null : data);
+ var hideRoot = FieldConverters.ExcludeHidden;
+ var hideNested = FieldConverters.ForValues(ValueConverters.ForNested(ValueConverters.ExcludeHidden));
+
+ var actual = input.ConvertId2Name(schema, hideRoot, hideNested);
var expected =
new NamedContentData()
.AddField("field1",
new ContentFieldData()
- .AddValue("en", "EN"));
+ .AddValue("en", "EN"))
+ .AddField("array",
+ new ContentFieldData()
+ .AddValue("iv",
+ JsonValue.Array(
+ JsonValue.Object()
+ .Add("nested2", 200))));
Assert.Equal(expected, actual);
}
diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs
index ed3a9603d..179393282 100644
--- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs
+++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs
@@ -5,10 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
-using System;
-using System.Collections.Generic;
using System.Linq;
-using FakeItEasy;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
@@ -21,31 +18,22 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
{
public class FieldConvertersTests
{
- private readonly IUrlGenerator urlGenerato = A.Fake();
- private readonly Guid id1 = Guid.NewGuid();
- private readonly Guid id2 = Guid.NewGuid();
private readonly LanguagesConfig languagesConfig = LanguagesConfig.English.Set(Language.DE);
- public FieldConvertersTests()
- {
- A.CallTo(() => urlGenerato.AssetContent(A._))
- .ReturnsLazily(ctx => $"url/to/{ctx.GetArgument(0)}");
- }
-
[Fact]
public void Should_filter_for_value_conversion()
{
var field = Fields.String(1, "string", Partitioning.Invariant);
- var input =
+ var source =
new ContentFieldData()
.AddJsonValue(JsonValue.Object());
- var actual = FieldConverters.ForValues((f, i) => Value.Unset)(input, field);
+ var result = FieldConverters.ForValues((value, field, parent) => null)(source, field);
var expected = new ContentFieldData();
- Assert.Equal(expected, actual);
+ Assert.Equal(expected, result);
}
[Fact]
@@ -53,133 +41,17 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
{
var field = Fields.Json(1, "json", Partitioning.Invariant);
- var input =
+ var source =
new ContentFieldData()
.AddJsonValue(JsonValue.Object());
- var actual = FieldConverters.ForValues(ValueConverters.EncodeJson(TestUtils.DefaultSerializer))(input, field);
+ var result = FieldConverters.ForValues(ValueConverters.EncodeJson(TestUtils.DefaultSerializer))(source, field);
var expected =
new ContentFieldData()
.AddValue("iv", "e30=");
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Should_convert_name_to_id()
- {
- var field =
- Fields.Array(1, "1", Partitioning.Invariant,
- Fields.Number(1, "field1"),
- Fields.Number(2, "field2").Hide());
-
- var input =
- new ContentFieldData()
- .AddJsonValue(
- JsonValue.Array(
- JsonValue.Object()
- .Add("field1", 100)
- .Add("field2", 200)
- .Add("invalid", 300)));
-
- var actual = FieldConverters.ForNestedName2Id(ValueConverters.ExcludeHidden())(input, field);
-
- var expected =
- new ContentFieldData()
- .AddJsonValue(
- JsonValue.Array(
- JsonValue.Object()
- .Add("1", 100)));
-
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Should_convert_name_to_name()
- {
- var field =
- Fields.Array(1, "1", Partitioning.Invariant,
- Fields.Number(1, "field1"),
- Fields.Number(2, "field2").Hide());
-
- var input =
- new ContentFieldData()
- .AddJsonValue(
- JsonValue.Array(
- JsonValue.Object()
- .Add("field1", 100)
- .Add("field2", 200)
- .Add("invalid", 300)));
-
- var actual = FieldConverters.ForNestedName2Name(ValueConverters.ExcludeHidden())(input, field);
-
- var expected =
- new ContentFieldData()
- .AddJsonValue(
- JsonValue.Array(
- JsonValue.Object()
- .Add("field1", 100)));
-
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Should_convert_id_to_id()
- {
- var field =
- Fields.Array(1, "1", Partitioning.Invariant,
- Fields.Number(1, "field1"),
- Fields.Number(2, "field2").Hide());
-
- var input =
- new ContentFieldData()
- .AddValue("iv",
- JsonValue.Array(
- JsonValue.Object()
- .Add("1", 100)
- .Add("2", 200)
- .Add("99", 300)));
-
- var actual = FieldConverters.ForNestedId2Id(ValueConverters.ExcludeHidden())(input, field);
-
- var expected =
- new ContentFieldData()
- .AddValue("iv",
- JsonValue.Array(
- JsonValue.Object()
- .Add("1", 100)));
-
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Should_convert_id_to_name()
- {
- var field =
- Fields.Array(1, "1", Partitioning.Invariant,
- Fields.Number(1, "field1"),
- Fields.Number(2, "field2").Hide());
-
- var input =
- new ContentFieldData()
- .AddValue("iv",
- JsonValue.Array(
- JsonValue.Object()
- .Add("1", 100)
- .Add("2", 200)
- .Add("99", 300)));
-
- var actual = FieldConverters.ForNestedId2Name(ValueConverters.ExcludeHidden())(input, field);
-
- var expected =
- new ContentFieldData()
- .AddValue("iv",
- JsonValue.Array(
- JsonValue.Object()
- .Add("field1", 100)));
-
- Assert.Equal(expected, actual);
+ Assert.Equal(expected, result);
}
[Fact]
@@ -192,7 +64,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
.AddValue("en", null)
.AddValue("de", 1);
- var result = FieldConverters.ExcludeChangedTypes()(source, field);
+ var result = FieldConverters.ExcludeChangedTypes(source, field);
Assert.Same(source, result);
}
@@ -207,7 +79,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
.AddValue("en", "EN")
.AddValue("de", 0);
- var result = FieldConverters.ExcludeChangedTypes()(source, field);
+ var result = FieldConverters.ExcludeChangedTypes(source, field);
Assert.Null(result);
}
@@ -219,19 +91,19 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
var source = new ContentFieldData();
- var result = FieldConverters.ExcludeHidden()(source, field);
+ var result = FieldConverters.ExcludeHidden(source, field);
Assert.Same(source, result);
}
[Fact]
- public void Should_return_null_values_if_field_hidden()
+ public void Should_return_null_if_field_hidden()
{
var field = Fields.String(1, "string", Partitioning.Language);
var source = new ContentFieldData();
- var result = FieldConverters.ExcludeHidden()(source, field.Hide());
+ var result = FieldConverters.ExcludeHidden(source, field.Hide());
Assert.Null(result);
}
@@ -293,8 +165,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
var source =
new ContentFieldData()
- .AddValue("iv", "A")
- .AddValue("it", "B");
+ .AddValue("iv", "A");
var expected =
new ContentFieldData()
@@ -474,125 +345,5 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
Assert.Same(source, result);
}
-
- [Fact]
- public void Should_convert_asset_ids_to_urls()
- {
- var field = Fields.Assets(1, "assets", Partitioning.Invariant);
-
- var source =
- new ContentFieldData()
- .AddJsonValue(JsonValue.Array(id1, id2));
-
- var expected =
- new ContentFieldData()
- .AddJsonValue(JsonValue.Array($"url/to/{id1}", $"url/to/{id2}"));
-
- var result = FieldConverters.ResolveAssetUrls(new HashSet(new[] { "assets" }), urlGenerato)(source, field);
-
- Assert.Equal(expected, result);
- }
-
- [Fact]
- public void Should_convert_nested_asset_ids_to_urls()
- {
- var field =
- Fields.Array(1, "array", Partitioning.Invariant,
- Fields.Assets(1, "assets"));
-
- var source =
- new ContentFieldData()
- .AddJsonValue(JsonValue.Array(
- JsonValue.Object()
- .Add("assets", JsonValue.Array(id1, id2))));
-
- var expected =
- new ContentFieldData()
- .AddJsonValue(JsonValue.Array(
- JsonValue.Object()
- .Add("assets", JsonValue.Array($"url/to/{id1}", $"url/to/{id2}"))));
-
- var result = FieldConverters.ResolveAssetUrls(new HashSet(new[] { "array.assets" }), urlGenerato)(source, field);
-
- Assert.Equal(expected, result);
- }
-
- [Fact]
- public void Should_convert_asset_ids_to_urls_for_wildcard_fields()
- {
- var field = Fields.Assets(1, "assets", Partitioning.Invariant);
-
- var source =
- new ContentFieldData()
- .AddJsonValue(JsonValue.Array(id1, id2));
-
- var expected =
- new ContentFieldData()
- .AddJsonValue(JsonValue.Array($"url/to/{id1}", $"url/to/{id2}"));
-
- var result = FieldConverters.ResolveAssetUrls(new HashSet(new[] { "*" }), urlGenerato)(source, field);
-
- Assert.Equal(expected, result);
- }
-
- [Fact]
- public void Should_convert_nested_asset_ids_to_urls_for_wildcard_fields()
- {
- var field =
- Fields.Array(1, "array", Partitioning.Invariant,
- Fields.Assets(1, "assets"));
-
- var source =
- new ContentFieldData()
- .AddJsonValue(JsonValue.Array(
- JsonValue.Object()
- .Add("assets", JsonValue.Array(id1, id2))));
-
- var expected =
- new ContentFieldData()
- .AddJsonValue(JsonValue.Array(
- JsonValue.Object()
- .Add("assets", JsonValue.Array($"url/to/{id1}", $"url/to/{id2}"))));
-
- var result = FieldConverters.ResolveAssetUrls(new HashSet(new[] { "*" }), urlGenerato)(source, field);
-
- Assert.Equal(expected, result);
- }
-
- [Fact]
- public void Should_not_convert_asset_ids_to_urls_when_field_does_not_match()
- {
- var field = Fields.Assets(1, "assets", Partitioning.Invariant);
-
- var source =
- new ContentFieldData()
- .AddJsonValue(JsonValue.Array(id1, id2));
-
- var expected =
- new ContentFieldData()
- .AddJsonValue(JsonValue.Array(id1, id2));
-
- var result = FieldConverters.ResolveAssetUrls(new HashSet(new[] { "other" }), urlGenerato)(source, field);
-
- Assert.Equal(expected, result);
- }
-
- [Fact]
- public void Should_not_convert_asset_ids_to_urls_when_fields_is_null()
- {
- var field = Fields.Assets(1, "assets", Partitioning.Invariant);
-
- var source =
- new ContentFieldData()
- .AddJsonValue(JsonValue.Array(id1, id2));
-
- var expected =
- new ContentFieldData()
- .AddJsonValue(JsonValue.Array(id1, id2));
-
- var result = FieldConverters.ResolveAssetUrls(null, urlGenerato)(source, field);
-
- Assert.Equal(expected, result);
- }
}
}
diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ValueConvertersTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ValueConvertersTests.cs
index f1e3a82f6..01762b092 100644
--- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ValueConvertersTests.cs
+++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ValueConvertersTests.cs
@@ -5,8 +5,11 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
+using System;
+using FakeItEasy;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.Schemas;
+using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Xunit;
@@ -14,10 +17,19 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
{
public class ValueConvertersTests
{
+ private readonly IUrlGenerator urlGenerator = A.Fake();
+ private readonly Guid id1 = Guid.NewGuid();
+ private readonly Guid id2 = Guid.NewGuid();
private readonly RootField stringField = Fields.String(1, "1", Partitioning.Invariant);
private readonly RootField jsonField = Fields.Json(1, "1", Partitioning.Invariant);
private readonly RootField numberField = Fields.Number(1, "1", Partitioning.Invariant);
+ public ValueConvertersTests()
+ {
+ A.CallTo(() => urlGenerator.AssetContent(A._))
+ .ReturnsLazily(ctx => $"url/to/{ctx.GetArgument(0)}");
+ }
+
[Fact]
public void Should_encode_json_value()
{
@@ -79,23 +91,89 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
}
[Fact]
- public void Should_return_unset_if_field_hidden()
+ public void Should_return_null_if_field_hidden()
{
var source = JsonValue.Create(123);
- var result = ValueConverters.ExcludeHidden()(source, stringField.Hide());
+ var result = ValueConverters.ExcludeHidden(source, stringField.Hide());
- Assert.Same(Value.Unset, result);
+ Assert.Null(result);
}
[Fact]
- public void Should_return_unset_if_field_has_wrong_type()
+ public void Should_return_null_if_field_has_wrong_type()
{
var source = JsonValue.Create("invalid");
- var result = ValueConverters.ExcludeChangedTypes()(source, numberField);
+ var result = ValueConverters.ExcludeChangedTypes(source, numberField);
+
+ Assert.Null(result);
+ }
+
+ [Theory]
+ [InlineData("assets")]
+ [InlineData("*")]
+ public void Should_convert_asset_ids_to_urls(string path)
+ {
+ var field = Fields.Assets(1, "assets", Partitioning.Invariant);
+
+ var source = JsonValue.Array(id1, id2);
+
+ var expected = JsonValue.Array($"url/to/{id1}", $"url/to/{id2}");
+
+ var result = ValueConverters.ResolveAssetUrls(HashSet.Of(path), urlGenerator)(source, field);
+
+ Assert.Equal(expected, result);
+ }
+
+ [Theory]
+ [InlineData("other")]
+ [InlineData("**")]
+ public void Should_not_convert_asset_ids_when_field_name_does_not_match(string path)
+ {
+ var field = Fields.Assets(1, "assets", Partitioning.Invariant);
+
+ var source = JsonValue.Array(id1, id2);
+
+ var expected = source;
+
+ var result = ValueConverters.ResolveAssetUrls(HashSet.Of(path), urlGenerator)(source, field);
+
+ Assert.Equal(expected, result);
+ }
+
+ [Theory]
+ [InlineData("parent.assets")]
+ [InlineData("*")]
+ public void Should_convert_nested_asset_ids_to_urls(string path)
+ {
+ var field = Fields.Array(1, "parent", Partitioning.Invariant, Fields.Assets(11, "assets"));
+
+ var source = JsonValue.Array(id1, id2);
+
+ var expected = JsonValue.Array($"url/to/{id1}", $"url/to/{id2}");
+
+ var result = ValueConverters.ResolveAssetUrls(HashSet.Of(path), urlGenerator)(source, field.Fields[0], field);
+
+ Assert.Equal(expected, result);
+ }
+
+ [Theory]
+ [InlineData("assets")]
+ [InlineData("parent")]
+ [InlineData("parent.other")]
+ [InlineData("other.assets")]
+ public void Should_not_convert_nested_asset_ids_when_field_name_does_not_match(string path)
+ {
+ var field = Fields.Array(1, "parent", Partitioning.Invariant, Fields.Assets(11, "assets"));
+
+ var source = JsonValue.Array(id1, id2);
+
+ var expected = source;
+
+ var result = ValueConverters.ResolveAssetUrls(HashSet.Of(path), urlGenerator)(source, field.Fields[0], field);
- Assert.Same(Value.Unset, result);
+ Assert.Equal(expected, result);
}
}
}
diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs
index fb45e05fa..16662a41d 100644
--- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs
+++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs
@@ -109,11 +109,11 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
.Add("nested", JsonValue.Array(id2)))));
var cleaner = ValueReferencesConverter.CleanReferences(new HashSet { id2 });
+ var cleanNested = ValueConverters.ForNested(cleaner);
- var converter = FieldConverters.ForValues(cleaner);
- var converterNested = FieldConverters.ForNestedName2Name(cleaner);
+ var converter = FieldConverters.ForValues(cleaner, cleanNested);
- var actual = source.ConvertName2Name(schema, converter, converterNested);
+ var actual = source.ConvertName2Name(schema, converter);
Assert.Equal(expected, actual);
}
@@ -210,7 +210,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
[MemberData(nameof(ReferencingFields))]
public void Should_return_same_value_from_field_when_value_is_json_null(IField field)
{
- var result = ValueReferencesConverter.CleanReferences(RandomIds())(JsonValue.Null, field);
+ var result = ValueReferencesConverter.CleanReferences(RandomIds())(JsonValue.Null, field, null);
Assert.Equal(JsonValue.Null, result);
}
@@ -224,7 +224,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
var value = CreateValue(id1, id2);
- var result = ValueReferencesConverter.CleanReferences(HashSet.Of(id1))(value, field);
+ var result = ValueReferencesConverter.CleanReferences(HashSet.Of(id1))(value, field, null);
Assert.Equal(CreateValue(id1), result);
}
diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs
index da02198f3..d6ef0b12e 100644
--- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs
+++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs
@@ -52,7 +52,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
await sut.HandleAsync(context);
- A.CallTo(() => contentEnricher.EnrichAsync(A._, requestContext))
+ A.CallTo(() => contentEnricher.EnrichAsync(A._, A._, requestContext))
.MustNotHaveHappened();
}
@@ -70,7 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
Assert.Same(result, context.Result());
- A.CallTo(() => contentEnricher.EnrichAsync(A._, requestContext))
+ A.CallTo(() => contentEnricher.EnrichAsync(A._, A._, requestContext))
.MustNotHaveHappened();
}
@@ -86,7 +86,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var enriched = new ContentEntity();
- A.CallTo(() => contentEnricher.EnrichAsync(result, requestContext))
+ A.CallTo(() => contentEnricher.EnrichAsync(result, true, requestContext))
.Returns(enriched);
await sut.HandleAsync(context);
diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs
index e50722d01..c40297db7 100644
--- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs
+++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs
@@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FakeItEasy;
+using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
@@ -83,7 +84,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
var sut = new ContentEnricher(new[] { step1, step2 }, new Lazy(() => contentQuery));
- await sut.EnrichAsync(source, requestContext);
+ await sut.EnrichAsync(source, false, requestContext);
A.CallTo(() => step1.EnrichAsync(requestContext))
.MustHaveHappened();
@@ -108,7 +109,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
var sut = new ContentEnricher(new[] { step1, step2 }, new Lazy(() => contentQuery));
- await sut.EnrichAsync(source, requestContext);
+ await sut.EnrichAsync(source, false, requestContext);
Assert.Same(schema, step1.Schema);
Assert.Same(schema, step1.Schema);
@@ -117,9 +118,33 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
.MustHaveHappenedOnceExactly();
}
- private ContentEntity CreateContent()
+ [Fact]
+ public async Task Should_clone_data_when_requested()
+ {
+ var source = CreateContent(new NamedContentData());
+
+ var sut = new ContentEnricher(Enumerable.Empty(), new Lazy(() => contentQuery));
+
+ var result = await sut.EnrichAsync(source, true, requestContext);
+
+ Assert.NotSame(source.Data, result.Data);
+ }
+
+ [Fact]
+ public async Task Should_not_clone_data_when_not_requested()
+ {
+ var source = CreateContent(new NamedContentData());
+
+ var sut = new ContentEnricher(Enumerable.Empty(), new Lazy(() => contentQuery));
+
+ var result = await sut.EnrichAsync(source, false, requestContext);
+
+ Assert.Same(source.Data, result.Data);
+ }
+
+ private ContentEntity CreateContent(NamedContentData? data = null)
{
- return new ContentEntity { SchemaId = schemaId };
+ return new ContentEntity { SchemaId = schemaId, Data = data! };
}
}
}
diff --git a/backend/tests/Squidex.Infrastructure.Tests/Json/Objects/JsonObjectTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Json/Objects/JsonObjectTests.cs
index 04867c285..58d4184b3 100644
--- a/backend/tests/Squidex.Infrastructure.Tests/Json/Objects/JsonObjectTests.cs
+++ b/backend/tests/Squidex.Infrastructure.Tests/Json/Objects/JsonObjectTests.cs
@@ -182,6 +182,22 @@ namespace Squidex.Infrastructure.Json.Objects
Assert.Equal("[1, \"2\"]", json.ToString());
}
+ [Fact]
+ public void Should_create_array_from_source()
+ {
+ var json = JsonValue.Array(1, "2");
+
+ var copy = new JsonArray(json);
+
+ Assert.Equal("[1, \"2\"]", copy.ToJsonString());
+ Assert.Equal("[1, \"2\"]", copy.ToString());
+
+ copy.Clear();
+
+ Assert.Empty(copy);
+ Assert.NotEmpty(json);
+ }
+
[Fact]
public void Should_create_object()
{
@@ -236,6 +252,76 @@ namespace Squidex.Infrastructure.Json.Objects
Assert.Equal("null", json.ToString());
}
+ [Fact]
+ public void Should_clone_number_and_return_same()
+ {
+ var source = JsonValue.Create(1);
+
+ var clone = source.Clone();
+
+ Assert.Same(source, clone);
+ }
+
+ [Fact]
+ public void Should_clone_string_and_return_same()
+ {
+ var source = JsonValue.Create("test");
+
+ var clone = source.Clone();
+
+ Assert.Same(source, clone);
+ }
+
+ [Fact]
+ public void Should_clone_boolean_and_return_same()
+ {
+ var source = JsonValue.Create(true);
+
+ var clone = source.Clone();
+
+ Assert.Same(source, clone);
+ }
+
+ [Fact]
+ public void Should_clone_null_and_return_same()
+ {
+ var source = JsonValue.Null;
+
+ var clone = source.Clone();
+
+ Assert.Same(source, clone);
+ }
+
+ [Fact]
+ public void Should_clone_array_and_also_children()
+ {
+ var source = JsonValue.Array(JsonValue.Array(), JsonValue.Array());
+
+ var clone = (JsonArray)source.Clone();
+
+ Assert.NotSame(source, clone);
+
+ for (var i = 0; i < source.Count; i++)
+ {
+ Assert.NotSame(clone[i], source[i]);
+ }
+ }
+
+ [Fact]
+ public void Should_clone_object_and_also_children()
+ {
+ var source = JsonValue.Object().Add("1", JsonValue.Array()).Add("2", JsonValue.Array());
+
+ var clone = (JsonObject)source.Clone();
+
+ Assert.NotSame(source, clone);
+
+ foreach (var (key, value) in clone)
+ {
+ Assert.NotSame(value, source[key]);
+ }
+ }
+
[Fact]
public void Should_create_arrays_in_different_ways()
{