Alexander Zeitler

Serializing .NET objects for use with Alpine.js x-data attribute

Published on Sunday, January 15, 2023

Photo by Clint Patterson on Unsplash

Serialization in progress...

Alpine.js allows you set javascript objects in the scope of a HTML tag using the x-data attribute:

<div x-data="{ open: false }">
    <button @click="open = ! open">Toggle Content</button>
     
    <div x-show="open">
        Content...
    </div>
</div>

The data is a plain javascript object whose properties are available to all child elements of the outer div.

Clicking the button will show/hide the content of the inner div.

In a typical SPA like setup you might use fetch together with Alpine's json function to include server side data:

<div
    x-data="{ posts: [] }"
    x-init="posts = await (await fetch('/posts')).json()"
>...</div>

But what to do if you want to render the Razor view and let it include the server side object directly into Alpine's x-data attribute? The closest you can get using out of the box serialization is this:

<div x-data='@Html.Raw(Json.Serialize(new { Open = false}))'></div>

This will render the expected output, and you can toggle the content using the button.

But as you may have noticed, the x-data attribute in the last sample is using single quotes. When using double quotes, the code will break at runtime. Your single quotes might get replaced by double quotes due to IDE settings on when saving the cshtml file.

A better solution would be to use single quotes for the javascript object string and date properties.

One approach could be to replace the instance of IJsonHelper which is being called by Json.Serialize inside the view.

Yet, the implementation is using System.Text.Json which neither allows to use non-quoted property names nor use a different quote char.

So, back to Newtonsoft.Json - here's a possible solution (we could also create a new implementation of IJSonHelper, of course):

public static class JavaScriptConverter
{
  public static IHtmlContent SerializeObject(
    object value
  )
  {
    using var stringWriter = new StringWriter();
    using var jsonWriter = new JsonTextWriter(stringWriter);

    var serializer = new JsonSerializer
    {
      ContractResolver = new CamelCasePropertyNamesContractResolver()
    };

    jsonWriter.QuoteName = false;
    jsonWriter.QuoteChar = '\'';
    serializer.Serialize(jsonWriter, value);

    return new HtmlString(stringWriter.ToString());
  }
}

Using the SerializeObject method, we solve three tasks:

.NET objects get serialized

  • using camelCase
  • without quotes in property names, hence creating a Javascript object
  • using single quotes for string and date properties

Usage inside the view changed to following:

<div x-data="@JavaScriptSerializer.SerializeObject(new { Open = false})"></div>

This is less error-prone but still feels a bit cumbersome.

So let's wrap the JavaScriptSerializer call using a Tag Helper:

[HtmlTargetElement("*", Attributes = "alpine-data")]
public class AlpineTagHelper : TagHelper
{
  public override void Process(
    TagHelperContext context,
    TagHelperOutput output
  )
  {
    output.Attributes.Add("x-data", JavaScriptConverter.SerializeObject(Data));
    base.Process(context, output);
  }

  [HtmlAttributeName("alpine-data")] 
  public object Data { get; set; } = null!;
}

Now lets see how we can use this one.

First, register the Tag Helper in _ViewImports.cshtml:

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Alpine.TagHelpers

And just use the Tag Helper:

<div alpine-data="new { Open = false }">
  <button x-on:click="open = !open">Show</button>
  <div x-show="open" x-cloak>Some details...</div>
</div>

The rendered result:

<div x-data="{open:false}">
  <button x-on:click="open = !open">Show</button>
  <div x-show="open">Some details...</div>
</div>

The Tag Helper is available as a NuGet package:

dotnet add package Alpine.TagHelpers
What are your thoughts about
"Serializing .NET objects for use with Alpine.js x-data attribute"?
Drop me a line - I'm looking forward to your feedback!
Please be aware that I'm no longer active on social media. I'm just cross posting things over there (it's a bot).
Imprint | Privacy