Skip to content

Commit

Permalink
Serialize recursively placing all nested objects in the 'included' array
Browse files Browse the repository at this point in the history
  • Loading branch information
rhyek committed May 27, 2016
1 parent 9fff8a2 commit cf68749
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 26 deletions.
20 changes: 11 additions & 9 deletions Saule/Serialization/ResourceSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public JObject Serialize(JsonSerializer serializer)

var result = new JObject
{
["data"] = SerializeArrayOrObject(objectJson, SerializeData),
["data"] = SerializeArrayOrObject(_resource, objectJson, SerializeData),
["links"] = new JObject
{
["self"] = new JValue(_baseUrl)
Expand All @@ -66,21 +66,21 @@ public JObject Serialize(JsonSerializer serializer)
return result;
}

private static JToken SerializeArrayOrObject(JToken token, Func<IDictionary<string, JToken>, JToken> serializeObj)
private static JToken SerializeArrayOrObject(ApiResource resource, JToken token, Func<ApiResource, IDictionary<string, JToken>, JToken> serializeObj)
{
var dataArray = token as JArray;

// single thing, just serialize it
if (dataArray == null)
{
return token is JObject ? serializeObj((JObject)token) : null;
return token is JObject ? serializeObj(resource, (JObject)token) : null;
}

// serialize each element separately
var data = new JArray();
foreach (var obj in dataArray.OfType<JObject>())
{
data.Add(serializeObj(obj));
data.Add(serializeObj(resource, obj));
}

return data;
Expand Down Expand Up @@ -171,12 +171,12 @@ private JToken CreateTopLevelLinks(int count)
return result;
}

private JToken SerializeData(IDictionary<string, JToken> properties)
private JToken SerializeData(ApiResource resource, IDictionary<string, JToken> properties)
{
var data = SerializeMinimalData(properties);

data["attributes"] = SerializeAttributes(properties);
data["relationships"] = SerializeRelationships(properties);
data["relationships"] = SerializeRelationships(resource, properties);

if (_isCollection)
{
Expand All @@ -199,11 +199,11 @@ private JToken SerializeAttributes(IDictionary<string, JToken> properties)
return SerializeAttributes(properties, _resource);
}

private JToken SerializeRelationships(IDictionary<string, JToken> properties)
private JToken SerializeRelationships(ApiResource resource, IDictionary<string, JToken> properties)
{
var relationships = new JObject();

foreach (var rel in _resource.Relationships)
foreach (var rel in resource.Relationships)
{
relationships[rel.Name] = SerializeRelationship(rel, properties);
}
Expand Down Expand Up @@ -240,8 +240,9 @@ private JToken SerializeRelationship(ResourceRelationship relationship, IDiction
private JToken GetRelationshipData(ResourceRelationship relationship, JToken relationshipValues)
{
var data = SerializeArrayOrObject(
relationship.RelatedResource,
relationshipValues,
props =>
(resource, props) =>
{
var values = SerializeMinimalData(props, relationship.RelatedResource);
var includedData = values.DeepClone();
Expand All @@ -250,6 +251,7 @@ private JToken GetRelationshipData(ResourceRelationship relationship, JToken rel
(string)EnsureHasId(props, relationship.RelatedResource));
includedData["attributes"] = SerializeAttributes(props, relationship.RelatedResource);
includedData["relationships"] = SerializeRelationships(resource, props);
includedData["links"] = AddUrl(new JObject(), "self", url);
if (!IsResourceIncluded(includedData))
{
Expand Down
56 changes: 44 additions & 12 deletions Tests/Models/Recursion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,64 @@ namespace Tests.Models
{
internal static class Recursion
{
public class Resource : ApiResource
public class FirstModelResource : ApiResource
{
public Resource()
public FirstModelResource()
{
BelongsTo<Second>("Model");
BelongsTo<SecondModelResource>("Child");
}
}

public class SecondModelResource : ApiResource
{
public SecondModelResource()
{
BelongsTo<FirstModelResource>("Parent");
BelongsTo<ThirdModelResource>("Child");
}
}

public class ThirdModelResource : ApiResource
{
public ThirdModelResource()
{
BelongsTo<SecondModelResource>("Parent");
BelongsTo<FourthModelResource>("Child");
}
}

private class Second : ApiResource
public class FourthModelResource : ApiResource
{
public FourthModelResource()
{
public Second()
{
BelongsTo<Resource>("Model");
}
BelongsTo<ThirdModelResource>("Parent");
}
}

public class FirstModel
{
public string Id => "123";
public SecondModel Model { get; set; }
public string Id = "1";
public SecondModel Child { get; set; }
}

public class SecondModel
{
public string Id => "456";
public FirstModel Model { get; set; }
public string Id = "2";
public FirstModel Parent { get; set; }
public ThirdModel Child { get; set; }
}

public class ThirdModel
{
public string Id = "3";
public SecondModel Parent { get; set; }
public FourthModel Child { get; set; }
}

public class FourthModel
{
public string Id = "4";
public ThirdModel Parent { get; set; }
}
}
}
17 changes: 12 additions & 5 deletions Tests/Serialization/ResourceSerializerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,25 @@ public void HandlesRecursiveProperties()
{
var firstModel = new Recursion.FirstModel();
var secondModel = new Recursion.SecondModel();
firstModel.Model = secondModel;
secondModel.Model = firstModel;

var target = new ResourceSerializer(firstModel, new Recursion.Resource(),
GetUri(id: "123"), DefaultPathBuilder, null);
var thirdModel = new Recursion.ThirdModel();
var fourthModel = new Recursion.FourthModel();
firstModel.Child = secondModel;
secondModel.Parent = firstModel;
secondModel.Child = thirdModel;
thirdModel.Parent = secondModel;
thirdModel.Child = fourthModel;
fourthModel.Parent = thirdModel;

var target = new ResourceSerializer(firstModel, new Recursion.FirstModelResource(),
GetUri(id: firstModel.Id), DefaultPathBuilder, null);

var result = target.Serialize();
_output.WriteLine(result.ToString());

var id = result["data"].Value<string>("id");

Assert.Equal(firstModel.Id, id);
Assert.Equal(3, (result["included"] as JArray).Count);
}

[Fact(DisplayName = "Uses a property called 'Id' when none is specified for Ids")]
Expand Down

4 comments on commit cf68749

@bxh
Copy link
Contributor

@bxh bxh commented on cf68749 Jun 14, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a possibility that you guys will a cut a new release soon? We have a product that couldn't ship without this commit.

@joukevandermaas
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is currently in a pre-release. I'm not opposed to releasing a new stable version if that would help you.

@bxh
Copy link
Contributor

@bxh bxh commented on cf68749 Jun 14, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That will be awesome!

@joukevandermaas
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bxh I just cut a release that includes this PR (https://github.com/joukevandermaas/saule/releases/tag/saule-v1.5.0).

Please sign in to comment.