Jcg.Application.Core.Optics 1.0.2

There is a newer version of this package available.
See the version list below for details.
dotnet add package Jcg.Application.Core.Optics --version 1.0.2
                    
NuGet\Install-Package Jcg.Application.Core.Optics -Version 1.0.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Jcg.Application.Core.Optics" Version="1.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Jcg.Application.Core.Optics" Version="1.0.2" />
                    
Directory.Packages.props
<PackageReference Include="Jcg.Application.Core.Optics" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Jcg.Application.Core.Optics --version 1.0.2
                    
#r "nuget: Jcg.Application.Core.Optics, 1.0.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Jcg.Application.Core.Optics@1.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Jcg.Application.Core.Optics&version=1.0.2
                    
Install as a Cake Addin
#tool nuget:?package=Jcg.Application.Core.Optics&version=1.0.2
                    
Install as a Cake Tool

Overview

A C# Implementation of Functional Lenses, but adapted to Object-Oriented Programming.

License

MIT

Dependencies

⦁ Net Standard 2.1

Motivation

I always find complexity when updating nested objects, especially when dealing with nested collections. Common OOP Patterns like the Builder Pattern and some other tricks work, but they are not good enough because:

  1. They cause coupling
  2. Writing builders consume time.

Manipulating data structures can be challenging, but Functional Programming offers an elegant solution through the use of Lenses. A Lens enables you to focus on a specific part of a data structure, allowing you to get or set values without having to consider the entire structure.

This library applies some of those concepts to the object-oriented programming world, allowing you to update objects with a lens that is generic and composable.

It also works great with immutable records, as demonstrated in the tests.

You can:

  1. Update an object property.
  2. Update a nested object property.
  3. Update a collection of objects.
  4. Update a nested collection of objects.
  5. Update a property on an object inside a nested collection

There is no limit to the depth of the nested objects.

Example

We will use the following Customer class for the examples:

{
  "CustomerId": "b1a2c3d4-e5f6-7890-abcd-1234567890ef",
  "CustomerName": "Customer_b1a2c3d4e5f67890abcd1234567890ef",
  "ContactInfo": {
    "Address": {
      "Street": "Market Street"
    }
  },
  "Orders": [
    {
      "OrderId": "c0ffee12-3456-7890-abcd-1234567890ab",
      "Number": 11111,
      "Items": [
        {
          "ProductName": "Bolts",
          "Quantity": 100,
          "Price": 10.0
        }
      ]
    }
  ]
}

We will also use a Customer Builder to facilitate configuring the Customer object for the examples.

Example 1: Set the Customer.ContactInfo.Address.Street to "Main Street"

The following is a simple example to show the basic functionality.

[Fact]
    public void CanOperateOnNestedProperty()
    {
        // ***** ARRANGE *****

        var customer = Customer.Random;

        var customerContactAddressStreetLens = customer
            .CreateLens(cust => cust.ContactInfo,
                (cust, contactInf) => cust with { ContactInfo = contactInf })
            .FocusLens(contactInfo => contactInfo.Address,
                (contactInf, addr) => contactInf with { Address = addr })
            .FocusLens(address => address.Street,
                (add, street) => add with { Street = street });

        // ***** ACT *****

        customerContactAddressStreetLens.Value = "Elm Street";

        // ***** ASSERT *****

        // The updated object is always available in the RootObject property
        Assert.Equal("Elm Street", customerContactAddressStreetLens.RootObject.ContactInfo.Address.Street);
        Assert.Equal("Elm Street", customerContactAddressStreetLens. Value);
    }

Example 2: Add a new Order to the Customer

Here, a more complex use case. Adding an order to the Customer.Orders collection.

Keep in mind that we are not mutating the original object, but creating a new one with the updated collection.

[Fact]
    public void CanAddItemToNestedCollection()
    {
        // ***** ARRANGE *****

        var customer = new CustomerBuilder()
            .AddOrder(out var order1)
            .AddOrder(out var order2)
            .Build();

        var customerOrdersLens = customer
            .CreateLens(cust => cust.Orders,
                (cust, orders) => cust with { Orders = orders });

        // ***** ACT *****

        customerOrdersLens.AddWhenDoesNotExists(order => order.Number == 11111,
            () => new Order
            {
                OrderId = Guid.NewGuid(),
                Number = 11111,
                Items = []
            });

        // ***** ASSERT *****

        Assert.Equal(3, customerOrdersLens.RootObject.Orders.Count());
        Assert.Contains(customerOrdersLens.RootObject.Orders, o => o.Number == 11111);
    }

Example 3: Focus on a deeply nested collection and update a particular object property value

In this example, our Customer has an order that includes an item. We will update the item's product name.

[Fact]
    public void CanUpdateItemFromDeeplyNestedCollection()
    {
        // ***** ARRANGE *****

        var customer = new CustomerBuilder()
            .AddOrder(out var order1)
            .AddOrderItem(order1, out var line1)
            .Build();

        var customerOrderItemsLens = customer
            .CreateLens(
                c => c.Orders,
                (c, ord) => c with { Orders = ord })
            .FocusLens(orders => orders.First(o => o.OrderId == order1.OrderId),
                (orders, ord) => orders = orders.Select(o =>
                    o.OrderId == order1.OrderId ? ord : o))
            .FocusLens(orderOne => orderOne.Items,
                (orderO, it) => orderO with { Items = it });

        // ***** ACT *****

        customerOrderItemsLens.UpdateWhenExists(item => item.ProductName == line1.ProductName,
            item => item with { ProductName = "Bolts" });

        // ***** ASSERT *****

        var resultingOrder = customerOrderItemsLens.RootObject.Orders.First(o => o.OrderId == order1.OrderId);

        Assert.Single(resultingOrder.Items);
        Assert.Contains(resultingOrder.Items, i => i.ProductName == "Bolts");
    }

Other Examples

There are more use cases, you can see in the following test class

For instance:

  • Updating and reading properties.
  • Adding items to a deeply nested collection.
  • Removing items from a deeply nested collection.
  • Updating a particular item from a deeply nested collection.

Credits

Author: Julio C. Cachay. Chattanooga, TN, USA.

This library is inspired by the concept of lenses in functional programming, and in the optics.ts library

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.1

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.4 127 8/3/2025
1.0.3 102 8/3/2025
1.0.2 103 8/3/2025
1.0.0 76 8/3/2025