How to convert a class instance to JsonDocument?

Viewed 6673

Let's say we have an entity class that looks like this:

public class SerializedEntity
{
    public JsonDocument Payload { get; set; }

    public SerializedEntity(JsonDocument payload)
    {
        Payload = payload;
    }
}

According to npsql this generates a table with column payload of type jsonb for this class which is correct.

Now what I would like to do is take any class instance and store it as payload in this table e.g.:

public class Pizza {
    public string Name { get; set; }
    public int Size { get; set; }
}

should then be possible to be retrieved as an object with following structure:

{Name: "name", Size: 10}

So I need something like this:

var pizza = new Pizza("Margharita", 10);
var se = new SerializedEntity(someConverter.method(pizza))
5 Answers

With System.Text.Json it's a little awkward but possible:

using System.Text.Json;
using System.Text.Json.Serialization;

var pizza = new Pizza("Margharita", 10);
var se = new SerializedEntity(JsonDocument.Parse(JsonSerializer.Serialize(pizza)));

It has been built-in to dotnet core since (I think) v3.0, so you do not need any additional 3rd party libs. Just don't forget the usings.

There may be some tricks to get the parsing a little more efficient, though (using async API, perhaps or as Magnus suggests by serializing to binary using SerializeToUtf8Bytes).

I haven't been able to find any approach that goes directly from T or object to JsonDocument. And I cannot believe this is not possible somehow. Please leave a comment if you know how that works or add your answer.

You can use JsonSerializer.SerializeToDocument which was added in .NET 6. In your case you would end up with this:

var pizza = new Pizza("Margharita", 10);
var se = new SerializedEntity(JsonSerializer.SerializeToDocument(pizza))

Serailize it and than parse it to JsonDocument.

var doc = JsonDocument.Parse(JsonSerializer.SerializeToUtf8Bytes(
                new Pizza {Name = "Calzone", Size = 10}));

If your EF entity (SerializedEntity) will always have a Pizza as its serialized JSON document, then you can simply use POCO mapping - replace the JsonDocument property with a Pizza property, and map it to a jsonb column.

If the actual types you want vary (sometimes Pizza, sometimes something else), you can also map an object property to jsonb, and assign whatever you want to it. Npgsql will internally serialize any object to JSON:

class Program
{
    static void Main()
    {
        using var ctx = new BlogContext();
        ctx.Database.EnsureDeleted();
        ctx.Database.EnsureCreated();

        ctx.FoodEntities.Add(new FoodEntity { SomeJsonFood = new Pizza { Name = "Napoletana" } });
        ctx.FoodEntities.Add(new FoodEntity { SomeJsonFood = new Sushi { FishType = "Salmon" } });
        ctx.SaveChanges();
    }
}

public class BlogContext : DbContext
{
    public DbSet<FoodEntity> FoodEntities { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.UseNpgsql("...");
}

public class FoodEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
    [Column(TypeName = "jsonb")]
    public object SomeJsonFood { get; set; }
}

public class Pizza
{
    public string Name { get; set; }
    public int Size { get; set; }
}

public class Sushi
{
    public string FishType { get; set; }
}

Final solution for me:

public class SerializedEntity
{
    public object? Payload { get; set; }

    public SerializedEntity(object? payload)
    {
        Payload = payload;
    }
}

and the EF configuration for it:

public void Configure(EntityTypeBuilder<SerializedEntity> builder)
{
    builder.Property(n => n.Payload).HasColumnType("jsonb").HasConversion(
        v => JsonConvert.SerializeObject(v,
            new JsonSerializerSettings {NullValueHandling = NullValueHandling.Ignore}),
        v => JsonConvert.DeserializeObject<object?>(v,
            new JsonSerializerSettings {NullValueHandling = NullValueHandling.Ignore}));
}
Related